## https://sploitus.com/exploit?id=2CBEB41F-A7AD-522F-872B-1381303510FE
# CVE-2025-69219 โ Apache Airflow Providers HTTP RCE via Unsafe Pickle Deserialization
> **Severity:** High (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 โ Improper Control of Dynamically-Managed Code Resources
> **Affected:** `apache-airflow-providers-http >= 5.1.0, **Fixed in:** `apache-airflow-providers-http == 6.0.0`
> **Patch:** [apache/airflow#61662](https://github.com/apache/airflow/pull/61662) ยท [commit 97839f7](https://github.com/apache/airflow/commit/97839f7b0a8ae66d6079bb7fad5a363068f61617)
---
## Overview
Apache Airflow's HTTP provider used Python's `pickle` module to serialize and deserialize HTTP trigger responses for deferred tasks. The vulnerable sink in `HttpOperator.execute_complete()` looked like this:
```python
# VULNERABLE (pre-patch) โ providers/http/src/.../operators/http.py
response = pickle.loads(base64.standard_b64decode(event["response"]))
```
The `event` dictionary is read from the Airflow **database**, meaning any principal with DB write access can plant a malicious pickle payload. When the **Triggerer worker** processes the deferred task it blindly deserializes that payload, executing arbitrary OS commands with the permissions of the Triggerer process โ equivalent to a DAG Author.
---
## Root Cause
`pickle.loads()` on untrusted data is an inherently unsafe operation. Pickle's `__reduce__` protocol allows any Python callable to be embedded in the serialized blob and invoked during deserialization with no sandboxing.
The Airflow Triggerer serializes the HTTP response at completion time and stores it in the DB:
```python
# triggers/http.py (pre-patch)
yield TriggerEvent({
"status": "success",
"response": base64.standard_b64encode(pickle.dumps(response)).decode("ascii"),
})
```
A DB-level attacker replaces this value with a crafted pickle blob, e.g.:
```python
class RCEPayload:
def __reduce__(self):
return (os.system, ("id",))
payload = base64.b64encode(pickle.dumps(RCEPayload())).decode()
```
The Triggerer then calls `execute_complete` โ `pickle.loads()` โ **RCE**.
---
## The Fix (6.0.0)
The patch entirely replaces pickle with a safe, explicit JSON serializer:
```python
# triggers/http.py (post-patch)
yield TriggerEvent({
"status": "success",
"response": HttpResponseSerializer.serialize(response), # JSON dict
})
```
```python
# HttpResponseSerializer.deserialize() โ validates input is a dict, never executes code
if isinstance(data, str):
raise TypeError("Response data must be a dict, got str")
```
The new deserializer only reconstructs a `requests.Response` object from known fields (`status_code`, `headers`, `content`, etc.) โ no arbitrary code execution is possible.
---
## PoC
### Requirements
```bash
# Python 3.9+; install a vulnerable version
pip install "apache-airflow-providers-http>=5.1.0,=6.0.0"
```
---
## Timeline
| Date | Event |
|---|---|
| 2026-03-09 | CVE published by Apache Software Foundation |
| 2026-03-09 | Fix merged (PR #61662, commit 97839f7) |
| 2026-03-09 | Disclosed on oss-security mailing list |
---
## References
- [CVE-2025-69219 on NVD](https://nvd.nist.gov/vuln/detail/CVE-2025-69219)
- [GitHub Advisory GHSA-9r5j-7r2x-rv4g](https://github.com/advisories/GHSA-9r5j-7r2x-rv4g)
- [Patch PR #61662](https://github.com/apache/airflow/pull/61662)
- [Patch commit 97839f7](https://github.com/apache/airflow/commit/97839f7b0a8ae66d6079bb7fad5a363068f61617)
- [oss-security disclosure](http://www.openwall.com/lists/oss-security/2026/03/09/1)
---
## Credits
- **skypher** โ finder
- **[Shauryae1337](https://github.com/Shauryae1337) / ahmetartuc** โ finders
- **poc-cve-2025-69219** โ PoC development
---
## Disclaimer
This proof-of-concept is published for educational and security research purposes only. Only test against systems you own or have explicit written authorization to test. The authors are not responsible for any misuse.