
Une preuve de concept pour démontrer la CVE-2026-25604
Injection d'en-tête Host menant à un contournement de l'authentification SAML dans AWS Auth Manager d'Apache Airflow
Un attaquant peut injecter un en-tête Host malveillant dans le flux de connexion SAML, ce qui entraîne le pointage de l'URL du service de consommation d'assertion (ACS) vers un serveur contrôlé par l'attaquant. Cela permet à l'attaquant de capturer des réponses SAML valides et de les rejouer pour obtenir un accès non autorisé à l'instance Airflow de la victime — ou de réutiliser des jetons sur différentes instances Airflow avec des contrôles d'accès différents.
| Package | Affectées | Corrigée |
|---|---|---|
apache-airflow-providers-amazon | 8.0.0 – 9.21.x | 9.22.0 |
CVE-2026-25604 : Erreur de validation d'origine dans AWS Auth Manager (CWE-346)
Dans AWS Auth Manager, l'origine de l'authentification SAML a été utilisée telle que fournie par le client et non vérifiée par rapport à l'URL réelle de l'instance. Cela permettait d'obtenir un accès à différentes instances avec des contrôles d'accès potentiellement différents en réutilisant la réponse SAML d'autres instances.
— NVD
| Source | Lien |
|---|---|
| NVD | https://nvd.nist.gov/vuln/detail/CVE-2026-25604 |
| PR de correctif | https://github.com/apache/airflow/pull/61368 |
Le gestionnaire d'authentification AWS d'Apache Airflow utilise SAML 2.0 via AWS IAM Identity Center pour l'authentification. Lors de la construction de la demande d'authentification SAML, la méthode _prepare_flask_request() lit l'en-tête Host directement de la requête HTTP entrante pour construire l'URL de rappel ACS :
# Code vulnérable dans aws_auth_manager.py
def _prepare_flask_request(req):
host = req.headers.get("Host", req.host) # <-- Contrôlé par l'attaquant
if ":" in host:
hostname, port = host.rsplit(":", 1)
else:
hostname = host
port = "443" if req.scheme == "https" else "80"
return {
"http_host": hostname, # Utilisé pour construire l'URL ACS
"server_port": port,
...
}
Les valeurs résultantes http_host et server_port sont utilisées pour construire l'URL AssertionConsumerService SAML. Comme le fournisseur d'identité (IdP) fait confiance à cette URL, il redirige l'utilisateur authentifié — ainsi que la réponse SAML signée — vers l'empointement de l'en-tête Host.
┌──────────┐ ┌──────────────┐ ┌─────────────┐
│ Attaquant│ │ Victime │ │ AWS IAM │
│ │ │ Airflow │ │ Identity │
│ │ │ Instance │ │ Center │
└────┬─────┘ └──────┬───────┘ └──────┬──────┘
│ │ │
│ 1. GET /login │ │
│ Host: evil.com:8080 │ │
│─────────────────────>│ │
│ │ │
│ │ 2. SAML AuthnRequest │
│ │ ACS URL = │
│ │ evil.com:8080/ │
│ │ login_callback │
│ │───────────────────────>│
│ │ │
│ │ 3. L'utilisateur │
│ │ s'authentifie │
│ │ sur la page IdP │
│ │ │
│ 4. IdP redirige │ │
│ SAMLResponse vers │<───────────────────────│
│ evil.com:8080 │ │
│<─────────────────────│ │
│ │ │
│ 5. L'attaquant │ │
│ capture la │ │
│ SAMLResponse │ │
│ │ │
│ 6. Rejoue vers │ │
│ la victime │ │
│ POST /login_callback │
│ avec la │ │
│ SAMLResponse │ │
│ capturée │ │
│─────────────────────>│ │
│ │ │
│ 7. Authentifié ! │ │
│<─────────────────────│ │
└──────────────────────┴────────────────────────┘
Scénario A — Vol de jeton par hameçonnage : Un attaquant envoie un lien de connexion forgé (avec un en-tête Host usurpé via un proxy inverse) à un utilisateur légitime. Après que l'utilisateur s'est authentifié avec IAM Identity Center, la réponse SAML est redirigée vers le serveur de l'attaquant. L'attaquant la rejoue contre l'instance Airflow réelle.
Scénario B — Réutilisation de jeton entre instances : Dans des environnements Airflow multi-locataires ou multi-instances, une réponse SAML valide de l'instance A peut être rejouée sur l'instance B. Comme l'origine n'est jamais validée par rapport à l'URL réelle de l'instance, les différents contrôles d'accès de l'instance B sont contournés.
CVE-2026-25604-PoC/
├── README.md # Ce fichier
└── mock_airflow.py # Serveur Airflow factice vulnérable
pip install flask python3-saml
Configurez une application SAML 2.0 dans AWS IAM Identity Center en suivant la documentation du gestionnaire d'authentification AWS Airflow :
http://<airflow-host>:<port>/login_callbackaws-auth-manager-saml-clientpython mock_airflow.py <SAML_METADATA_URL> [PORT]
Par exemple :
python mock_airflow.py https://portal.sso.us-east-1.amazonaws.com/saml/metadata/XXXX 8080
Dans un terminal séparé, initiez une connexion SAML avec un en-tête Host manipulé :
curl -v -H "Host: attacker.com:9090" http://127.0.0.1:8080/login
Le serveur répond avec une 302 Redirect vers la page de connexion AWS IAM Identity Center. Inspectez la AuthnRequest SAML — l'URL AssertionConsumerService pointera vers attacker.com:9090/login_callback au lieu du serveur légitime.
Sur la console du serveur Airflow factice :
[LOGIN] Host header: attacker.com:9090
[DEBUG] http_host=attacker.com, server_port=9090
La AuthnRequest SAML ordonne désormais au IdP de délivrer la réponse SAML authentifiée à attacker.com:9090, donnant ainsi à l'attaquant un jeton valide à rejouer.
airflow/providers/amazon/aws/auth_manager/aws_auth_manager.py — la méthode _prepare_flask_request() :
host = request.headers.get("Host", request.host)
Cette ligne fait confiance à l'en-tête Host fourni par le client sans le valider par rapport à l'URL de base Airflow configurée (AIRFLOW__API__BASE_URL).
Le correctif (PR #61368, fusionné le 3 février 2026) remplace l'hôte dérivé de la requête par la valeur de la configuration Airflow :
- host = request.headers.get("Host", request.host)
+ host = conf.get("api", "base_url")
Cela garantit que l'URL ACS correspond toujours à l'URL réelle de l'instance configurée par l'administrateur, indépendamment de l'en-tête Host envoyé par le client.
Cette preuve de concept est fournie à des fins éducatives et de tests de sécurité autorisés uniquement. Utilisez-la de manière responsable et uniquement contre des systèmes que vous possédez ou pour lesquels vous avez une autorisation explicite de test.
| Commit de correctif | 1a86aec |