Les en-têtes comme sacs
Les frameworks de journalisation HTTP traitent les en-têtes de requête comme un sac de paires clé-valeur. L’API de journalisation expose le sac complet. Les opérateurs activent la journalisation des en-têtes pour le débogage : lorsqu’une requête échoue, les en-têtes racontent l’histoire. Aucune liste de blocage intégrée. Aucun filtrage des identifiants dans la documentation. Tous les en-têtes sur disque.
Les en-têtes d’identifiants dans une requête typique :
- Authorization: Bearer eyJhbGciOiJIUzI1NiJ9... (jeton JWT ou OAuth)
- Cookie: session=abc123; auth=xyz789
- X-API-Key: sk-live-abc123...
- X-Auth-Token: ghp_abc123... (jeton d’accès personnel GitHub)
Ces valeurs authentifient la requête. Une fois écrites dans un fichier journal, elles permettent d’authentifier n’importe quelle requête.
Le pipeline des identifiants
Un identifiant écrit dans un fichier journal ne reste pas à un seul endroit. Il circule :
1. Le serveur web écrit dans /var/log/nginx/access.log
2. L’agent de rotation des journaux (logrotate) copie vers /var/log/nginx/access.log.1
3. L’expéditeur de journaux (Fluentd, Filebeat, Logstash) lit et envoie vers l’agrégateur
4. L’agrégateur de logs (Elasticsearch, Splunk, Datadog) indexe et stocke
5. Conservé pendant 30 à 90 jours selon la politique par défaut
Le credential existe simultanément dans les cinq emplacements. La révocation du jeton de session ne supprime pas le credential de l’agrégateur de logs. Il reste consultable, exportable et accessible à toute personne disposant d’un accès aux logs pendant toute la période de rétention.
La fenêtre d’exposition
Fenêtre d’exposition d’un credential en mémoire : max(durée de session, durée de vie du processus). Session : quelques heures à quelques jours. Processus : quelques heures à quelques semaines.
Fenêtre d’exposition d’un credential dans un log : max(durée de session, durée de rétention des logs). Session : quelques heures à quelques jours. Rétention : 30 à 90 jours.
Un credential volé en mémoire nécessite que l’attaquant soit présent pendant la fenêtre de session. Un credential volé dans un log ne nécessite qu’un accès à l’agrégateur de logs, disponible rétroactivement, pendant toute la période de rétention.
MOAD-0003 vs MOAD-0004
MOAD-0003 (Fuite de contexte) : un identifiant en mémoire fuit vers le mauvais gestionnaire de requête. Accessible uniquement pendant la fenêtre du processus, via le pool de threads. Éphémère.
MOAD-0004 (Secret journalisé) : un identifiant sur disque persiste après la rotation, l’expédition et l’agrégation des journaux. Accessible rétroactivement, à toute personne ayant accès aux journaux, pendant 30 à 90 jours. Persistant.
La différence structurelle : éphémère versus persistant. La correction s’opère à une couche différente.
Éphémère vs Persistant
La distinction éphémère/persistant détermine la surface de risque, la couche de correction et les exigences de réponse aux incidents.
Liste de refus des identifiants au niveau de la couche de sérialisation
La correction : une liste de refus des identifiants au niveau de la couche de sérialisation. Avant qu’une valeur d’en-tête atteigne la sortie du journal, vérifiez le nom de l’en-tête par rapport à une liste de refus. Remplacez la valeur par [REDACTED].
CREDENTIAL_HEADERS = {
'authorization',
'cookie',
'x-api-key',
'x-auth-token',
'x-csrf-token',
'proxy-authorization',
}
def sanitize_headers(headers: dict) -> dict:
return {
k: '[REDACTED]' if k.lower() in CREDENTIAL_HEADERS else v
for k, v in headers.items()
}
La denylist appartient à la couche de sérialisation, pas à la couche de requête de logs. Redaction des requêtes de logs : s’applique après que le credential a atteint le disque ; la valeur brute existe toujours, simplement masquée à l’affichage. Redaction à la couche de sérialisation : le credential n’atteint jamais le disque. La valeur brute n’entre jamais dans le fichier de log, le shipper de logs, ni l’agrégateur de logs.
Test de la Denylist
Trois modèles de test :
- Positif : une requête avec Authorization: Bearer token123 produit une entrée de log avec Authorization: [REDACTED]
- Négatif : une requête avec Content-Type: application/json génère une entrée de journal avec la valeur intacte
- Insensible à la casse : AUTHORIZATION: Bearer token123 produit également [REDACTED] (les noms d'en-têtes HTTP sont insensibles à la casse)
La liste de refus nécessite une maintenance : les nouveaux modèles d'en-têtes d'identifiants (par ex. les en-têtes personnalisés X-Service-Auth) doivent être ajoutés explicitement. Le correctif est structurel mais non auto-maintenu.
Appliquer la liste de refus
Une équipe configure le format de son journal d'accès Nginx pour inclure tous les en-têtes de requête afin de déboguer un incident en production. La configuration :
log_format debug_format '$remote_addr - $request - $http_authorization - $http_cookie';
access_log /var/log/nginx/debug.log debug_format;
Ils résolvent l’incident et prévoient de supprimer la configuration de débogage, mais la modification n’atteint pas la production avant le cycle de déploiement suivant (7 jours plus tard).