-
Notifications
You must be signed in to change notification settings - Fork 13.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
alternative smtp_enum.rb #1915
alternative smtp_enum.rb #1915
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
## | ||
# $Id: smtp_enum.rb 14774 2012-02-21 01:42:17Z rapid7 $ | ||
## | ||
|
||
## | ||
# This file is part of the Metasploit Framework and may be subject to | ||
# redistribution and commercial restrictions. Please see the Metasploit | ||
|
@@ -17,6 +21,7 @@ class Metasploit3 < Msf::Auxiliary | |
def initialize | ||
super( | ||
'Name' => 'SMTP User Enumeration Utility', | ||
'Version' => '$Revision: 14774 $', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
'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 | ||
|
@@ -33,7 +38,8 @@ def initialize | |
'Author' => | ||
[ | ||
'==[ Alligator Security Team ]==', | ||
'Heyder Andrade <heyder[at]alligatorteam.org>' | ||
'Heyder Andrade <heyder[at]alligatorteam.org>', | ||
'nebulus' | ||
], | ||
'License' => MSF_LICENSE | ||
) | ||
|
@@ -45,8 +51,9 @@ def initialize | |
[ | ||
true, 'The file that contains a list of probable users accounts.', | ||
File.join(Msf::Config.install_root, 'data', 'wordlists', 'unix_users.txt') | ||
] | ||
)], self.class) | ||
]), | ||
OptBool.new('UNIXONLY', [ true, 'Skip Microsoft bannered servers when testing unix users', true]) | ||
], self.class) | ||
|
||
deregister_options('MAILTO','MAILFROM') | ||
end | ||
|
@@ -55,174 +62,167 @@ def target | |
"#{rhost}:#{rport}" | ||
end | ||
|
||
def smtp_send(data=nil, con=true) | ||
def smtp_send(data=nil) | ||
begin | ||
@result='' | ||
@coderesult='' | ||
if (con) | ||
@connected=false | ||
connect | ||
select(nil,nil,nil,0.4) | ||
end | ||
@connected=true | ||
result='' | ||
code=0 | ||
sock.put("#{data}") | ||
@result=sock.get_once | ||
@coderesult=@result[0..2] if @result | ||
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("Error: #{e}") | ||
raise e | ||
print_error("#{target} Error smtp_send: '#{e.class}' '#{e}' '#{e.backtrace}'") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should never print backtraces. Log them instead, please. |
||
return nil, 0 | ||
end | ||
end | ||
|
||
def run_host(ip) | ||
@users_found = {} | ||
@mails_found = {} | ||
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" | ||
smtp_send(cmd,true) | ||
print_status(banner) | ||
@domain = @result.split()[1].split(".")[1..-1].join(".") | ||
print_status("Domain Name: #{@domain}") | ||
connect | ||
result, code = smtp_send(cmd) | ||
|
||
begin | ||
cmd = 'VRFY' + " " + "root" + "\r\n" | ||
smtp_send(cmd,!@connected) | ||
if (@result.match(%r{Cannot})) or (@result.match(%r{recognized})) | ||
vprint_status("VRFY command disabled") | ||
elsif (@result.match(%r{restricted})) | ||
vprint_status("VRFY command restricted") | ||
else | ||
vprint_status("VRFY command enabled") | ||
vrfy_ok=true | ||
end | ||
if(not result or result == nil) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is redundant. if |
||
print_error("#{target} Connection but no data...skipping") | ||
return | ||
end | ||
banner.chomp! if (banner) | ||
if(banner =~ /microsoft/i and datastore['UNIXONLY']) | ||
print_status("#{target} Skipping microsoft (#{banner})") | ||
return | ||
elsif(banner) | ||
print_status("#{target} Banner: #{banner}") | ||
end | ||
|
||
domain = result.split()[1] | ||
domain = 'localhost' if(domain == '' or not domain or domain.downcase == 'hello') | ||
|
||
begin | ||
if (vrfy_ok) | ||
extract_words(datastore['USER_FILE']).each {|user| | ||
do_vrfy_enum(user) | ||
} | ||
else | ||
do_mail_from() | ||
extract_words(datastore['USER_FILE']).each {|user| | ||
return finish_host() if ((do_rcpt_enum(user)) == :abort) | ||
} | ||
end | ||
|
||
if(@users_found.empty?) | ||
print_status("#{target} No users or e-mail addresses found.") | ||
vprint_status("#{ip}:#{rport} Domain Name: #{domain}") | ||
|
||
result, code = smtp_send("VRFY root\r\n") | ||
vrfy = false if (code != 250) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this could be simplified as |
||
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 = false if (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("#{target} 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 | ||
vprint_status("#{target} - SMTP - Trying to get valid e-mail addresses") | ||
@users_found.keys.each {|mails| | ||
return finish_host() if((do_get_mails(mails)) == :abort) | ||
} | ||
finish_host() | ||
disconnect | ||
rcpt = false | ||
end | ||
end | ||
end | ||
|
||
def finish_host() | ||
if @users_found && !@users_found.empty? | ||
print_good("#{target} Users found: #{@users_found.keys.sort.join(", ")}") | ||
report_note( | ||
:host => rhost, | ||
:port => rport, | ||
:type => 'smtp.users', | ||
:data => {:users => @users_found.keys.join(", ")} | ||
) | ||
if(not vrfy and not expn and not rcpt) | ||
print_status("#{target} 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( (e.to_str == 'execution expired') ? "Error: #{target} Execution expired" : "Error: #{target} '#{e.class}' '#{e}' '#{e.backtrace}'") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like you're trying to treat timeout differently. You should rescue |
||
end | ||
|
||
if(@mails_found.nil? or @mails_found.empty?) | ||
print_status("#{target} No e-mail addresses found.") | ||
else | ||
print_good("#{target} E-mail addresses found: #{@mails_found.keys.sort.join(", ")}") | ||
def finish_host(users_found) | ||
ip, port = target.split(':') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if users_found and not users_found.empty? | ||
print_good("#{target} Users found: #{users_found.sort.join(", ")}") | ||
report_note( | ||
:host => rhost, | ||
:port => rport, | ||
:type => 'smtp.mails', | ||
:data => {:mails => @mails_found.keys.join(", ")} | ||
:host => ip, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
:port => port, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
:type => 'smtp.users', | ||
:data => {:users => users_found.join(", ")} | ||
) | ||
end | ||
end | ||
|
||
def do_vrfy_enum(user) | ||
cmd = 'VRFY' + " " + user + "\r\n" | ||
smtp_send(cmd,!@connected) | ||
vprint_status("#{target} - SMTP - Trying name: '#{user}'") | ||
case @coderesult.to_i | ||
when (250..259) | ||
print_good "#{target} - Found user: #{user}" | ||
@users_found[user] = :reported | ||
mail = @result.scan(%r{\<(.*)(@)(.*)\>}) | ||
unless (mail.nil? || mail.empty?) | ||
@mails_found[mail.to_s] = :reported | ||
end | ||
end | ||
def kiss_and_make_up(cmd) | ||
vprint_status("#{target} 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("#{target} - SMTP - Re-trying #{cmd} received #{code} '#{result}'") | ||
return result,code | ||
end | ||
|
||
def do_mail_from() | ||
vprint_status("Trying to use to RCPT TO command") | ||
cmd = 'MAIL FROM:' + " root@" + @domain + "\r\n" | ||
smtp_send(cmd,!@connected) | ||
if (@coderesult == '501') && @domain.split(".").count > 2 | ||
print_error "#{target} - MX domain failure for #{@domain}, trying #{@domain.split(/\./).slice(-2,2).join(".")}" | ||
cmd = 'MAIL FROM:' + " root@" + @domain.split(/\./).slice(-2,2).join(".") + "\r\n" | ||
smtp_send(cmd,!@connected) | ||
if (@coderesult == '501') | ||
print_error "#{target} - MX domain failure for #{@domain.split(/\./).slice(-2,2).join(".")}" | ||
return :abort | ||
def do_enum(cmd, usernames) | ||
|
||
users = [] | ||
usernames.each {|user| | ||
next if user.downcase == 'root' | ||
result, code = smtp_send("#{cmd} #{user}\r\n") | ||
vprint_status("#{target} - 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("#{target} - Found user: #{user}") | ||
users.push(user) | ||
end | ||
elsif (@coderesult == '501') | ||
print_error "#{target} - MX domain failure for #{@domain}" | ||
return :abort | ||
end | ||
} | ||
return users | ||
end | ||
|
||
def do_rcpt_enum(user) | ||
cmd = 'RCPT TO:' + " " + user + "\r\n" | ||
smtp_send(cmd,!@connected) | ||
vprint_status("#{target} - SMTP - Trying name: '#{user}'") | ||
case @coderesult.to_i | ||
# 550 is User unknown, which obviously isn't fatal when trying to | ||
# enumerate users, so only abort on other 500-series errors. See #4031 | ||
when (500..549), (551..599) | ||
print_error "#{target} : #{@result.strip if @result} " | ||
print_error "#{target} : Enumeration not possible" | ||
return :abort | ||
when (250..259) | ||
print_good "#{target} - Found user: #{user}" | ||
@users_found[user] = :reported | ||
mail = @result.scan(%r{\<(.*)(@)(.*)\>}) | ||
unless (mail.nil? || mail.empty?) | ||
@mails_found[mail.to_s] = :reported | ||
end | ||
end | ||
end | ||
def do_rcpt_enum(domain, usernames) | ||
users = [] | ||
usernames.each {|user| | ||
next if user.downcase == 'root' | ||
vprint_status("#{target} - 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 | ||
|
||
def do_get_mails(user) | ||
cmd = 'EXPN' + " " + user + "\r\n" | ||
smtp_send(cmd,!@connected) | ||
if (@coderesult == '502') | ||
print_error "#{target} - EXPN : #{@result.strip if @result}" | ||
return :abort | ||
else | ||
unless (@result.nil? || @result.empty?) | ||
mail = @result.scan(%r{\<(.*)(@)(.*)\>}) | ||
unless (mail.nil? || mail.empty?) | ||
print_good "#{target} - Mail Found: #{mail}" | ||
@mails_found[mail.to_s] = :reported | ||
if(code == 250) | ||
vprint_status("#{target} - Found user: #{user}") | ||
users.push(user) | ||
end | ||
else | ||
vprint_status("#{target} MAIL FROM: #{user} NOT allowed during brute...aborting ( '#{code}' '#{result}')") | ||
break | ||
end | ||
end | ||
smtp_send("RSET\r\n") | ||
} | ||
return users | ||
end | ||
|
||
def extract_words(wordfile) | ||
return [] unless wordfile && File.readable?(wordfile) | ||
begin | ||
words = File.open(wordfile, "rb") {|f| f.read} | ||
rescue | ||
return | ||
end | ||
words = File.open(wordfile, "rb") {|f| f.read} | ||
save_array = words.split(/\r?\n/) | ||
return save_array | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$Id$
was for Subversion. It is no longer needed or expanded in git. This entire comment block can be removed.