SPF Lookup Limit Exploitation

SPF records have a 10 DNS lookup limit; attackers exploit overly complex SPF records that exceed this limit, causing validation failures and enabling spoofing.

MITRE ATT&CK: T1566.002

Timeline: The Cat and Mouse

2006 ← Problem emerges β†’ 2015 ← Solutions develop β†’ 2019 ← Cloud complexity β†’ Present

The Evolution

Phase Period What Happened
DESIGN 2006 SPF RFC includes 10 lookup limit; poorly understood
PROBLEM 2010-2012 Complex infrastructures start exceeding limits
RESPONSE 2015 SPF flattening services emerge
Β  2017 Validation monitoring tools developed
Β  2019 RFC 7208 clarifies void lookup limits
CLOUD ERA 2019+ SaaS explosion (Google, O365, Salesforce, etc.) makes limits hard
CURRENT Present ~15% of domains broken; ~30% fragile; ongoing problem

Key Events with Sources

Date Event Significance Source
2006 RFC 4408 published SPF includes 10 lookup limit RFC 4408
2014 RFC 7208 updates SPF Clarifies limit behavior and void lookups RFC 7208
2015 Flattening services emerge Tools to inline IPs and reduce lookup count AutoSPF
2018 Academic research USENIX paper on SPF lookup vulnerabilities USENIX
2020+ Cloud complexity Typical org uses 5-10 SaaS services, each needing SPF include Industry-wide

Overview

SPF records limit DNS lookups to 10 per validation. Organizations with complex email infrastructure often exceed this limit, causing SPF validation to permanently fail (PermError). Attackers can exploit this: if a domain’s SPF always fails, spoofing emails appear no worse than legitimate ones.

The Attack

The 10 Lookup Limit

RFC 7208 specifies that SPF validation must not exceed 10 DNS lookups:

Lookup-generating mechanisms:

  • include: β€” 1 lookup each
  • a: β€” 1 lookup each
  • mx: β€” 1 lookup each
  • ptr: β€” 1 lookup each (deprecated)
  • redirect= β€” 1 lookup
  • exists: β€” 1 lookup

Non-lookup mechanisms:

  • ip4: β€” No lookup (direct IP)
  • ip6: β€” No lookup (direct IP)
  • -all / ~all β€” No lookup

How Limits Are Exceeded

Complex Cloud Infrastructure:

v=spf1 include:_spf.google.com           # 1 lookup β†’ chains to more
       include:spf.protection.outlook.com # 1 lookup β†’ chains to more
       include:_spf.salesforce.com        # 1 lookup β†’ chains to more
       include:servers.mcsv.net           # 1 lookup (Mailchimp)
       include:mail.zendesk.com           # 1 lookup
       include:_spf.freshdesk.com         # 1 lookup
       -all

Each include: counts as 1 lookup, but the included records themselves contain more lookups:

_spf.google.com contains:
  include:_netblocks.google.com   # +1
  include:_netblocks2.google.com  # +1
  include:_netblocks3.google.com  # +1

Total for just Google: 4 lookups

Real-World Example (Exceeds Limit):

Direct includes: 6 lookups
_spf.google.com chain: 4 lookups
spf.protection.outlook.com chain: 2 lookups
─────────────────────────────────
Total: 12 lookups β†’ PERMERROR

What Happens When Limit Exceeded

Receiving Server:
1. Query SPF record for sender domain
2. Follow includes, count lookups
3. Lookup count exceeds 10
4. Return SPF PermError
5. Email treated as SPF=none or SPF=fail (depending on config)

Impact:

  • Legitimate email may be rejected or quarantined
  • If receivers treat PermError as β€œnone,” spoofing goes undetected
  • Organization can’t tell the difference between spoofed and legitimate

Void Lookup Attacks

RFC 7208 also limits β€œvoid lookups” (lookups returning no records) to 2:

Attack:

Attacker's SPF record:
v=spf1 include:nonexistent1.attacker.com  # void lookup 1
       include:nonexistent2.attacker.com  # void lookup 2
       include:nonexistent3.attacker.com  # void lookup 3 β†’ exceeds limit
       ip4:attacker_ip
       -all

This causes validation errors on the receiving server, potentially used for DoS or to confuse logging.

Exploiting Broken SPF

Reconnaissance:

# Check if target exceeds lookup limit
dig +short TXT example.com | grep spf

# Use online SPF validator
# If result = PermError, domain is exploitable

Spoofing When SPF is Broken:

If target.com has PermError:
- Legitimate mail: SPF=PermError
- Spoofed mail: SPF=PermError or SPF=Fail
- No distinguishing signal!

Attacker sends:
MAIL FROM: <ceo@target.com>
From: CEO <ceo@target.com>
β†’ SPF fails, but so does everything else from that domain

Raw Email Headers (SPF PermError)

Email from a domain with broken SPF:

Return-Path: <spoofed@broken-spf-domain.com>
Received: from mail.attacker.com (mail.attacker.com [198.51.100.77])
        by mx.victim.com (Postfix) with ESMTPS id SPFLIMIT01
        for <target@victim.com>; Fri, 31 Jan 2025 11:45:22 -0500 (EST)
Authentication-Results: mx.victim.com;
        spf=permerror (too many DNS lookups) smtp.mailfrom=spoofed@broken-spf-domain.com;
        dkim=none;
        dmarc=fail (p=NONE) header.from=broken-spf-domain.com
From: "Finance Team" <finance@broken-spf-domain.com>
To: target@victim.com
Subject: Wire Transfer Instructions
Date: Fri, 31 Jan 2025 11:45:18 -0500
Message-ID: <spf-exploit-001@attacker.com>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8

Please process the attached wire transfer...

Key observations:

  • spf=permerror β€” Validation failed due to lookup limit
  • Legitimate mail from this domain would ALSO show permerror
  • No way to distinguish spoofed from legitimate
  • dmarc=fail but p=NONE means no enforcement
  • Attacker exploits the domain’s broken SPF

Defenses

SPF Flattening

Inline IP addresses to reduce lookups:

Before Flattening:

v=spf1 include:_spf.google.com -all

After Flattening:

v=spf1 ip4:35.190.247.0/24 ip4:64.233.160.0/19 ip4:66.102.0.0/20
       ip4:66.249.80.0/20 ip4:72.14.192.0/18 ip4:74.125.0.0/16
       ip4:108.177.8.0/21 ip4:173.194.0.0/16 ip4:209.85.128.0/17 -all

Challenges:

  • IPs change; requires regular updates
  • Use automated flattening services
  • Must monitor for changes in included records

SPF Subdomains

Split SPF across subdomains:

# Main domain
v=spf1 include:_spf1.example.com include:_spf2.example.com -all

# Subdomain 1 (3 lookups)
_spf1.example.com: v=spf1 include:_spf.google.com -all

# Subdomain 2 (3 lookups)
_spf2.example.com: v=spf1 include:spf.protection.outlook.com -all

This distributes lookups but adds complexity.

Lookup Monitoring

Regular auditing:

# Check current lookup count
spf_check example.com --count-lookups

# Alert if approaching limit
if lookups > 8:
    alert("SPF approaching limit")

DMARC with DKIM

If SPF is fragile, ensure DKIM is robust:

  • DMARC passes if either SPF or DKIM aligns
  • Strong DKIM provides fallback
  • Monitor DMARC reports for SPF failures

Current State

Status: Active

Many organizations still struggle with SPF limits:

Problem Prevalence Impact
Exceeded limits ~15% of domains SPF never validates
Approaching limits ~30% of domains Fragile, one change breaks it
Poor monitoring Common Don’t know they’re broken
Void lookup abuse Rare but increasing DoS potential

Detection Guidance

SPF Health Monitoring

# Regular SPF audits
for domain in critical_domains:
    result = validate_spf(domain)
    if result.lookups > 8:
        alert("SPF approaching limit")
    if result.status == "permerror":
        critical_alert("SPF broken")

Email Authentication Analysis

Alert on:
email.authentication.spf.result = "permerror"
AND email.from.domain IN (monitored_domains)

DMARC Reporting

Monitor aggregate reports for:

  • High SPF failure rates
  • PermError in SPF results
  • Unexpected sources sending as your domain

What Killed It (or Weakened It)

Defense Introduced Impact
SPF Record Flattening 2015 Tools that inline IP addresses to reduce lookups
SPF Validation Monitoring 2017 Alerting when SPF records approach or exceed limits
Void Lookup Limits 2019 RFC 7208 limits void lookups to prevent DoS