Email Enumeration Framework

Email Enumeration Framework

A Practical Approach to Detecting Account Existence Issues


Summary

Email enumeration remains a common issue in many web applications. Even if developers try to hide error messages, systems often leak information through small differences in their responses.

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.
  • The same error messages do not always indicate the same backend behavior.
  • Timing, response size, and keywords are enough to detect valid accounts.

This demonstrates why email enumeration should be addressed both at the UI level and in the backend logic.

Introduction

Email enumeration occurs when an attacker can determine whether an email address exists in a system. This typically happens during login, registration, or password reset, or when changing emails via the account profile.

Most applications attempt to mitigate 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 often differs.
These small differences can still be observed and exploited.

Objective

The goal of this work is to create a simple tool that can:

  • Test various authentication-related endpoints
  • Accept any email domain (e.g., @gmail.com, @company.com)
  • Detect enumeration through response behavior

Threat Model

The tool simulates an attacker who can:

  • Send requests to public authentication endpoints.
  • Use a list of possible usernames or names.
  • Observe server responses.

The attacker’s goal is to identify which email addresses are registered.

Enumeration Flow

1
2
3
4
5
6
7
8
9
10
11
Username list

Create email (name + domain)

Send request to target endpoint

Observe response
(status, size, time, keywords)

Decide result
(EXISTS / INVALID / UNKNOWN)

Implementation

sample-script.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

#!/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 lead to:

  • Targeted phishing
  • Credential stuffing
  • Preparation for account takeovers
  • Privacy concerns

Knowing which emails exist makes these 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 provide full access to an account. It only allows an attacker to identify which email addresses are registered in the system.

However, the real risk emerges when email enumeration is combined with other existing weaknesses in the application.

Why Email Enumeration Is Often Downplayed

Email enumeration is sometimes dismissed because:

  • It does not provide direct account access.
  • No data is exposed.
  • The issue appears 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

1
2
3
4
5
6
7
8
9
10
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 is often seen as a low-risk issue, but this assessment may not always be accurate. On its own, it usually does not provide direct access to user accounts or sensitive data. However, 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 critical part of an attack chain. In these scenarios, enumeration allows attackers to focus only on valid accounts, significantly lowering the effort required for OTP abuse or account takeover attacks and increasing their overall success rate.