Zum Hauptinhalt springen

RFC 7208 - Sender Policy Framework (SPF)

Grundlegende Informationen

  • RFC-Nummer: 7208
  • Titel: Sender Policy Framework (SPF) for Authorizing Use of Domains in Email
  • Deutscher Titel: Absender-Richtlinien-Framework
  • Veröffentlichungsdatum: April 2014
  • Status: PROPOSED STANDARD (Vorgeschlagener Standard)
  • Autor: S. Kitterman

Zusammenfassung (Abstract)

SPF ermöglicht es Domaininhabern, über DNS-Einträge anzugeben, welche Mailserver zum Versenden von E-Mails für diese Domain autorisiert sind. Empfänger können SPF-Einträge abfragen, um zu überprüfen, ob E-Mails von autorisierten Servern stammen, was hilft, E-Mail-Spoofing zu erkennen und zu blockieren.

Contents

Appendices (Anhänge)

SPF-Überblick

Was ist SPF?

Definition:

SPF = Sender Policy Framework (Absender-Richtlinien-Framework)
Funktion: Verifizierung autorisierter E-Mail-Versandserver
Methode: DNS TXT-Einträge
Zweck:
✓ Verhinderung von E-Mail-Spoofing
✓ Reduzierung von Spam
✓ Verbesserung der E-Mail-Zustellbarkeit

E-Mail-Sicherheits-Trio:
1. SPF (dieser RFC) - Verifiziert Versandserver
2. DKIM (RFC 6376) - Verifiziert E-Mail-Inhalt
3. DMARC (RFC 7489) - Einheitliche Richtlinien und Berichte

Funktionsprinzip:

Absender (example.com):
1. Veröffentlicht SPF-Eintrag im DNS
example.com. IN TXT "v=spf1 ip4:203.0.113.1 -all"
→ Nur 203.0.113.1 ist autorisiert

2. Mailserver sendet E-Mails normal
MAIL FROM: `<[email protected]>`

Empfänger:
1. Extrahiert Absender-Domain
MAIL FROM: [email protected] → Domain: example.com

2. Fragt SPF-Eintrag ab
DNS-Abfrage: example.com TXT-Eintrag

3. Prüft Versandserver-IP
Versandserver-IP: 203.0.113.1
SPF-Eintrag erlaubt: ip4:203.0.113.1
→ Übereinstimmung!

4. SPF-Ergebnis:
Pass ✓ → Autorisierter Server
Fail ✗ → Nicht autorisierter Server

SPF vs DKIM vs DMARC

Funktionsvergleich:

SPF (RFC 7208):
- Verifiziert: Versandserver-IP
- Position: SMTP MAIL FROM
- DNS-Eintrag: TXT
- Einschränkung: Weitergeleitete E-Mails schlagen fehl

DKIM (RFC 6376):
- Verifiziert: Digitale E-Mail-Signatur
- Position: DKIM-Signature Header
- DNS-Eintrag: TXT (_domainkey)
- Einschränkung: Erfordert korrekte Schlüsselkonfiguration

DMARC (RFC 7489):
- Verifiziert: SPF + DKIM Alignment
- Position: From Header
- DNS-Eintrag: TXT (_dmarc)
- Funktion: Richtlinie + Berichte

Kombinierte Verwendung:
SPF + DKIM → DMARC bestanden → Optimaler Schutz

SPF-Eintragsformat

Grundlegende Syntax

v=spf1 <mechanisms> <qualifiers> <modifiers>

Beispiel:
v=spf1 ip4:192.0.2.0/24 include:_spf.example.com -all

Bestandteile:
- v=spf1: Versionskennung (erforderlich, immer spf1)
- mechanisms: Matching-Mechanismen
- qualifiers: Ergebnis-Qualifizierer
- modifiers: Modifikatoren

Mechanismen (Mechanisms)

1. all:

Definition: Matcht alle IPs
Verwendung: Normalerweise als Standard-Richtlinie am Ende

Beispiele:
v=spf1 -all Alle IPs nicht erlaubt (am strengsten)
v=spf1 ~all Alle IPs Soft-Fail (empfohlen)
v=spf1 +all Alle IPs erlaubt (nicht empfohlen!)

2. ip4/ip6:

Definition: Explizite IP-Adressen oder Bereiche

Beispiele:
v=spf1 ip4:203.0.113.1 -all
→ Nur 203.0.113.1 erlaubt

v=spf1 ip4:192.0.2.0/24 -all
→ Erlaubt 192.0.2.0-192.0.2.255

v=spf1 ip6:2001:db8::1 -all
→ Erlaubt IPv6-Adresse

v=spf1 ip4:203.0.113.0/24 ip4:198.51.100.0/24 -all
→ Erlaubt mehrere Bereiche

3. a:

Definition: A/AAAA-Einträge der aktuellen Domain

Beispiele:
v=spf1 a -all
→ Erlaubt IPs aus A-Eintrag von example.com

v=spf1 a:mail.example.com -all
→ Erlaubt IPs aus A-Eintrag von mail.example.com

v=spf1 a/24 -all
→ Erlaubt /24-Netzwerk der A-Eintrag-IP von example.com

4. mx:

Definition: MX-Einträge der aktuellen Domain

Beispiele:
v=spf1 mx -all
→ Erlaubt MX-Server-IPs von example.com

v=spf1 mx:example.com -all
→ Erlaubt MX-Server-IPs von example.com

v=spf1 mx/24 -all
→ Erlaubt /24-Netzwerk der MX-Server

5. include:

Definition: Bezieht SPF-Eintrag einer anderen Domain ein

Beispiele:
v=spf1 include:_spf.google.com -all
→ Erlaubt Google Mailserver (Gmail for Business)

v=spf1 include:spf.protection.outlook.com -all
→ Erlaubt Microsoft 365 Mailserver

Mehrere includes:
v=spf1 include:_spf.google.com include:spf.protection.outlook.com -all

Hinweis: Maximal 10 DNS-Abfragen Limit!

6. exists:

Definition: Matcht wenn die angegebene Domain einen A-Eintrag hat

Beispiel:
v=spf1 exists:%\{i}.spamhaus.example.com -all
→ Fortgeschrittene Verwendung, normalerweise für Anti-Spam-Blacklist-Prüfung

Makro-Erweiterung:
%\{i} = Versandserver-IP (umgekehrt)

7. ptr (nicht empfohlen):

Definition: Reverse-DNS-Abfrage

Beispiel:
v=spf1 ptr:example.com -all

Probleme:
❌ Schlechte Performance (erfordert Reverse-DNS-Abfrage)
❌ Geringe Zuverlässigkeit
❌ RFC empfiehlt ausdrücklich nicht zu verwenden

Alternative: Verwenden Sie ip4/ip6 oder include

Qualifizierer (Qualifiers)

Symbol | Name | Bedeutung | Empfohlene Verwendung
-------|------|-----------|----------------------
+ | Pass | Bestanden (Standard) | Autorisierte Server
- | Fail | Fehlgeschlagen | E-Mail ablehnen
~ | SoftFail | Soft-Fail | Akzeptieren aber markieren
? | Neutral | Neutral | Keine klare Richtlinie

Beispiele:
v=spf1 +ip4:203.0.113.1 -all
↑explizit pass ↑explizit fail

v=spf1 ip4:203.0.113.1 ~all
↑Standard + ↑soft-fail

v=spf1 ?all
↑neutral (entspricht keinem SPF)

Empfehlungen zur Verwendung von Qualifizierern:

+ (Pass): 
✓ Autorisierte Mailserver
Beispiel: +ip4:203.0.113.1

- (Fail):
✓ Finales -all (streng)
✓ Explizit bestimmte IPs verbieten
Beispiel: -all

~ (SoftFail):
✓ Finales ~all (locker, empfohlen anfangs)
✓ Übergangszeit verwenden
Beispiel: ~all

? (Neutral):
✗ Selten verwendet
✗ Entspricht keiner Richtlinie

Modifikatoren (Modifiers)

1. redirect:

Definition: Umleitung auf SPF-Eintrag einer anderen Domain

Beispiel:
example.com: v=spf1 redirect=_spf.example.com
_spf.example.com: v=spf1 ip4:203.0.113.1 -all

Zweck:
✓ Zentrale SPF-Verwaltung
✓ Mehrere Domains teilen sich Richtlinie

Hinweis:
- Nach redirect können keine anderen Mechanismen folgen
- Kann nicht zusammen mit all verwendet werden

2. exp:

Definition: Erklärung (Erklärungstext bei SPF-Fehler)

Beispiel:
v=spf1 -all exp=explain.example.com

explain.example.com TXT-Eintrag:
"This domain does not send email"

Zweck:
✓ Benutzerfreundliche Fehlermeldungen bereitstellen
- Wird selten tatsächlich verwendet

SPF-Eintragsbeispiele

Basiskonfigurationen

1. Nur ein Mailserver:

v=spf1 ip4:203.0.113.1 -all

Erklärung:
- Nur 203.0.113.1 darf E-Mails senden
- Andere IPs werden abgelehnt

2. Verwendung von MX-Einträgen:

v=spf1 mx -all

Erklärung:
- Erlaubt MX-Server der Domain E-Mails zu senden
- Passt sich automatisch an MX-Änderungen an

3. Mehrere IP-Bereiche:

v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.0/24 -all

Erklärung:
- Erlaubt zwei Class-C-Netzwerke
- Geeignet für Multi-Datacenter-Bereitstellung

Drittanbieter-Dienste

4. Google Workspace (Gmail for Business):

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

Erklärung:
- Verwendet Google zum E-Mail-Versand
- Bezieht Googles SPF-Eintrag ein

5. Microsoft 365:

v=spf1 include:spf.protection.outlook.com -all

6. SendGrid:

v=spf1 include:sendgrid.net -all

7. Mailchimp:

v=spf1 include:servers.mcsv.net -all

Gemischte Konfigurationen

8. Eigener Server + Drittanbieter:

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

Erklärung:
- Eigener Server: 203.0.113.1
- Google Workspace: include

9. Mehrere Drittanbieter-Dienste:

v=spf1 include:_spf.google.com include:spf.protection.outlook.com include:sendgrid.net -all

Hinweis: Jedes include zählt als eine DNS-Abfrage

10. Domain sendet keine E-Mails:

v=spf1 -all

Erklärung:
- Domain sendet keine E-Mails
- Verhindert Spoofing
- Geeignet für nur-Empfangs-Domains

Subdomains

11. Separate Subdomain-Konfiguration:

example.com: v=spf1 ip4:203.0.113.1 -all
mail.example.com: v=spf1 include:_spf.google.com -all

Erklärung:
- Hauptdomain verwendet eigenen Server
- mail-Subdomain verwendet Google

12. Subdomain-Vererbung (wenn kein SPF-Eintrag vorhanden):

Wenn mail.example.com keinen SPF-Eintrag hat:
→ Verwendet SPF-Eintrag von example.com

Wenn keine Vererbung gewünscht:
mail.example.com: v=spf1 -all

SPF-Prüfablauf

Empfänger-Verifizierungsschritte

// SPF-Verifizierungs-Pseudocode
async function checkSPF(clientIP, sender, helo) {
// 1. Domain extrahieren
const domain = sender.split('@')[1]; // [email protected] → example.com

// 2. SPF-Eintrag abfragen
const spfRecord = await queryDNS(domain, 'TXT', 'v=spf1');

if (!spfRecord) {
return 'none'; // Kein SPF-Eintrag
}

// 3. SPF-Eintrag parsen
const mechanisms = parseSPF(spfRecord);

// 4. Mechanismen nacheinander prüfen
for (const mechanism of mechanisms) {
const result = await evaluateMechanism(mechanism, clientIP, domain);

if (result !== null) {
return result; // Match gefunden, Ergebnis zurückgeben
}
}

return 'neutral'; // Kein Match
}

// Einzelnen Mechanismus bewerten
async function evaluateMechanism(mechanism, clientIP, domain) {
const { type, value, qualifier } = mechanism;

switch (type) {
case 'ip4':
if (isInIPRange(clientIP, value)) {
return mapQualifier(qualifier); // +pass, -fail, ~softfail
}
break;

case 'a':
const aRecords = await queryDNS(value || domain, 'A');
if (aRecords.includes(clientIP)) {
return mapQualifier(qualifier);
}
break;

case 'mx':
const mxRecords = await queryDNS(value || domain, 'MX');
for (const mx of mxRecords) {
const mxIPs = await queryDNS(mx, 'A');
if (mxIPs.includes(clientIP)) {
return mapQualifier(qualifier);
}
}
break;

case 'include':
const includeResult = await checkSPF(clientIP, `user@${value}`, null);
if (includeResult === 'pass') {
return mapQualifier(qualifier);
}
break;

case 'all':
return mapQualifier(qualifier);
}

return null; // Kein Match
}

SPF-Ergebnisse

Rückgabewert       | Bedeutung            | Empfohlene Behandlung
-------------------|----------------------|----------------------
none | Kein SPF-Eintrag | Akzeptieren (niedrigeres Vertrauen)
neutral | Explizit keine | Akzeptieren
| Richtlinie |
pass | Bestanden | Akzeptieren
fail | Fehlgeschlagen | Ablehnen
softfail | Soft-Fail | Akzeptieren aber markieren
temperror | Temporärer Fehler | Später erneut versuchen
permerror | Permanenter Fehler | Ablehnen

SMTP-Antwortbeispiele:
pass: 250 OK (SPF pass)
fail: 550 SPF check failed
softfail: 250 OK (X-SPF: softfail Header hinzufügen)

DNS-Abfragelimits

10-Abfragen-Limit

Problem:

SPF-Prüfung führt maximal 10 DNS-Abfragen durch
Überschreitung des Limits → permerror (Permanenter Fehler)

Zählt zu den 10 Abfragen:
✓ include
✓ a
✓ mx
✓ exists
✓ redirect

Zählt nicht:
✗ ip4/ip6 (direktes Matching)
✗ all (direktes Matching)

Beispiel - Limit überschritten:

v=spf1 
include:_spf1.example.com ← 1
include:_spf2.example.com ← 2
include:_spf3.example.com ← 3
include:_spf4.example.com ← 4
include:_spf5.example.com ← 5
include:_spf6.example.com ← 6
include:_spf7.example.com ← 7
include:_spf8.example.com ← 8
include:_spf9.example.com ← 9
include:_spf10.example.com ← 10
include:_spf11.example.com ← Überschritten! permerror
-all

Wenn ein include weitere includes enthält, zählen diese auch zur Gesamtzahl!

Lösungen:

1. Verwenden Sie ip4/ip6 statt a/mx
❌ v=spf1 a mx -all (2 Abfragen)
✓ v=spf1 ip4:203.0.113.1 ip4:198.51.100.1 -all (0 Abfragen)

2. includes zusammenführen
❌ include:service1.com include:service2.com
✓ Eigenen SPF-Eintrag pflegen, der alle IPs enthält

3. SPF Flattening
Regelmäßig IPs aus includes abfragen, in ip4/ip6 umwandeln

SPF Flattening Tool

// SPF Flattening Beispiel
async function flattenSPF(domain) {
const spf = await querySPF(domain);
const ips = [];

// SPF parsen
const mechanisms = parseSPF(spf);

for (const mech of mechanisms) {
if (mech.type === 'ip4' || mech.type === 'ip6') {
ips.push(mech.value);
} else if (mech.type === 'include') {
// Rekursiv includes abfragen
const includeIPs = await resolveInclude(mech.value);
ips.push(...includeIPs);
} else if (mech.type === 'a') {
const aRecords = await queryDNS(mech.value, 'A');
ips.push(...aRecords.map(ip => `ip4:${ip}`));
} else if (mech.type === 'mx') {
const mxRecords = await queryDNS(mech.value, 'MX');
for (const mx of mxRecords) {
const mxIPs = await queryDNS(mx, 'A');
ips.push(...mxIPs.map(ip => `ip4:${ip}`));
}
}
}

// Flattened SPF generieren
return `v=spf1 ${ips.join(' ')} -all`;
}

// Verwendung
const flatSPF = await flattenSPF('example.com');
console.log('Flattened SPF:', flatSPF);
// v=spf1 ip4:74.125.0.0/16 ip4:209.85.128.0/17 ip4:216.58.192.0/19 -all

Praktische Werkzeuge

SPF-Eintrags-Generator

class SPFBuilder {
constructor(domain) {
this.domain = domain;
this.mechanisms = [];
this.modifier = null;
}

addIP(ip) {
if (ip.includes(':')) {
this.mechanisms.push(`ip6:${ip}`);
} else {
this.mechanisms.push(`ip4:${ip}`);
}
return this;
}

addIPRange(cidr) {
if (cidr.includes(':')) {
this.mechanisms.push(`ip6:${cidr}`);
} else {
this.mechanisms.push(`ip4:${cidr}`);
}
return this;
}

useA() {
this.mechanisms.push('a');
return this;
}

useMX() {
this.mechanisms.push('mx');
return this;
}

include(domain) {
this.mechanisms.push(`include:${domain}`);
return this;
}

setDefault(qualifier) {
const qualifiers = { pass: '+all', fail: '-all', softfail: '~all', neutral: '?all' };
this.mechanisms.push(qualifiers[qualifier] || '-all');
return this;
}

redirect(domain) {
this.modifier = `redirect=${domain}`;
return this;
}

build() {
let spf = 'v=spf1';

if (this.mechanisms.length > 0) {
spf += ' ' + this.mechanisms.join(' ');
}

if (this.modifier) {
spf += ' ' + this.modifier;
}

return spf;
}

countLookups() {
let count = 0;
for (const mech of this.mechanisms) {
if (mech.startsWith('include:') || mech.startsWith('a') ||
mech.startsWith('mx') || mech.startsWith('exists:')) {
count++;
}
}
if (this.modifier && this.modifier.startsWith('redirect=')) {
count++;
}
return count;
}
}

// Verwendungsbeispiel
const spf = new SPFBuilder('example.com')
.addIP('203.0.113.1')
.addIPRange('192.0.2.0/24')
.include('_spf.google.com')
.include('spf.protection.outlook.com')
.setDefault('fail')
.build();

console.log('SPF Record:', spf);
console.log('DNS Lookups:', spf.countLookups());

// Ausgabe:
// SPF Record: v=spf1 ip4:203.0.113.1 ip4:192.0.2.0/24 include:_spf.google.com include:spf.protection.outlook.com -all
// DNS Lookups: 2

SPF-Verifizierungs-Tool

const dns = require('dns').promises;

class SPFChecker {
async check(domain, ip) {
try {
// SPF-Eintrag abfragen
const records = await dns.resolveTxt(domain);
const spfRecord = records.find(r =>
r.join('').startsWith('v=spf1')
);

if (!spfRecord) {
return { result: 'none', message: 'No SPF record found' };
}

const spf = spfRecord.join('');
console.log('SPF Record:', spf);

// Parsen und verifizieren
const result = await this.evaluate(spf, ip, domain);

return result;

} catch (err) {
return { result: 'temperror', message: err.message };
}
}

async evaluate(spf, ip, domain, depth = 0) {
if (depth > 10) {
return { result: 'permerror', message: 'Too many DNS lookups' };
}

const parts = spf.split(/\s+/);

for (const part of parts) {
if (part === 'v=spf1') continue;

// Qualifizierer extrahieren
let qualifier = '+';
let mechanism = part;

if (['+', '-', '~', '?'].includes(part[0])) {
qualifier = part[0];
mechanism = part.slice(1);
}

// Mechanismus prüfen
if (mechanism.startsWith('ip4:')) {
const range = mechanism.slice(4);
if (this.isIPInRange(ip, range)) {
return this.mapResult(qualifier);
}
} else if (mechanism.startsWith('include:')) {
const includeDomain = mechanism.slice(8);
const includeRecords = await dns.resolveTxt(includeDomain);
const includeSPF = includeRecords.find(r =>
r.join('').startsWith('v=spf1')
);

if (includeSPF) {
const result = await this.evaluate(
includeSPF.join(''),
ip,
includeDomain,
depth + 1
);

if (result.result === 'pass') {
return this.mapResult(qualifier);
}
}
} else if (mechanism === 'all' || mechanism === '-all' ||
mechanism === '~all' || mechanism === '?all') {
return this.mapResult(qualifier);
}
// Weitere Mechanismen können hier hinzugefügt werden...
}

return { result: 'neutral', message: 'No match found' };
}

isIPInRange(ip, range) {
// Vereinfachte Version, Produktionsumgebung benötigt vollständige Implementierung
if (!range.includes('/')) {
return ip === range;
}
// CIDR-Matching-Implementierung ausgelassen...
return false;
}

mapResult(qualifier) {
const map = {
'+': { result: 'pass', message: 'SPF pass' },
'-': { result: 'fail', message: 'SPF fail' },
'~': { result: 'softfail', message: 'SPF softfail' },
'?': { result: 'neutral', message: 'SPF neutral' }
};
return map[qualifier] || map['+'];
}
}

// Verwendung
const checker = new SPFChecker();
const result = await checker.check('example.com', '203.0.113.1');
console.log('SPF Check Result:', result);

Best Practices für die Bereitstellung

1. Schrittweise Bereitstellung

Phase 1: Überwachungsmodus
v=spf1 ?all
oder
v=spf1 ~all

Zweck: Daten sammeln, beobachten welche Server E-Mails senden
Dauer: 2-4 Wochen

Phase 2: Soft-Fail
v=spf1 ip4:x.x.x.x include:provider.com ~all

Zweck: Nicht autorisierte E-Mails markieren aber nicht ablehnen
Dauer: 4-8 Wochen

Phase 3: Strenger Modus
v=spf1 ip4:x.x.x.x include:provider.com -all

Zweck: Nicht autorisierte E-Mails ablehnen

2. Häufige Fehler

❌ Fehler 1: -all vergessen
v=spf1 ip4:203.0.113.1
→ Entspricht v=spf1 ip4:203.0.113.1 ?all
→ Jede IP ist neutral

✓ Korrekt:
v=spf1 ip4:203.0.113.1 -all

❌ Fehler 2: Mehrere SPF-Einträge
example.com TXT "v=spf1 ip4:203.0.113.1 -all"
example.com TXT "v=spf1 include:provider.com -all"
→ permerror

✓ Korrekt: Zu einem zusammenführen
v=spf1 ip4:203.0.113.1 include:provider.com -all

❌ Fehler 3: Mehr als 10 Abfragen
v=spf1 include:a include:b include:c ... (zu viele)

✓ Korrekt: Verwenden Sie ip4 direkt oder flattening

❌ Fehler 4: Verwendung von ptr
v=spf1 ptr:example.com -all
→ Schlechte Performance, nicht empfohlen

✓ Korrekt: Verwenden Sie ip4 oder include

3. Testen und Verifizieren

# Kommandozeilen-Test-Tools

# 1. SPF-Eintrag abfragen
dig example.com TXT | grep "v=spf1"
oder
nslookup -type=TXT example.com

# 2. Online-Tools verwenden
# - https://mxtoolbox.com/spf.aspx
# - https://www.kitterman.com/spf/validate.html

# 3. Test-E-Mail senden
# An eigene E-Mail-Adresse senden, E-Mail-Header prüfen:
# Received-SPF: pass ...

Integration mit DKIM/DMARC

Vollständige E-Mail-Sicherheitskonfiguration

1. SPF-Eintrag:
example.com. IN TXT "v=spf1 ip4:203.0.113.1 include:_spf.google.com -all"

2. DKIM-Eintrag:
default._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIGfMA0..."

3. DMARC-Eintrag:
_dmarc.example.com. IN TXT "v=DMARC1; p=reject; rua=mailto:[email protected]"

Ergebnis:
- SPF verifiziert Versandserver ✓
- DKIM verifiziert E-Mail-Inhalt ✓
- DMARC vereinheitlicht Richtlinie ✓
→ Dreifacher Schutz!

Referenzen

SPF-bezogene RFCs:

  • [RFC 7208] SPF ← Dieses Dokument
  • [RFC 7489] DMARC
  • [RFC 6376] DKIM

Verwandte Ressourcen:


Zusammenfassung: SPF ist die erste Verteidigungslinie für E-Mail-Sicherheit. Durch DNS-Einträge werden Versandserver autorisiert und E-Mail-Spoofing effektiv verhindert. Kombiniert mit DKIM und DMARC entsteht ein vollständiges E-Mail-Sicherheitssystem. Denken Sie daran: Beginnen Sie mit Soft-Fail und verschärfen Sie schrittweise auf den strengen Modus, und beachten Sie die 10-DNS-Abfrage-Grenze!