
نشرة أمنية لـ CVE-2026-51385. كان من الضروري نشرها لأن GRAPHIFY لم يتعرف على النشرة ولم ينشرها، وقد خصصت MITRE المعرف CVE-2026-51385، وهذه هي النشرة الخاصة به.
نشرة أمنية لـ CVE-2026-51385. كان من الضروري نشرها لأن GRAPHIFY لم يتعرف على النشرة ولم ينشرها، بينما قامت MITRE بتعيين CVE-2026-51385، وهذه هي النشرة الأمنية الخاصة بها.
ES: كان هذا أول CVE لي، لأكون صادقًا وجدت طريقة لكسر المنطق في دالة مكافحة SSRF، وأردت حقًا نشر أول CVE لي لذا فكرت كيف يمكن أن يؤثر ذلك على الأمان لإظهار التأثير، حتى وإن كان التنفيذ معقدًا (من الصعب أن يحدث هذا في بيئة حقيقية، لكنه ممكن ولذلك درجة تعقيده عالية). أرسلته إلى MITRE وقبلوه. هذه هي النشرة.
EN: كان هذا أول CVE لي، لأكون صادقًا وجدت بنفسي طريقة لكسر المنطق في دالة مكافحة SSRF، وأردت حقًا نشر هذا CVE لذا فكرت كيف يمكن أن يؤثر على الأمان، ووجدت مبررًا لذلك، أرسلته إلى MITRE وقبلوه. هذه هي النشرة.
ضع في اعتبارك أن التقرير أُعد جزئيًا بالذكاء الاصطناعي، لكنه خضع لإشراف بشري
ثغرة SSRF عبر إعادة ربط DNS (TOCTOU) في graphify.
الحزمة: graphify (PyPI: graphifyy)، المستودع Graphify-Labs/graphify
المكوّن المتأثر: مسار استيعاب عناوين URL، graphify add <url>
الإصدارات المتأثرة: >=0.3.2, <=0.4.29
أُصلح في: 0.5.4 (commit dd86271، PRs #591 / #592)
CVSS 3.1: 8.3 (عالية)، CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CWE: CWE-918 (SSRF)، CWE-367 (سباق TOCTOU)
المُبلّغ: Arturo Melgarejo Galindo، باحث أمني مستقل (@Arturo0x90)
يحاول graphify add <url> حماية نفسه من SSRF بالطريقة التي تتبعها الكثير من أدوات الجلب: فهو يحل اسم المضيف، ويتحقق من عنوان IP الناتج مقابل قائمة حظر (loopback وRFC1918 وlink-local والعناوين المحجوزة)، ولا يجلب المحتوى إلا إذا اجتاز هذا الفحص.
المشكلة أن عنوان IP الذي تم التحقق منه ليس هو عنوان IP الذي يتم الاتصال به. يجري التحقق بحث DNS واحدًا، ثم يقوم requests.get(hostname) ببحثٍ ثانٍ مستقل. إذا كنت تملك نطاق DNS لاسم المضيف وأعدت قيمة TTL منخفضة جدًا مع سجلين A، أحدهما عام والآخر داخلي، يمكن للمحلل (resolver) قانونيًا إعطاء إجابة مختلفة لكل استعلام. الإجابة الأولى تجتاز قائمة الحظر. أما الإجابة الثانية فهي حيث يتجه المقبس (socket) فعلًا. هذا هو الخلل بأكمله. إنه تجاوز إعادة الربط الكلاسيكي لمنطق «تحقق من عنوان IP ثم اتصل بالاسم»، والحل يتمثل في تثبيت عنوان IP الذي تم التحقق منه على الاتصال، وهذا ما يفعله 0.5.4.
أريد أن أكون صادقًا بشأن هذا، لأنها عند النظر إليها منفردة تبدو أضعف مما هي عليه.
إذا جلس إنسان وكتب عنوان URL يثق به بالفعل في graphify add، فإن هذا قريب من اللاقيمة. يمكن لهذا الشخص أصلًا توجيه graphify إلى 127.0.0.1 الخاص به إذا أراد. لا يحتاج إلى حيلة إعادة ربط لفعل ذلك، ولا يوجد مهاجم في هذا السيناريو.
تصبح ثغرة حقيقية في اللحظة التي يعمل فيها graphify على عنوان URL لم يختره المشغّل. وبالنسبة لهذه الأداة، ليست هذه حالة نادرة، بل هي طريقة الاستخدام الطبيعية. يبني graphify رسومًا بيانية معرفية تستهلكها مساعدات الذكاء الاصطناعي، لذا فإن السيناريو الواقعي هو أن تقوم عملية ما باستدعاء graphify add بالنيابة عنك: وكيل (agent)، أو مهمة CI، أو سكربت يمر على قائمة عناوين URL من ملف README، أو عنوان URL خرج مباشرة من مخرجات نموذج آخر. في كل هذه الحالات، يتحكم المهاجم في سلسلة الإدخال ولم يقم الإنسان بفحصها أبدًا.
تلك هي الحالة المهمة. بمجرد أن يكون الإدخال غير موثوق ويُربح سباق إعادة الربط، يهبط الجلب على عنوان داخلي بدلًا من العنوان العام الذي تم التحقق منه. وبشكل ملموس يمكنه الوصول إلى:
127.0.0.1 وأي شيء مرتبط بـ loopback،169.254.169.254 (البيانات الوصفية لمثيل سحابي)،100.64.0.0/10، الذي لا تغطيه قائمة الحظر إطلاقًا، لذا فإن هذا النطاق لا يحتاج حتى إلى السباق، يكفي سجل A عادي يشير إليه.والوصول إلى هذه العناوين لا يتحول إلى ضرر إلا بسبب ما يوجد هناك. الكثير من الخدمات الداخلية وخدمات التطوير تعرض نقاط نهاية GET إما تسلّم بيانات أو تغيّر حالة عند GET بسيط. لذا فإن جلب graphify الذي يهبط على إحداها، بمسار وسلسلة استعلام خاطئين، ليس مجرد قراءة. إذا كان الهدف الداخلي هو scriptText في Jenkins، أو مصحح أخطاء Flask/Django في وضع --debug، أو لوحة إدارة، أو نقطة نهاية بيانات وصفية، فإن طلب GET المشوّه هذا هو المهاجم الذي يعمل من داخل المحيط الأمني. graphify هو النائب الذي ينفذ الطلب بالنيابة عنهم.
أنا لا أدّعي أن هذه الثغرة توفّر استغلال RCE. فهي لا تفعل ذلك بمفردها. لكن SSRF كاملة يمكن توجيهها إلى loopback وIMDS وRFC1918 وCGN من مسار استيعاب آلي، هي بالضبط اللبنة الأساسية التي تُبنى عليها تلك الهجمات، وهذه هي النقطة من الإبلاغ عنها.
استخدمت أداة Tavis Ormandy العامة rbndr.us، التي تمنحك اسم مضيف يتقلب بين عنواني IP عند كل عملية تحليل. 7f000001.08080808.rbndr.us يتبادل بين 8.8.8.8 و127.0.0.1.
شغّل شيئًا على الهدف الداخلي (هنا loopback، لأغراض العرض):
sudo python3 -m http.server 80
ثم شغّل الاستيعاب وأعد المحاولة حتى ينجح السباق. تنجح التجربة تقريبًا مرة واحدة من كل 4 أو 5 محاولات، وحلقة بسيطة ترفع الاحتمال إلى ما يتجاوز 99%:
for i in {1..20}; do
graphify add http://7f000001.08080808.rbndr.us/ && break
sleep 1
done
في محاولة ناجحة، يستوعب graphify كل ما أعاده الخادم المحلي على 127.0.0.1:80، حتى مع أن اسم المضيف اجتاز قائمة حظر عناوين IP أولًا.
0.5.4 (dd86271)، بعد 8 أيام.Arturo Melgarejo Galindo، باحث أمني مستقل.