ENUM^:Enumeration

SMTP User Enumeration

smtp-user-enum, swaks and nmap's smtp-enum-users — VRFY/EXPN/RCPT username enumeration, open-relay checks and manual SMTP probing.

beginnerupdated 2026-09-25smtp-user-enum · swaks · Nmap · Hydra

SMTP User Enumeration

Three ways to answer “does this mailbox exist?” against an SMTP server (25/465/587): the purpose-built smtp-user-enum.pl (bulk VRFY/EXPN/RCPT), nmap’s bundled smtp-enum-users script (same three methods, one-shot with the rest of a scan), and swaks for hand-crafted probes — relay testing, RCPT TO acceptance checks, and STARTTLS/auth testing that the dedicated enumerators don’t do. Start with nmap -sC -p25 $IP (the smtp-commands script) to see which of VRFY/EXPN/AUTH/STARTTLS the server even advertises before picking a method. For the full protocol-level workflow (open relay, spray, CVE-2020-7247) see Service Enumeration → SMTP and Attacking Common Services → Email.

Raw SMTP session — telnet / nc (hand verification)

Before trusting a tool, drive the protocol by hand — it shows the exact response codes the enumerators key off and lets you confirm a couple of hits without a wordlist.

$ nc -nv $IP 25
220 mail.corp.local ESMTP Sendmail 8.15.2; ...
HELO x
250 mail.corp.local
VRFY root
252 2.1.5 Cannot VRFY user; try RCPT to attempt delivery   # ambiguous (calibrate below)
VRFY nonexistent
252 2.1.5 Cannot VRFY user; try RCPT to attempt delivery   # identical 252 for a bad user => VRFY is neutered, use RCPT
EXPN postmaster
502 5.5.1 EXPN command disabled        # EXPN is off on most modern MTAs
MAIL FROM:<probe@evil.com>
250 2.1.0 Ok
RCPT TO:<jsmith@corp.local>
250 2.1.5 Ok                           # 250/251 => mailbox exists
RCPT TO:<nouser@corp.local>
550 5.1.1 <nouser@corp.local>: Recipient address rejected: User unknown   # 550 => no such user
QUIT
221 2.0.0 Bye
CodeVRFY / RCPT meaning
250Mailbox exists / recipient accepted (VRFY echoes the full address)
251User not local, will forward — valid, remote mailbox
252Cannot verify but will attempt delivery — ambiguous; the classic Sendmail/Exchange “cannot VRFY” reply (default Postfix instead answers definitively 250/550, or 502 when VRFY is disabled)
550No such user / mailbox unavailable — invalid
551 / 553User not local (try forward-path) / mailbox name not allowed
450 / 451 / 452Temp failure (greylist / throttle) — not a validity signal, retry

[!tip] Calibrate VRFY in one shot Feed one known-bad and one known-good username down the same connection and compare the two codes — no need to script a whole run to find out if VRFY is real.

printf 'HELO x\r\nVRFY nonexistent_zzz9\r\nVRFY root\r\nQUIT\r\n' | nc -w3 $IP 25
# identical codes for both  => VRFY neutered (switch to -M RCPT)
# different codes (e.g. 550 vs 250) => VRFY is live, enumerate with it

[!tip] Same session inside TLS On 587/465 where cleartext is refused, get the exact hand-driven session over TLS with openssl s_client -starttls smtp -connect $IP:587 (STARTTLS) or openssl s_client -connect $IP:465 (implicit TLS), then type HELO/VRFY/RCPT as above.

smtp-user-enum.pl

Perl script (pentestmonkey), preinstalled on Kali; on macOS clone it — git clone https://github.com/pentestmonkey/smtp-user-enum. It drives raw sockets directly (use Socket; IO::Socket::INET; IO::Select; IO::Handle; Getopt::Std), so it needs no CPAN modules — not even Net::SMTP. Runs one method against a wordlist with configurable concurrency.

# VRFY (default method) — most reliable when not disabled
perl smtp-user-enum.pl -M VRFY -U users.txt -t $IP

# EXPN — expands a mailing list/alias to member addresses
perl smtp-user-enum.pl -M EXPN -U users.txt -t $IP

# RCPT — works even when VRFY/EXPN are disabled; needs a MAIL FROM domain
perl smtp-user-enum.pl -M RCPT -U users.txt -D $DOMAIN -t $IP

# single username check, custom port, more workers
perl smtp-user-enum.pl -M VRFY -u root -t $IP -p 25 -m 10
FlagDescriptionDefault
-M modeMethod: VRFY, EXPN, or RCPTVRFY
-u userSingle username to check—
-U fileFile of usernames—
-t hostSingle target host—
-T fileFile of target hosts—
-D domDomain appended to usernames for RCPT mode (guess full addresses)none
-f addrMAIL FROM address, RCPT mode onlyuser@example.com
-p portTCP port25
-m nMax concurrent processes5
-dDebug outputoff
-vVerboseoff

[!warning] Watch out

  • Many MTAs (Sendmail and Exchange classically) answer VRFY with 252 Cannot VRFY user regardless of validity — calibrate first by testing one known-bad and one known-good username; if both give identical responses, VRFY is neutered and you need RCPT instead. Default Postfix is the opposite — it answers VRFY definitively (250/550), which is why VRFY enum is classically demonstrated against it, and returns 502 5.5.1 VRFY command is disabled when an admin turns it off (disable_vrfy_command=yes).
  • RCPT mode is slow (~5–7 requests/sec) and the noisiest of the three since it opens a full MAIL FROM/RCPT TO sequence per guess (no DATA sent, so no mail is actually delivered) — scope the wordlist, don’t point it at rockyou.
  • -m concurrency above ~10–15 gets rate-limited or blacklisted by most modern MTAs.

nmap smtp-enum-users

Same three methods, run as part of an nmap scan — convenient when you’re already scanning the port and want a same-command result, or need nmap’s userdb/passdb-style scripted output.

# default: tries RCPT, then VRFY, then EXPN, against nmap's built-in username list
nmap -p25 --script smtp-enum-users $IP

# pick methods and order explicitly
nmap -p25 --script smtp-enum-users --script-args smtp-enum-users.methods={EXPN,RCPT,VRFY} $IP

# custom username list (standard nmap unpwdb 'userdb' argument)
nmap -p25 --script smtp-enum-users --script-args userdb=/opt/users.txt $IP

# banner/capability check first — shows if VRFY/EXPN/STARTTLS/AUTH are even offered
nmap -p25 -sC -sV $IP

Stops early if the server enforces authentication, and prints whatever usernames it found before any error. Related scripts on the same target: smtp-commands (EHLO/HELP capability banner), smtp-open-relay (tries hardcoded MAIL FROM/RCPT TO combinations to detect relaying — flags authenticated servers as not-vulnerable rather than erroring), smtp-vuln-cve2010-4344 / smtp-vuln-cve2011-1720 (Exim/Postfix heap overflows).

nmap -p25 --script smtp-commands,smtp-open-relay,smtp-vuln* $IP

smtp-ntlm-info — internal hostname/domain disclosure

When a server advertises AUTH NTLM (typically Exchange message-submission on 587, sometimes 25/465), the smtp-ntlm-info NSE script sends a Type-1 NTLM token and decodes the Type-2 challenge — leaking the NetBIOS domain, NetBIOS computer name, DNS domain, FQDN and OS build with no credentials.

# hit all three SMTP ports; 587 (submission) is the usual winner on Exchange
nmap -p25,465,587 --script smtp-ntlm-info $IP

[!tip] Why 587 Perimeter/internal Exchange exposes authenticated submission on 587 with NTLM enabled far more often than on 25, so 587 is where this leak usually lands. Same primitive as rdp-ntlm-info / ms-sql-ntlm-info — an unauthenticated pull of the AD domain and host FQDN. Feed the recovered domain straight into smtp-user-enum -M RCPT -D <domain> or swaks --to user@<domain>.

hydra & metasploit — enumeration equivalents

The same VRFY/EXPN/RCPT primitives wrapped in tooling you may already have loaded — one framework for spray + enum, or Metasploit’s DB to store found users.

# Hydra's smtp-enum module — mode is the URL suffix (VRFY default / EXPN / RCPT)
# VRFY/EXPN ignore the password field; a dummy -p keeps hydra's parser happy on builds that demand one
hydra -L users.txt -p x smtp-enum://$IP/VRFY
hydra -L users.txt -p x smtp-enum://$IP/EXPN
# RCPT mode: -p carries the domain appended to each username
hydra -L users.txt -p $DOMAIN smtp-enum://$IP/RCPT
hydra -U smtp-enum          # print the module's own options
# Metasploit — auxiliary/scanner/smtp/smtp_enum (RCPT/VRFY/EXPN, no creds needed)
msfconsole -q
use auxiliary/scanner/smtp/smtp_enum
set RHOSTS $IP
set RPORT 25
set USER_FILE /usr/share/seclists/Usernames/Names/names.txt
run
# grab the banner/version alongside it:
use auxiliary/scanner/smtp/smtp_version
ToolEnum invocationNotes
hydrasmtp-enum://$IP/{VRFY,EXPN,RCPT}RCPT needs -p $DOMAIN; VRFY/EXPN take a throwaway -p; ships with hydra
metasploitauxiliary/scanner/smtp/smtp_enumUSER_FILE default data/wordlists/unix_users.txt; UNIXONLY true skips the MS banner check
smtp-user-enum-M {VRFY,EXPN,RCPT} (above)fastest / most controllable for large lists

swaks — manual probing, relay & auth testing

swaks doesn’t bulk-enumerate on its own, but it’s the right tool for anything smtp-user-enum/nmap don’t cover: single-shot RCPT TO acceptance checks (useful when VRFY/EXPN are both off), open-relay testing, and exercising STARTTLS/AUTH.

# basic connectivity / banner + EHLO capabilities
swaks --to test@$DOMAIN --server $IP

# RCPT-based user check — stop right after RCPT TO, read the response code
# (accepted, 250/251 = valid mailbox; 550/551/553 = no such user)
swaks --to victim@$DOMAIN --from test@evil.com --server $IP --quit-after RCPT --hide-all; echo "exit: $?"

# same idea, scripted over a userlist (swaks exits non-zero on rejection)
for u in $(cat users.txt); do
  swaks --to "$u@$DOMAIN" --server $IP --quit-after RCPT --hide-all \
    && echo "VALID: $u"
done

# open relay test — external sender AND external recipient, neither in the local domain
swaks --to outsider@external-test.com --from spoofed@some-other-domain.com --server $IP
# accepted (250) with no auth from an address outside $DOMAIN, to an address outside $DOMAIN = open relay

# force/require STARTTLS, dump the peer cert
swaks --to test@$DOMAIN --server $IP --tls --tls-get-peer-cert

# authenticated send (validate creds found via spraying/loot)
swaks --to test@$DOMAIN --from user@$DOMAIN --server $IP \
  --auth LOGIN --auth-user "user@$DOMAIN" --auth-password 'Password1!'
FlagDescriptionDefault
-t, --to ADDREnvelope recipient (only truly required option)—
-f, --from ADDREnvelope sender; <> for null senderbest-guess local user@host
-s, --server HOST[:PORT]Target serverlocalhost
--port PORTOverride portprotocol default (25)
--protocol PROTOSMTP, ESMTP, SSMTP, SMTPS, LMTP, … — sets port/TLS/HELO type as a side effectESMTP
--quit-after STOPEnd the transaction cleanly right after a stage: CONNECT, HELO/EHLO, STARTTLS, AUTH, MAIL, RCPTfull transaction
-tlsRequire STARTTLS, abort if unavailableoff
-tlsc, --tls-on-connectImplicit TLS on connect (port 465 style)off
-a, --auth [TYPE]Require auth: LOGIN, PLAIN, CRAM-MD5, DIGEST-MD5, NTLMoff
-au, --auth-user / -ap, --auth-passwordCredentials for --authprompt
-n, --suppress-dataDon’t print the DATA section (keep output readable)off
-ha, --hide-allSuppress all output — check $? insteadoff
--timeout TIMETransaction timeout (5s/3m/1h, 0 = none)30s

[!tip] --quit-after RCPT is the whole trick This is straight from swaks’ own quick-start docs: stopping right after RCPT TO: gets you the server’s accept/reject verdict without ever sending a message body, so it reads almost identically to what smtp-user-enum -M RCPT does internally — useful when you want to hand-verify a couple of hits, or when you need swaks’ TLS/auth handling that smtp-user-enum doesn’t have.

[!warning] Watch out

  • Open-relay and unauthenticated RCPT probing generate real SMTP session log entries (and, on a relay hit, an actual outbound message) — scope carefully on live/production mail infrastructure, this isn’t a passive check.
  • --protocol SMTP forces HELO instead of EHLO, which some scanners use specifically to dodge servers that only rate-limit/log on ESMTP extensions.

25 / 587 / 465 with the right TLS mode

# 587 submission — explicit STARTTLS upgrade, then RCPT probe
swaks --to victim@$DOMAIN --server $IP:587 --tls --quit-after RCPT --hide-all; echo "exit: $?"

# 465 implicit TLS (SMTPS) — TLS handshake on connect, no cleartext EHLO
swaks --to victim@$DOMAIN --server $IP --port 465 --tls-on-connect --quit-after RCPT --hide-all; echo "exit: $?"

# plain 25, no TLS (MTA relay path)
swaks --to victim@$DOMAIN --server $IP:25 --quit-after RCPT --hide-all; echo "exit: $?"

[!warning] STARTTLS vs implicit TLS --tls performs an in-band STARTTLS upgrade after a cleartext EHLO (ports 25/587). --tls-on-connect (-tlsc) does implicit TLS — the socket is encrypted from byte zero (port 465). Using the wrong one hangs or errors: 465 will not answer a plaintext EHLO, and most 587 listeners refuse RCPT before STARTTLS.

Ports & TLS — 25 vs 587 vs 465

PortRoleTransportEnum relevance
25MTA ↔ MTA relay (RFC 5321)Cleartext, opportunistic STARTTLSClassic VRFY/EXPN/RCPT + open-relay tests; client submission often ISP-blocked
587Message submission (RFC 6409)Cleartext EHLO → STARTTLS, AUTH requiredBest smtp-ntlm-info target on Exchange; RCPT enum after STARTTLS
465Submission over implicit TLS (SMTPS, RFC 8314)TLS on connect, then AUTHSame probes as 587 but wrap in --tls-on-connect / openssl s_client -connect

[!tip] STARTTLS = opportunistic upgrade on 25/587; implicit TLS = 465 from the first byte. If VRFY/RCPT come back refused on 587, you almost certainly need to STARTTLS first (swaks --tls, or openssl s_client -starttls smtp -connect $IP:587).

[!warning] Microsoft 365 & Google Workspace are out of scope here Their public MX (*.mail.protection.outlook.com, aspmx.l.google.com) accepts or generically rejects every RCPT, so SMTP VRFY/EXPN/RCPT enumeration does not work against them. User enumeration on those platforms is a web/auth technique — Autodiscover / GetCredentialType / OWA-timing for M365, login-flow responses for Workspace — handled by tools like o365spray and MailSniper, not this sheet. See Attacking Common Services → Email for the M365 workflow.

Troubleshooting

ProblemCause & fix
VRFY/EXPN always return 252The server accepts the verb but refuses to confirm — 252 means “cannot verify, will try to deliver”. Switch to RCPT TO enumeration, which most MTAs still answer distinctly
Every user returns the same codeUser enumeration is disabled or the MTA replies identically for valid and invalid names. Fall back to timing analysis, or accept the vector is closed
smtp-user-enum reports all users validYou are hitting a catch-all / accept-all domain. Confirm with an obviously bogus name; if that “exists” too, the signal is worthless
Connection resets after a few probesRate limiting or greylisting kicked in. Slow down (-w delay), reconnect per batch, and expect the first attempt after a pause to be deferred
RCPT TO needs MAIL FROM firstThe SMTP state machine requires an envelope sender before a recipient. Send MAIL FROM:<test@test.com> once, then iterate RCPT TO on the same connection
STARTTLS required before any commandThe MTA rejects cleartext auth/enum on 25/587. Use swaks --tls (587) or connect to 465 implicit TLS; openssl s_client -starttls smtp gives you a raw TLS session to type into
Works on port 25 but not 587587 (submission) usually mandates AUTH and rejects anonymous VRFY/RCPT. Enumeration lives on 25; 587 is for authenticated relay testing

See Also

Sources

On this page

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.