Land #1915, smtp user enumeration enhancements
commit
0302437c2b
|
@ -33,7 +33,8 @@ class Metasploit3 < Msf::Auxiliary
|
|||
'Author' =>
|
||||
[
|
||||
'==[ Alligator Security Team ]==',
|
||||
'Heyder Andrade <heyder[at]alligatorteam.org>'
|
||||
'Heyder Andrade <heyder[at]alligatorteam.org>',
|
||||
'nebulus'
|
||||
],
|
||||
'License' => MSF_LICENSE
|
||||
)
|
||||
|
@ -45,184 +46,173 @@ class Metasploit3 < Msf::Auxiliary
|
|||
[
|
||||
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
|
||||
|
||||
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("#{rhost}:#{rport} Error smtp_send: '#{e.class}' '#{e}'")
|
||||
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")
|
||||
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
|
||||
vprint_status("VRFY command enabled")
|
||||
vrfy_ok=true
|
||||
rcpt = false
|
||||
end
|
||||
end
|
||||
|
||||
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.")
|
||||
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
|
||||
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()
|
||||
if @users_found && !@users_found.empty?
|
||||
print_good("#{target} Users found: #{@users_found.keys.sort.join(", ")}")
|
||||
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.keys.join(", ")}
|
||||
)
|
||||
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(", ")}")
|
||||
report_note(
|
||||
:host => rhost,
|
||||
:port => rport,
|
||||
:type => 'smtp.mails',
|
||||
:data => {:mails => @mails_found.keys.join(", ")}
|
||||
: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("#{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_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("#{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
|
||||
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("#{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 == '')
|
||||
|
||||
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)
|
||||
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
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue