218 lines
6.6 KiB
Ruby
218 lines
6.6 KiB
Ruby
##
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
|
|
require 'msf/core'
|
|
|
|
class MetasploitModule < Msf::Auxiliary
|
|
|
|
include Msf::Exploit::Remote::Smtp
|
|
include Msf::Auxiliary::Report
|
|
include Msf::Auxiliary::Scanner
|
|
|
|
def initialize
|
|
super(
|
|
'Name' => 'SMTP User Enumeration Utility',
|
|
'Description' => %q{
|
|
The SMTP service has two internal commands that allow the enumeration
|
|
of users: VRFY (confirming the names of valid users) and EXPN (which
|
|
reveals the actual address of users aliases and lists of e-mail
|
|
(mailing lists)). Through the implementation of these SMTP commands can
|
|
reveal a list of valid users.
|
|
},
|
|
'References' =>
|
|
[
|
|
['URL', 'http://www.ietf.org/rfc/rfc2821.txt'],
|
|
['OSVDB', '12551'],
|
|
['CVE', '1999-0531']
|
|
],
|
|
'Author' =>
|
|
[
|
|
'Heyder Andrade <heyder[at]alligatorteam.org>',
|
|
'nebulus'
|
|
],
|
|
'License' => MSF_LICENSE
|
|
)
|
|
|
|
register_options(
|
|
[
|
|
Opt::RPORT(25),
|
|
OptString.new('USER_FILE',
|
|
[
|
|
true, 'The file that contains a list of probable users accounts.',
|
|
File.join(Msf::Config.install_root, 'data', 'wordlists', 'unix_users.txt')
|
|
]),
|
|
OptBool.new('UNIXONLY', [ true, 'Skip Microsoft bannered servers when testing unix users', true])
|
|
], self.class)
|
|
|
|
deregister_options('MAILTO','MAILFROM')
|
|
end
|
|
|
|
def smtp_send(data=nil)
|
|
begin
|
|
result=''
|
|
code=0
|
|
sock.put("#{data}")
|
|
result=sock.get_once
|
|
result.chomp! if(result)
|
|
code = result[0..2].to_i if result
|
|
return result, code
|
|
rescue Rex::ConnectionError, Errno::ECONNRESET, ::EOFError
|
|
return result, code
|
|
rescue ::Exception => e
|
|
print_error("#{rhost}:#{rport} Error smtp_send: '#{e.class}' '#{e}'")
|
|
return nil, 0
|
|
end
|
|
end
|
|
|
|
def run_host(ip)
|
|
users_found = {}
|
|
result = nil # temp for storing result of SMTP request
|
|
code = 0 # status code parsed from result
|
|
vrfy = true # if vrfy allowed
|
|
expn = true # if expn allowed
|
|
rcpt = true # if rcpt allowed and useful
|
|
usernames = extract_words(datastore['USER_FILE'])
|
|
|
|
cmd = 'HELO' + " " + "localhost" + "\r\n"
|
|
connect
|
|
result, code = smtp_send(cmd)
|
|
|
|
if(not result)
|
|
print_error("#{rhost}:#{rport} Connection but no data...skipping")
|
|
return
|
|
end
|
|
banner.chomp! if (banner)
|
|
if(banner =~ /microsoft/i and datastore['UNIXONLY'])
|
|
print_status("#{rhost}:#{rport} Skipping microsoft (#{banner})")
|
|
return
|
|
elsif(banner)
|
|
print_status("#{rhost}:#{rport} Banner: #{banner}")
|
|
end
|
|
|
|
domain = result.split()[1]
|
|
domain = 'localhost' if(domain == '' or not domain or domain.downcase == 'hello')
|
|
|
|
|
|
vprint_status("#{ip}:#{rport} Domain Name: #{domain}")
|
|
|
|
result, code = smtp_send("VRFY root\r\n")
|
|
vrfy = (code == 250)
|
|
users_found = do_enum('VRFY', usernames) if (vrfy)
|
|
|
|
if(users_found.empty?)
|
|
# VRFY failed, lets try EXPN
|
|
result, code = smtp_send("EXPN root\r\n")
|
|
expn = (code == 250)
|
|
users_found = do_enum('EXPN', usernames) if(expn)
|
|
end
|
|
|
|
if(users_found.empty?)
|
|
# EXPN/VRFY failed, drop back to RCPT TO
|
|
result, code = smtp_send("MAIL FROM: root\@#{domain}\r\n")
|
|
if(code == 250)
|
|
user = Rex::Text.rand_text_alpha(8)
|
|
result, code = smtp_send("RCPT TO: #{user}\@#{domain}\r\n")
|
|
if(code >= 250 and code <= 259)
|
|
vprint_status("#{rhost}:#{rport} RCPT TO: Allowed for random user (#{user})...not reliable? #{code} '#{result}'")
|
|
rcpt = false
|
|
else
|
|
smtp_send("RSET\r\n")
|
|
users_found = do_rcpt_enum(domain, usernames)
|
|
end
|
|
else
|
|
rcpt = false
|
|
end
|
|
end
|
|
|
|
if(not vrfy and not expn and not rcpt)
|
|
print_status("#{rhost}:#{rport} could not be enumerated (no EXPN, no VRFY, invalid RCPT)")
|
|
return
|
|
end
|
|
finish_host(users_found)
|
|
disconnect
|
|
|
|
rescue Rex::ConnectionError, Errno::ECONNRESET, Rex::ConnectionTimeout, EOFError, Errno::ENOPROTOOPT
|
|
rescue ::Exception => e
|
|
print_error("Error: #{rhost}:#{rport} '#{e.class}' '#{e}'")
|
|
end
|
|
|
|
def finish_host(users_found)
|
|
if users_found and not users_found.empty?
|
|
print_good("#{rhost}:#{rport} Users found: #{users_found.sort.join(", ")}")
|
|
report_note(
|
|
:host => rhost,
|
|
:port => rport,
|
|
:type => 'smtp.users',
|
|
:data => {:users => users_found.join(", ")}
|
|
)
|
|
end
|
|
end
|
|
|
|
def kiss_and_make_up(cmd)
|
|
vprint_status("#{rhost}:#{rport} SMTP server annoyed...reconnecting and saying HELO again...")
|
|
disconnect
|
|
connect
|
|
smtp_send("HELO localhost\r\n")
|
|
result, code = smtp_send("#{cmd}")
|
|
result.chomp!
|
|
cmd.chomp!
|
|
vprint_status("#{rhost}:#{rport} - SMTP - Re-trying #{cmd} received #{code} '#{result}'")
|
|
return result,code
|
|
end
|
|
|
|
def do_enum(cmd, usernames)
|
|
|
|
users = []
|
|
usernames.each {|user|
|
|
next if user.downcase == 'root'
|
|
result, code = smtp_send("#{cmd} #{user}\r\n")
|
|
vprint_status("#{rhost}:#{rport} - SMTP - Trying #{cmd} #{user} received #{code} '#{result}'")
|
|
result, code = kiss_and_make_up("#{cmd} #{user}\r\n") if(code == 0 and result.to_s == '')
|
|
if(code == 250)
|
|
vprint_status("#{rhost}:#{rport} - Found user: #{user}")
|
|
users.push(user)
|
|
end
|
|
}
|
|
return users
|
|
end
|
|
|
|
def do_rcpt_enum(domain, usernames)
|
|
users = []
|
|
usernames.each {|user|
|
|
next if user.downcase == 'root'
|
|
vprint_status("#{rhost}:#{rport} - SMTP - Trying MAIL FROM: root\@#{domain} / RCPT TO: #{user}...")
|
|
result, code = smtp_send("MAIL FROM: root\@#{domain}\r\n")
|
|
result, code = kiss_and_make_up("MAIL FROM: root\@#{domain}\r\n") if(code == 0 and result.to_s == '')
|
|
|
|
if(code == 250)
|
|
result, code = smtp_send("RCPT TO: #{user}\@#{domain}\r\n")
|
|
if(code == 0 and result.to_s == '')
|
|
kiss_and_make_up("MAIL FROM: root\@#{domain}\r\n")
|
|
result, code = smtp_send("RCPT TO: #{user}\@#{domain}\r\n")
|
|
end
|
|
|
|
if(code == 250)
|
|
vprint_status("#{rhost}:#{rport} - Found user: #{user}")
|
|
users.push(user)
|
|
end
|
|
else
|
|
vprint_status("#{rhost}:#{rport} MAIL FROM: #{user} NOT allowed during brute...aborting ( '#{code}' '#{result}')")
|
|
break
|
|
end
|
|
smtp_send("RSET\r\n")
|
|
}
|
|
return users
|
|
end
|
|
|
|
def extract_words(wordfile)
|
|
return [] unless wordfile && File.readable?(wordfile)
|
|
words = File.open(wordfile, "rb") {|f| f.read}
|
|
save_array = words.split(/\r?\n/)
|
|
return save_array
|
|
end
|
|
|
|
end
|