Apache Airflow: Remote Code Execution im Scheduler und im API-Server über unbeschränkte Klassenrekonstruktion in der Dag-Deserialisierung

Ein Dag-Autor bringt über das Feld executor_config einen Ausnahme-Knoten in die serialisierte Dag-Darstellung, dessen Klassenname beim Deserialisieren ungeprüft importiert und mit Argumenten aus demselben Blob aufgerufen wird, sodass sein Code im Scheduler und im API-Server ausgeführt wird.

Advisory-ID: TP-2026-045
Produkt: Apache Airflow (Plattform zur Orchestrierung von Datenpipelines; betroffen sind der Scheduler und der API-Server von Airflow 3)
Schwachstellentyp: Deserialisierung nicht vertrauenswürdiger Daten zur Codeausführung (CWE-502)
CVE: CVE-2026-58076
CVSS 3.1: 8.8 (Hoch) · CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Hersteller-Advisory: Apache Airflow Security Announcement vom 12. August 2026
Betroffene Versionen: Apache Airflow (apache-airflow) >= 3.0.0, < 3.3.1
Behoben in: Apache Airflow 3.3.1
Gemeldet: 6. Juni 2026

Zusammenfassung

Apache Airflow ist eine Plattform zur Orchestrierung von Datenpipelines. Airflow 3 legt jeden Dag als JSON in der Tabelle serialized_dag ab: der Scheduler baut daraus in seiner normalen Schleife wieder Objekte, der API-Server tut dasselbe bei jedem authentifizierten Lesezugriff auf den Dag. Die Deserialisierung rekonstruierte Ausnahme-Knoten, indem sie import_string() auf einen Klassennamen aus dem gespeicherten Blob anwandte und das Ergebnis mit Argumenten aus demselben Blob instanziierte, ohne jede Einschränkung, welche Klasse importiert werden darf. Das Feld executor_config eines Operators läuft durch denselben rekursiven Dispatcher, sodass ein Dag-Autor dort einen Wert platzieren kann, der einen beliebigen Aufrufbaren importiert und ausführt, etwa subprocess.check_output oder os.system. Scheduler und API-Server sind genau die Komponenten, in denen laut Airflows eigenem Sicherheitsmodell niemals Code eines Dag-Autors laufen darf, und beide halten die Zugangsdaten der Metadaten-Datenbank sowie das Signaturgeheimnis der JWT. Eine vom Standard abweichende Konfiguration ist nicht erforderlich. turingpoint hat die Codeausführung im API-Server live verifiziert und die Schwachstelle an die Apache Software Foundation gemeldet, die sie in Airflow 3.3.1 behoben hat.

Ursache

BaseSerialization.deserialize verteilt die Knoten der serialisierten Darstellung anhand des Feldes __type (airflow-core/src/airflow/serialization/serialized_objects.py:626-708, Zeilennummern nach Airflow 3.2.2). Für die Knotentypen airflow_exc_ser und base_exc_ser nimmt der Zweig den Klassennamen aus dem Blob, löst ihn per import_string() auf und ruft das Ergebnis mit den ebenfalls aus dem Blob stammenden positionalen und benannten Argumenten auf (:671-675), ohne Allowlist. Dass die Gegenmaßnahme im selben Dispatcher bereits existierte, zeigen die Nachbarzweige: decode_timetable lässt import_string nur für Namen mit dem Präfix airflow.timetables. sowie eine Plugin-Registry zu, _decode_priority_weight_strategy nutzt eine strikte Registry ganz ohne Import. Erreichbar wird der Zweig, weil executor_config in _decorated_fields steht (:961) und deserialize_operator solche Felder über den vollständigen rekursiven cls.deserialize schickt (:1152-1157), sodass jeder dort abgelegte Wert durch den __type-Dispatcher läuft. Ausgelöst wird die Rekonstruktion im Scheduler ohne jeden Request und im API-Server durch einen authentifizierten Lesezugriff wie GET /api/v2/dags/{dag_id}/details oder GET /ui/structure/structure_data, für die das Leserecht auf den Dag genügt. Der Trigger-Knoten desselben Deserializers hat dieselbe Wurzel und wurde separat als CVE-2026-33264 behoben, wie das Hersteller-Advisory ausdrücklich festhält: Installationen, die nur wegen jener Meldung aktualisiert haben, blieben über den Ausnahme-Zweig angreifbar.

Proof of Concept

# Ausnahme-Knoten im Feld executor_config eines Operators, wie er in der
# serialisierten Dag-Darstellung (Tabelle serialized_dag, Spalte data) landet:
{"__type": "airflow_exc_ser",
 "__var": {"__type": "dict",
           "__var": {"exc_cls_name": "os.system",
                     "args": ["sh -c 'id > /tmp/PROOF'"],
                     "kwargs": {"__type": "dict", "__var": {}}}}}

# Loest im API-Server bei jedem authentifizierten Lesezugriff auf den Dag aus:
GET /api/v2/dags/<dag_id>/details
Authorization: Bearer <Token eines Kontos mit Leserecht auf den Dag>

turingpoint hat die Ausführung im API-Server auf dem unveränderten Image apache/airflow:3.2.2 (Docker Compose mit CeleryExecutor) live verifiziert: nach dem Leeren der Marker in allen fünf Containern und einem authentifizierten GET lag die Markerdatei mit uid=1000(default) gid=0(root) ausschließlich im Container airflow-apiserver, während Scheduler, Worker, Triggerer und Dag-Prozessor keine Markerdatei aufwiesen. Ohne Token antwortet der Endpunkt mit 401. Dass derselbe Zweig auch im Scheduler erreicht wird, dort ohne jeden Request, führt das Hersteller-Advisory aus.

Auswirkung

  • Ausführung beliebigen Codes im API-Server-Prozess, der die Zugangsdaten der Metadaten-Datenbank und das Signaturgeheimnis der JWT hält und im offiziellen Image als uid=1000 gid=0(root) läuft.
  • Laut Hersteller-Advisory erreicht der Scheduler denselben Zweig in seiner normalen Schleife, also ohne Request und ohne authentifizierten Aufrufer.
  • Bruch der dokumentierten Komponententrennung: Airflows Sicherheitsmodell sagt zu, dass Code eines Dag-Autors nie im Scheduler oder im API-Server ausgeführt wird, ausgenommen vorab registrierte Plugins und Provider.
  • Im API-Server genügt zum Auslösen ein Konto mit Leserecht auf den Dag, eine Administratorrolle ist nicht nötig; anonyme Aufrufe werden mit 401 abgewiesen.
  • Keine vom Standard abweichende Konfiguration erforderlich, betroffen sind alle Versionen ab 3.0.0 vor 3.3.1.

Referenzen

Steckt so etwas in Ihrer Software?

Diese Schwachstelle hat unser Team im Rahmen seiner Arbeit gefunden. Lassen Sie Ihre Anwendungen von denselben Spezialisten prüfen, mit einem Penetrationstest von turingpoint.