SPF - Sender Policy Framework¶
Principe et fonctionnement¶
SPF (RFC 7208) permet au propriétaire d'un domaine de définir quels serveurs sont autorisés à envoyer des emails pour son domaine. Le serveur récepteur vérifie l'adresse IP de l'émetteur contre cette politique.
Flux de validation SPF¶
sequenceDiagram
autonumber
participant E as Serveur SMTP émetteur<br/>192.0.2.10
participant R as Serveur SMTP récepteur
participant DNS as DNS
E->>R: MAIL FROM:<user@example.com>
Note over R: Extraction du domaine<br/>(example.com)
R->>DNS: Requête DNS TXT<br/>example.com
DNS-->>R: v=spf1 ip4:192.0.2.10 ~all
Note over R: Vérification SPF<br/>L’IP 192.0.2.10 correspond-elle<br/>à la politique SPF ?
alt IP autorisée
R-->>E: SPF Pass
else IP non autorisée explicitement
R-->>E: SPF Fail
else IP non listée, mécanisme ~all
R-->>E: SPF SoftFail
end Quand est-ce que SPF est vérifié?¶
SPF est vérifié sur l'adresse MAIL FROM (aussi appelée "envelope from" ou "Return-Path"), pas sur l'adresse From visible dans l'en-tête.
MAIL FROM: <bounce@example.com> ← Vérifié par SPF
From: contact@example.com ← Visible par l'utilisateur (vérifié par DMARC)
Syntaxe des enregistrements SPF¶
Structure de base¶
Composants obligatoires :
v=spf1: Version du protocole (toujours "spf1")- Qualificateur final :
all,~all,-allou?all
Mécanismes¶
Les mécanismes définissent quels serveurs sont autorisés :
| Mécanisme | Description | Exemple |
|---|---|---|
ip4 | Adresse ou plage IPv4 | ip4:192.0.2.10 ou ip4:192.0.2.0/24 |
ip6 | Adresse ou plage IPv6 | ip6:2001:db8::1 ou ip6:2001:db8::/32 |
a | L'enregistrement A du domaine | a ou a:mail.example.com |
mx | Les serveurs MX du domaine | mx ou mx:example.com |
include | Inclure la politique SPF d'un autre domaine | include:_spf.google.com |
exists | Vérifie l'existence d'un enregistrement A | exists:%{i}.spamhaus.example.com |
ptr | Résolution inverse (déprécié, éviter) | ptr:example.com |
all | Attrape-tout (doit être en dernier) | all |
Qualificateurs¶
Chaque mécanisme peut être préfixé par un qualificateur :
| Qualificateur | Symbole | Signification | Résultat SPF |
|---|---|---|---|
| Pass | + (ou rien) | Autorisé | Pass |
| Fail | - | Interdit | Fail |
| SoftFail | ~ | Probablement interdit | SoftFail |
| Neutral | ? | Aucune information | Neutral |
Exemples :
ip4:192.0.2.10 → Pass si IP match
-ip4:192.0.2.50 → Fail si IP match
~all → SoftFail pour toutes les autres IP
-all → Fail pour toutes les autres IP
Modificateurs¶
Les modificateurs ajoutent des informations supplémentaires :
| Modificateur | Description | Exemple |
|---|---|---|
redirect | Redirige vers un autre domaine | redirect=_spf.example.com |
exp | Message d'explication en cas d'échec | exp=explain.example.com |
Exemples d'enregistrements SPF¶
SPF simple¶
Autoriser uniquement les serveurs MX du domaine :
SPF avec adresses IP¶
Autoriser des IP spécifiques :
SPF avec include¶
Utiliser un service tiers (Google Workspace) :
SPF complexe¶
Combiner plusieurs mécanismes :
SPF avec redirection¶
Déléguer la politique à un autre domaine :
Et sur _spf.example.com :
Résultats SPF¶
Lors de la vérification SPF, plusieurs résultats sont possibles :
| Résultat | Signification | Action typique |
|---|---|---|
| Pass | L'IP est explicitement autorisée | Accepter |
| Fail | L'IP est explicitement interdite (-all) | Rejeter ou marquer comme spam |
| SoftFail | L'IP est probablement non autorisée (~all) | Accepter mais marquer |
| Neutral | Pas d'information (?all) | Accepter |
| None | Pas d'enregistrement SPF | Accepter |
| TempError | Erreur temporaire DNS | Accepter temporairement |
| PermError | Erreur permanente (syntaxe invalide) | Politique locale |
Limites de SPF¶
Limite de lookups DNS¶
SPF impose une limite de 10 requêtes DNS pour éviter les abus.
Comptabilisés :
includeamxexistsptr(déprécié)
Non comptabilisés :
ip4ip6all
Exemple dépassant la limite :
v=spf1 include:_spf1.example.com include:_spf2.example.com include:_spf3.example.com \
include:_spf.google.com include:sendgrid.net include:_spf.salesforce.com \
include:servers.mcsv.net include:_spf.example.org include:mail.zendesk.com \
include:_spf.createsend.com include:spf.protection.outlook.com mx -all
Résultat : PermError (plus de 10 lookups).
Solutions :
- Aplatir le SPF : Remplacer les
includepar les IP directes - Utiliser des sous-domaines : Séparer les flux d'emails
- Nettoyer : Supprimer les includes inutilisés
Outil pour compter :
# Utiliser un outil en ligne comme dmarcian.com/spf-survey/
# Ou un script qui récursivement résout les includes
SPF et transfert d'emails (forwarding)¶
Problème : Lors d'un transfert, l'IP change mais le MAIL FROM reste le même.
User@example.com ──> MailServer1 ──> MailServer2 (forwarding) ──> Gmail
(SPF Pass) | | (SPF Fail)
| | car IP de MailServer2
IP autorisée IP non autorisée n'est pas dans SPF
d'example.com
Solutions :
- SRS (Sender Rewriting Scheme) : Réécrire le MAIL FROM lors du transfert
- ARC (Authenticated Received Chain) : Préserver les résultats d'authentification
SPF ne protège pas l'en-tête From¶
SPF vérifie uniquement le MAIL FROM (envelope), pas le From visible :
MAIL FROM: <bounce@attacker.com> ← SPF Pass (domaine de l'attaquant)
From: ceo@example.com ← Visible par l'utilisateur (pas vérifié par SPF)
Solution : DMARC vérifie l'alignement entre les deux.
Mise en place de SPF¶
Audit des sources d'envoi¶
Avant de créer l'enregistrement SPF, identifier toutes les sources légitimes :
- Serveurs mail internes : Postfix, Exchange, etc.
- Services cloud : Google Workspace, Office 365
- Services marketing : Mailchimp, SendGrid, Salesforce
- Applications : CRM, ERP, notifications système
- Serveurs web : Formulaires de contact
Méthode :
# Analyser les logs Postfix pour identifier les sources
grep "client=" /var/log/mail.log | awk '{print $7}' | sort | uniq
# Analyser les rapports DMARC si déjà en place
Construction de l'enregistrement¶
Étape 1 : Lister les adresses IP des serveurs internes
Étape 2 : Ajouter les includes pour services tiers
Étape 3 : Choisir le qualificateur final
- Pendant les tests :
~all(SoftFail) - En production :
-all(Fail)
Enregistrement complet :
Publication DNS¶
Syntaxe Bind :
Vérification après publication :
Phase de test¶
Étape 1 : Utiliser ~all (SoftFail) pendant 1-2 semaines
Étape 2 : Analyser les rapports DMARC pour identifier les sources manquantes
Étape 3 : Basculer vers -all (Fail)
Validation et vérification¶
Outils de vérification syntaxe¶
En ligne :
- https://mxtoolbox.com/spf.aspx
- https://www.kitterman.com/spf/validate.html
- https://dmarcian.com/spf-survey/
En ligne de commande :
# Vérifier l'enregistrement
dig example.com TXT +short
# Tester le SPF d'un domaine
python3 -m pyspf check example.com 192.0.2.10
Test d'envoi¶
Envoyer un email de test et vérifier les en-têtes reçus :
Received-SPF: pass (google.com: domain of sender@example.com designates 192.0.2.10 as permitted sender)
Authentication-Results: mx.google.com;
spf=pass (google.com: domain of sender@example.com designates 192.0.2.10 as permitted sender) smtp.mailfrom=sender@example.com
Outils pour compter les lookups DNS¶
# Script bash simple
#!/bin/bash
SPF=$(dig +short example.com TXT | grep "v=spf1")
echo "SPF Record: $SPF"
echo "Include count:"
echo "$SPF" | grep -o "include:[^ ]*" | wc -l
echo "A count:"
echo "$SPF" | grep -o " a \| a:" | wc -l
echo "MX count:"
echo "$SPF" | grep -o " mx \| mx:" | wc -l
Problèmes courants SPF¶
Plusieurs enregistrements SPF¶
Erreur :
Résultat : Comportement imprévisible (selon le résolveur).
Solution : Fusionner en un seul enregistrement.
Dépassement de la limite de 10 lookups¶
Symptôme : PermError
Solution : Aplatir le SPF ou utiliser des sous-domaines.
Oubli du all¶
Erreur :
Résultat : Neutral pour toutes les IP non listées.
Solution : Toujours terminer par ~all ou -all.
Utilisation de ptr¶
Erreur :
Problème : Mécanisme déprécié, lent, peu fiable.
Solution : Utiliser ip4 ou a à la place.
Include d'un domaine sans SPF¶
Erreur :
Résultat : PermError si le domaine n'existe pas ou n'a pas de SPF.
Solution : Vérifier que tous les domaines inclus ont bien un SPF valide.
SPF pour sous-domaines¶
Par défaut, les sous-domaines n'héritent pas du SPF du domaine parent.
Exemple :
Solutions :
- Créer un SPF pour chaque sous-domaine utilisé :
- Utiliser un wildcard (avec prudence) :
SPF et IPv6¶
Support complet d'IPv6 dans SPF :
Attention : Ne pas oublier les adresses IPv6 si le serveur est dual-stack.
Aplatissement de SPF (SPF Flattening)¶
Lorsque la limite de 10 lookups est atteinte, il faut "aplatir" le SPF.
Avant (11 lookups - PermError) :
v=spf1 include:_spf.google.com include:sendgrid.net include:_spf.salesforce.com \
include:mail.zendesk.com mx a -all
Après aplatissement :
v=spf1 ip4:64.233.160.0/19 ip4:66.102.0.0/20 ip4:167.89.0.0/17 \
ip4:168.245.0.0/16 ip4:192.0.2.10 ip4:192.0.2.20 -all
Inconvénient : Nécessite une mise à jour manuelle si les IP des services tiers changent.
Solutions automatisées :
- autospf.com
- dmarcian.com
- Scripts personnalisés qui résolvent périodiquement les includes
Macro-expansion SPF (avancé)¶
SPF supporte des macros pour des configurations dynamiques :
| Macro | Description |
|---|---|
%{s} | Adresse email expéditeur |
%{l} | Partie locale de l'email |
%{d} | Domaine |
%{i} | Adresse IP de l'expéditeur |
Exemple avec exists :
Vérifie si l'IP est listée dans une blacklist.
SPF et services cloud¶
Google Workspace¶
Microsoft Office 365¶
SendGrid¶
Mailchimp¶
OVH¶
Récapitulatif SPF¶
Forces :
- Simple à comprendre et mettre en place
- Empêche l'usurpation basique de domaine
- Largement supporté
Faiblesses :
- Limité à 10 lookups DNS
- Problème avec le transfert d'emails
- Ne protège que l'envelope (MAIL FROM)
- Ne signe pas le contenu
Bonnes pratiques :
- Commencer avec
~allpuis passer à-all - Compter les lookups DNS (rester sous 10)
- Documenter toutes les sources d'envoi
- Monitorer les rapports DMARC
- Mettre à jour régulièrement
- Utiliser un TTL raisonnable (3600)