Skip to content

Commit 85de75c

Browse files
author
HD Moore
committed
Adds a smtp ntlm domain scanner, lands rapid7#4241
2 parents abc0640 + 3ddf848 commit 85de75c

File tree

2 files changed

+128
-2
lines changed

2 files changed

+128
-2
lines changed

lib/msf/core/exploit/smtp.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ def initialize(info = {})
2424
[
2525
Opt::RHOST,
2626
Opt::RPORT(25),
27-
OptString.new('MAILFROM', [ true, 'FROM address of the e-mail', '[email protected]']),
28-
OptString.new('MAILTO', [ true, 'TO address of the e-mail', '[email protected]']),
27+
OptString.new('MAILFROM', [ true, 'FROM address of the e-mail', '[email protected]']),
28+
OptString.new('MAILTO', [ true, 'TO address of the e-mail', '[email protected]']),
2929
], Msf::Exploit::Remote::Smtp)
3030
register_autofilter_ports([ 25, 465, 587, 2525, 25025, 25000])
3131
register_autofilter_services(%W{ smtp smtps})
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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+
8+
class Metasploit3 < Msf::Auxiliary
9+
10+
include Msf::Exploit::Remote::Smtp
11+
include Msf::Auxiliary::Report
12+
include Msf::Auxiliary::Scanner
13+
14+
def initialize
15+
super(
16+
'Name' => 'SMTP NTLM Domain Extraction',
17+
'Description' => 'Extract the Windows domain name from a SMTP NTLM challenge.',
18+
'References' => [ ['URL', 'http://msdn.microsoft.com/en-us/library/cc246870.aspx' ] ],
19+
'Author' => [ 'Rich Whitcroft <rwhitcroft[at]digitalboundary.net>' ],
20+
'License' => MSF_LICENSE
21+
)
22+
23+
register_options(
24+
[
25+
Opt::RPORT(25),
26+
OptString.new('EHLO_DOMAIN', [ true, 'The domain to send with the EHLO command', 'localhost' ]),
27+
], self.class)
28+
29+
deregister_options('MAILTO', 'MAILFROM')
30+
end
31+
32+
def run_host(ip)
33+
begin
34+
domain = nil
35+
connect
36+
37+
unless banner
38+
vprint_error("#{rhost}:#{rport} No banner received, aborting...")
39+
return
40+
end
41+
42+
vprint_status("#{rhost}:#{rport} Connected: #{banner.strip.inspect}")
43+
44+
# Report the last line of the banner as services information (typically the interesting one)
45+
report_service(host: rhost, port: rport, name: 'smtp', proto: 'tcp', info: banner.strip.split("\n").last)
46+
47+
# Send a EHLO and parse the extensions returned
48+
sock.puts("EHLO " + datastore['EHLO_DOMAIN'] + "\r\n")
49+
50+
# Find all NTLM references in the EHLO response
51+
exts = sock.get_once.to_s.split(/\n/).grep(/NTLM/)
52+
if exts.length == 0
53+
vprint_error("#{rhost}:#{rport} No NTLM extensions found")
54+
return
55+
end
56+
57+
exts.each do |ext|
58+
59+
# Extract the reply minus the first 4 chars (response code + dash)
60+
e = ext[4..-1].chomp
61+
62+
# Try the usual AUTH NTLM approach if possible, otherwise echo the extension back to server
63+
if e =~ /AUTH.*NTLM/
64+
sock.puts("AUTH NTLM\r\n")
65+
vprint_status("#{rhost}:#{rport} Sending AUTH NTLM")
66+
else
67+
sock.puts(e + "\r\n")
68+
vprint_status("#{rhost}:#{rport} Sending #{e}")
69+
end
70+
71+
# We expect a "334" code to go ahead with NTLM auth
72+
reply = sock.get_once.to_s
73+
if reply !~ /^334\s+/m
74+
vprint_status("#{rhost}:#{rport} Expected a 334 response, received #{reply.strip.inspect} aborting...")
75+
break
76+
else
77+
# Send the NTLM AUTH blob to tell the server we're ready to auth
78+
blob = "TlRMTVNTUAABAAAAt4II4gAAAAAAAAAAAAAAAAAAAAAFAs4OAAAADw=="
79+
sock.puts(blob + "\r\n")
80+
81+
# Capture the challenge sent by server
82+
challenge = sock.get_once.to_s.split(/\s+/).last
83+
84+
if challenge.length == 0
85+
vprint_status("#{rhost}:#{rport} Empty challenge response, aborting...")
86+
break
87+
end
88+
89+
begin
90+
# Extract the domain out of the NTLM response
91+
ntlm_reply = Rex::Proto::NTLM::Message.parse(Rex::Text.decode_base64(challenge))
92+
if ! ntlm_reply && ntlm_reply.has_key?(:target_name)
93+
vprint_status("#{rhost}:#{rport} Invalid challenge response, aborting...")
94+
break
95+
end
96+
97+
# TODO: Extract the server name from :target_info as well
98+
domain = ntlm_reply[:target_name].value.to_s.gsub(/\x00/, '')
99+
if domain.to_s.length == 0
100+
vprint_status("#{rhost}:#{rport} Invalid target name in challenge response, aborting...")
101+
break
102+
end
103+
104+
print_good("#{rhost}:#{rport} Domain: #{domain}")
105+
report_note(host: rhost, port: rport, proto: 'tcp', type: 'smtp.ntlm_auth_info', data: { domain: domain })
106+
break
107+
108+
rescue ::Rex::ArgumentError
109+
vprint_status("#{rhost}:#{rport} Invalid challenge response message, aborting...")
110+
break
111+
end
112+
end
113+
end
114+
115+
if ! domain
116+
vprint_error("#{rhost}:#{rport} No NTLM domain found")
117+
end
118+
119+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error
120+
# Ignore common networking and response timeout errors
121+
ensure
122+
disconnect
123+
end
124+
end
125+
126+
end

0 commit comments

Comments
 (0)