
A PoC for demonstrating CVE-2026-25604
Host Header Injection leading to SAML authentication bypass in Apache Airflow's AWS Auth Manager
An attacker can inject a malicious Host header into the SAML login flow, causing the Assertion Consumer Service (ACS) URL to point to an attacker-controlled server. This allows the attacker to capture valid SAML responses and replay them to gain unauthorized access to the victim Airflow instance — or reuse tokens across different Airflow instances with different access controls.
| Package | Affected | Fixed |
|---|---|---|
apache-airflow-providers-amazon | 8.0.0 – 9.21.x | 9.22.0 |
CVE-2026-25604: Origin Validation Error in AWS Auth Manager (CWE-346)
In AWS Auth Manager, the origin of the SAML authentication has been used as provided by the client and not verified against the actual instance URL. This allowed to gain access to different instances with potentially different access controls by reusing SAML response from other instances.
— NVD
| Source | Link |
|---|---|
| NVD | https://nvd.nist.gov/vuln/detail/CVE-2026-25604 |
| Fix PR | https://github.com/apache/airflow/pull/61368 |
Apache Airflow's AWS Auth Manager uses SAML 2.0 via AWS IAM Identity Center for authentication. When constructing the SAML authentication request, the _prepare_flask_request() method reads the Host header directly from the incoming HTTP request to build the ACS callback URL:
# Vulnerable code in aws_auth_manager.py
def _prepare_flask_request(req):
host = req.headers.get("Host", req.host) # <-- Attacker-controlled
if ":" in host:
hostname, port = host.rsplit(":", 1)
else:
hostname = host
port = "443" if req.scheme == "https" else "80"
return {
"http_host": hostname, # Used to build ACS URL
"server_port": port,
...
}
The resulting http_host and server_port are used to construct the SAML AssertionConsumerService URL. Because the Identity Provider (IdP) trusts this URL, it redirects the authenticated user — along with the signed SAML response — to wherever the Host header points.
┌──────────┐ ┌──────────────┐ ┌─────────────┐
│ 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! │ │
│<─────────────────────│ │
└──────────────────────┴────────────────────────┘
Scenario A — Token Theft via Phishing: An attacker sends a crafted login link (with a spoofed Host header via a reverse proxy) to a legitimate user. After the user authenticates with IAM Identity Center, the SAML response is redirected to the attacker's server. The attacker replays it against the real Airflow instance.
Scenario B — Cross-Instance Token Reuse: In multi-tenant or multi-instance Airflow environments, a valid SAML response from Instance A can be replayed to Instance B. Because the origin is never validated against the actual instance URL, different access controls on Instance B are bypassed.
CVE-2026-25604-PoC/
├── README.md # This file
└── mock_airflow.py # Mock vulnerable Airflow server
pip install flask python3-saml
Set up a SAML 2.0 application in AWS IAM Identity Center following the Airflow AWS Auth Manager documentation:
http://<airflow-host>:<port>/login_callbackaws-auth-manager-saml-clientpython mock_airflow.py <SAML_METADATA_URL> [PORT]
For example:
python mock_airflow.py https://portal.sso.us-east-1.amazonaws.com/saml/metadata/XXXX 8080
In a separate terminal, initiate a SAML login with a manipulated Host header:
curl -v -H "Host: attacker.com:9090" http://127.0.0.1:8080/login
The server responds with a 302 Redirect to the AWS IAM Identity Center login page. Inspect the SAML AuthnRequest — the AssertionConsumerService URL will point to attacker.com:9090/login_callback instead of the legitimate server.
On the mock Airflow server console:
[LOGIN] Host header: attacker.com:9090
[DEBUG] http_host=attacker.com, server_port=9090
The SAML AuthnRequest now instructs the IdP to deliver the authenticated SAML response to attacker.com:9090, giving the attacker a valid token to replay.
airflow/providers/amazon/aws/auth_manager/aws_auth_manager.py — the _prepare_flask_request() method:
host = request.headers.get("Host", request.host)
This line trusts the client-provided Host header without validating it against the configured Airflow base URL (AIRFLOW__API__BASE_URL).
The fix (PR #61368, merged Feb 3, 2026) replaces the request-derived host with the value from Airflow configuration:
- host = request.headers.get("Host", request.host)
+ host = conf.get("api", "base_url")
This ensures the ACS URL always matches the actual instance URL configured by the administrator, regardless of what Host header the client sends.
This proof-of-concept is provided for educational and authorized security testing purposes only. Use it responsibly and only against systems you own or have explicit permission to test.
| Fix Commit |
1a86aec |