Email Enumeration Framework
A Practical Approach to Detect Account Existence Issues
Summary
Email enumeration is still a common issue in many web applications. Even if developers try to hide error messages, systems often leak information through small differences in how they respond.
This article presents a simple email enumeration framework that works on login, registration, and forgot-password endpoints. Instead of relying on specific error messages, the tool checks how the application behaves when different email addresses are used.
The framework is designed as a single Python script to keep it easy to review, run, and explain during security testing or internal evaluations.
Based on testing, the results show that:
- Many applications still leak account existence indirectly.
- Same error messages do not always mean same backend behavior.
- Timing, response size, and keywords are enough to detect valid accounts.
This shows why email enumeration should be handled not only on the UI level, but also on the backend logic.
Introduction
Email enumeration happens when an attacker can tell whether an email address exists in a system. This usually happens during login, registration, password reset or changing emails via account profile.
Most applications try to fix this by using generic messages like:
“If the account exists, an email will be sent.”
However, even if the message is the same, the response behavior is often different.
These small differences can still be observed and abused.
Objective
The goal of this work is to build a simple tool that can:
- Test different authentication-related endpoints
- Accept any email domain (e.g. @gmail.com, @company.com)
- Detect enumeration using response behavior
Threat Model
The tool simulates a basic attacker who:
- Can send requests to public authentication endpoints
- Has a list of possible usernames or names
- Can observe server responses
The attacker’s goal is to find out which email addresses are registered.
Enumeration Flow
Username list ↓ Create email (name + domain) ↓ Send request to target endpoint ↓ Observe response (status, size, time, keywords) ↓ Decide result (EXISTS / INVALID / UNKNOWN)
|
Implementation
#!/usr/bin/env python3 import requests import time import statistics
HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "Accept": "*/*", "Content-Type": "application/json" }
SUCCESS_HINTS = [ "otp sent", "email sent", "already registered", "exists" ]
FAIL_HINTS = [ "not found", "does not exist", "invalid email" ]
def build_email(user, domain): return f"{user.lower().replace(' ', '.')}{domain}"
def send_request(method, url, email): payload = { "email": email, "emailAddress": email, "username": email }
start = time.perf_counter()
if method == "GET": r = requests.get(url, params={"emailAddress": email}, headers=HEADERS) else: r = requests.post(url, json=payload, headers=HEADERS)
return { "status": r.status_code, "length": len(r.text), "time": round(time.perf_counter() - start, 3), "body": r.text.lower() }
def analyze(email, resp, baseline_time): for k in SUCCESS_HINTS: if k in resp["body"]: return f"[+] {email} → EXISTS", "exists"
for k in FAIL_HINTS: if k in resp["body"]: return f"[-] {email} → INVALID", "invalid"
delta = round(resp["time"] - baseline_time, 3) return f"[?] {email} → UNKNOWN (Δ{delta}s)", "unknown"
def main(): url = input("Target endpoint URL: ").strip() method = input("HTTP method (GET/POST): ").strip().upper() wordlist = input("Wordlist file: ").strip() domain = input("Email domain (e.g. @gmail.com): ").strip() delay = float(input("Delay between requests (sec): ").strip())
if not domain.startswith("@"): domain = "@" + domain
users = [u.strip() for u in open(wordlist) if u.strip()]
baseline = send_request(method, url, build_email(users[0], domain)) baseline_time = baseline["time"]
timings = [] results = {"exists": 0, "invalid": 0, "unknown": 0}
for user in users: email = build_email(user, domain) resp = send_request(method, url, email) timings.append(resp["time"])
line, verdict = analyze(email, resp, baseline_time) results[verdict] += 1
print(line) time.sleep(delay)
print("\nSummary") print(results) print("Average response time:", statistics.mean(timings))
if __name__ == "__main__": main()
|
Security Impact
Email enumeration can be used for:
- Targeted phishing
- Credential stuffing
- Account takeover preparation
- Privacy issues
Knowing which emails exist makes attacks easier and faster.
About the Validity and Real Impact of Email Enumeration
Not all teams accept email enumeration as a valid security vulnerability.
In many cases, it is treated as a low-risk or informational issue, especially when it does not directly lead to account compromise.
On its own, email enumeration usually does not give full access to an account.
It only allows an attacker to identify which email addresses are registered in the system.
However, the real risk appears when email enumeration is combined with other existing weaknesses in the application.
Why Email Enumeration Is Often Downplayed
Email enumeration is sometimes rejected because:
-> No direct account access is gained
-> No data is exposed
-> The issue looks theoretical without a clear exploit path
Because of this, it is often marked as:
- “Informational”
- “Low Severity”
- “Not Applicable”
When Email Enumeration Becomes High Impact
Email enumeration becomes dangerous when the application also has issues such as:
-> No rate limit on OTP requests
-> No limit on OTP verification attempts
-> Predictable or short OTP values
-> Long OTP validity period
-> Missing account lockout controls
In these cases, enumeration acts as an enabler rather than a standalone issue.
Example Attack Chain
Email Enumeration ↓ List of valid email addresses ↓ Unlimited OTP request endpoint ↓ OTP brute force or abuse ↓ Account takeover
|
Without email enumeration, the attacker would be guessing blindly.
Why This Changes the Severity
When combined with OTP weaknesses, email enumeration:
-> Reduces attacker effort
-> Increases success rate
-> Enables automated attacks
-> Leads directly to account takeover
At this point, the issue is no longer theoretical. It becomes a critical part of a real exploit chain.
Conclusion
Email enumeration on its own is often seen as a low-risk issue, and in some cases, this assessment is reasonable. By itself, enumeration usually does not give direct access to user accounts or sensitive data. However, this work shows that email enumeration should not be evaluated in isolation. When combined with other weaknesses in the authentication flow’ such as missing rate limits on OTP requests, unlimited OTP attempts, or weak verification controls—it can become a key part of a real attack chain. In these scenarios, enumeration allows an attacker to focus only on valid accounts. This significantly lowers the effort required to perform OTP abuse or account takeover attacks and increases the overall success rate.