SMTP VRFY User Enumeration

Learn how to extract user info via SMTP VRFY commands in ethical hacking. Step-by-step tutorial with hands-on walkthrough, troubleshooting, and next steps.

Focus: extract user info via smtp vrfy commands

Sponsored

Imagine you are tasked with a penetration test and you need to identify valid usernames on a mail server. Manually guessing or brute-forcing credentials is noisy, slow, and likely to trigger alarms. The SMTP VRFY command offers a quieter, more efficient way to extract user info via SMTP VRFY commands — a classic enumeration technique that every ethical hacker should have in their toolkit. In this lesson, you'll learn how this simple protocol feature reveals valid users, how to use it safely and legally in your own labs, and how to protect your own servers from it.

The problem this lesson solves

User enumeration is a critical first step in any attack chain. Once you know which usernames are valid, you can launch targeted phishing attacks, guess passwords more effectively, or attempt credential stuffing. SMTP servers often expose this information through the VRFY command, which was designed to let administrators verify email addresses. Attackers (and ethical hackers) can abuse it to build a list of valid users.

The problem is that many organizations don't realize their mail servers are leaking this data. They’ve hardened their web servers and firewalls, but overlooked the email service. As an ethical hacker, you need to identify these leaks before a malicious actor does. This lesson gives you a practical, low-risk method to test for this vulnerability.

Core concept / mental model

Think of the SMTP VRFY command as a mailbox doorbell. When you ring it (send VRFY <username>), the server either says "yes, that user exists" (door opens) or "no, I don't know that person" (door stays closed). In a well-configured server, the responses between existing and non-existing users should be identical, but many servers reveal the truth in subtle ways.

Here’s a simple mental model:

  • Valid user → Server responds with a positive code (e.g., 252 or 250) that includes the user's full name or mailbox.
  • Invalid user → Server responds with a negative code (e.g., 550) or a generic "User not found" message.

This binary behavior lets you enumerate users one by one, much like a phone book — except the server is the phone book and VRFY is the search function. By automating this with a simple script, you can test dozens or hundreds of usernames quickly.

How it works step by step

To extract user info via SMTP VRFY commands, you need to understand the underlying protocol. Here’s the logical flow:

  1. Connect to the SMTP server — Usually on port 25 (or 587 for submission, but VRFY is often disabled there).
  2. Introduce yourself with HELO or EHLO — The server expects a greeting before processing commands.
  3. Send VRFY <username> — Replace <username> with the address you want to test (e.g., VRFY root or VRFY john.doe@example.com).
  4. Read the response — The server replies with a three-digit code and a message. Codes 250/252 suggest the user exists; 550/553 suggest they don’t.
  5. Repeat for other usernames — Loop through a wordlist of common usernames.
  6. Analyze results — Collect the valid usernames for further testing or reporting.

Why this matters in ethical hacking

In a penetration test, you’re always looking for the fastest, quietest way to gather intelligence. Compared to techniques like login brute-forcing, VRFY does not create multiple failed login attempts that could lock accounts or trigger SIEM alerts. It’s also simpler than complex SMTP-based exploits. However, it's not always available — many modern servers disable it by default.

Hands-on walkthrough

Let's get our hands dirty. We’ll start with a manual test using telnet, then move to a Python script for automation.

Manual test with telnet

Open your terminal and connect to a mail server you own or have explicit permission to test. If you don’t have one, set up a local lab with python -m smtpd (see troubleshooting section) or use a container with a lightweight SMTP server like smtp4dev.

telnet mail.example.com 25
Trying 192.168.1.100...
Connected to mail.example.com.
Escape character is '^]'.
220 mail.example.com ESMTP Postfix

Now issue the EHLO and VRFY commands:

EHLO test.com
250-mail.example.com
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-AUTH PLAIN LOGIN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
VRFY root
252 2.0.0 root
VRFY doesnotexist
550 5.1.1 <doesnotexist>: Recipient address rejected: User unknown in local recipient table

Observe the difference: root returns a 252 code (meaning the user exists), while doesnotexist returns 550. This is exactly the information you need.

Automating with Python

For larger lists, manual testing is impractical. Here's a simple Python script that reads usernames from a file and reports valid ones.

import socket

# Configuration
SERVER = "mail.example.com"
PORT = 25
TIMEOUT = 10

# List of usernames to test
usernames = ["root", "admin", "postmaster", "john.doe", "doesnotexist"]

def check_vrfy(username):
    try:
        with socket.create_connection((SERVER, PORT), timeout=TIMEOUT) as sock:
            sock.recv(1024)  # Read server greeting
            sock.sendall(b"EHLO test.local\r\n")
            sock.recv(1024)  # Read EHLO response
            sock.sendall(f"VRFY {username}\r\n".encode())
            response = sock.recv(1024).decode().strip()
            return response
    except Exception as e:
        return f"Error: {e}"

for user in usernames:
    result = check_vrfy(user)
    if result.startswith(('250', '252')):
        print(f"[+] VALID: {user} -> {result}")
    else:
        print(f"[-] Invalid: {user} -> {result}")

Expected output:

[+] VALID: root -> 252 2.0.0 root
[+] VALID: admin -> 252 2.0.0 admin
[-] Invalid: postmaster -> 550 5.1.1 <postmaster>: ...
[+] VALID: john.doe -> 252 2.0.0 john.doe
[-] Invalid: doesnotexist -> 550 5.1.1 <doesnotexist>: ...

Pro tip: Always wait for the server’s response after each command. Some servers enforce rate limits or disconnect if you send commands too quickly. A small delay (e.g., time.sleep(0.5)) can keep the connection alive.

Advanced: Using smtplib (but with caution)

The smtplib module can also send VRFY commands, but it’s more verbose. Here’s a snippet that works for internal testing:

import smtplib

server = smtplib.SMTP("mail.example.com", 25)
server.ehlo("test.local")
code, msg = server.verify("root")
print(code, msg)
server.quit()

This returns a tuple (code, message). Use it when you need to integrate with existing Python code, but raw sockets give you more control over timeouts and error handling.

Compare options / when to choose what

You have several ways to enumerate users via SMTP. Here’s a comparison to help you choose:

Method Speed Stealth Reliability Works when VRFY disabled?
VRFY Medium High Medium (depends on server config) No
EXPN Medium Medium Low (often disabled) No
RCPT TO (during MAIL FROM) Fast Medium High Yes
Email bounce-based Slow Low High Yes
  • VRFY — Use when the server advertises it (you saw 250-VRFY in the EHLO response). It's the most direct method.
  • EXPN — Expands mailing lists, but often disabled. Try it if you need to find list members.
  • RCPT TO — The most reliable method when VRFY is blocked. By sending a full email transaction and observing responses at the RCPT TO stage, you can determine user validity without sending the email.
  • Bounce-based — Send an email to a non-existent address and see if it bounces. Not practical for stealth.

When to choose VRFY over RCPT TO? In a lab, VRFY is simpler and less likely to be logged as a full email attempt. In a real engagement, RCPT TO is often your fallback since VRFY is commonly disabled.

Troubleshooting & edge cases

Even in a lab you'll hit issues. Here are the most common ones:

"VRFY is disabled" or returns 502

Many servers (e.g., Postfix with disable_vrfy_command = yes) return 502 5.5.2 Error: command not recognized. You'll see this if you try VRFY on a well-hardened server. To handle it:

  • Use the RCPT TO technique instead.
  • Or check if your lab server has it enabled — you may need to adjust its config.

Connection timeouts

Firewalls or ACLs may block port 25. Always use a server you control and ensure the port is open. Use nc -zv <server> 25 to test connectivity first.

Rate limiting and blocking

Some servers implement smtpd_client_connection_rate_limit to slow down or drop connections that send too many commands. This will cause your script to hang. Fix by adding a delay and using a single persistent connection.

False positives with 252

Some servers return 252 for any address to prevent enumeration. That means your results might be unreliable. Always cross-verify with another method like RCPT TO before reporting findings.

Using a local test server

If you don’t have a public mail server, create a simple one with Python:

python -m smtpd -c DebuggingServer -n localhost:25

But note: smtpd does not implement VRFY out of the box. For a realistic lab, use docker run -p 25:25 catatnight/postfix or a MailHog instance with enhanced capabilities.

What you learned & what's next

You now understand how to extract user info via SMTP VRFY commands — from the protocol basics to automation and troubleshooting. You can explain the core idea behind this enumeration technique and complete a practical exercise against a test server. You also know when VRFY is the right tool and when to fall back to RCPT TO.

In the next lesson, we’ll expand on this by examining SMTP banner grabbing and server fingerprinting, where you’ll learn to identify the exact mail server software and version — crucial for finding version-specific vulnerabilities. Keep practicing with your own lab, and always obtain proper authorization before testing any server that isn’t yours.

Practice recap

Set up a local SMTP server (like the Docker Postfix container mentioned in the lesson) and run the Python script against it. Try adding a delay between requests to avoid rate limiting, and test both a known-valid username and a random one to confirm the script works. Then, modify the script to read usernames from a file with 10+ common names to practice enumeration at scale.

Common mistakes

  • Sending VRFY without EHLO — the server will reject the command with 503. Always greet first.
  • Ignoring rate limits — blasting 1000 VRFY requests with no delay will get you disconnected or IP-banned.
  • Treating 252 as a hard yes — many servers return 252 for non-existent users to prevent enumeration, so verify with RCPT TO.
  • Testing without permission — running VRFY against a third-party server is illegal. Always use your own lab or sign-off.

Variations

  1. Use EXPN to expand mailing lists instead of verifying individual users — useful when VRFY is disabled.
  2. Automate with smtplib.verify() for quick one-off checks, but raw sockets give better control for broader enumeration.
  3. Use RCPT TO during a MAIL FROM transaction to enumerate users when VRFY is blocked — the server replies differently for valid vs invalid addresses.

Real-world use cases

  • During a penetration test, enumerate valid employee usernames to launch targeted phishing campaigns with legitimate-looking emails.
  • In a security audit, verify that your own mail server doesn't leak user lists and implement hardening measures if it does.
  • As a red teamer, combine VRFY enumeration with password guessing to find weak credentials for domain accounts, even if MFA is not enforced.

Key takeaways

  • The SMTP VRFY command is a quick, low-noise way to test if a username exists on a mail server.
  • A response code of 250 or 252 typically indicates a valid user, while 550 suggests the user does not exist.
  • Always greet the server with EHLO before sending VRFY, or you'll get an error.
  • VRFY is often disabled on modern servers; fall back to RCPT TO or EXPN when needed.
  • Ethical hacking requires explicit permission — only run VRFY against systems you own or have written authorization for.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.