adf03e28ce
This also deals with some errant tabs where internal spaces should be, as well as one syntax error which was preventing an old meterpreter script from ever working correctly. Some day, we need to get rid of those Meterpeter scripts. Srsly.
219 lines
6.7 KiB
Ruby
219 lines
6.7 KiB
Ruby
##
|
|
# This module requires Metasploit: http//metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
|
|
require 'msf/core'
|
|
|
|
class Metasploit3 < 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' =>
|
|
[
|
|
'==[ Alligator Security Team ]==',
|
|
'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
|