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

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

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

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

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
gortex-PoC — PoC — تتبع الروابط الرمزية للكشف عن محتوى خارج المستودع عبر search_text في Gortex (GHSA-6vhf-4wcm-2r83, CVE-2026-87003, CVSS 5.5). | Kitploit
أدوات/GitHubGitHub/squeeze440/gortex-poc
التحليل الثابتتحليل الثغرات الأمنيةتحليل الكودالاستغلالجمع المعلوماتأمن الويبالأوراق والأبحاث
GitHubsqueeze440/gortex-poc

gortex-PoC

PoC — تتبع الروابط الرمزية للكشف عن محتوى خارج المستودع عبر search_text في Gortex (GHSA-6vhf-4wcm-2r83, CVE-2026-87003, CVSS 5.5).

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

الأكثر شعبية

عرض الكل →

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

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

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

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

متابعة الروابط الرمزية في مسار ملفات المفهرس → الكشف عن محتوى خارج المستودع عبر search_text

حالة CVE: مطلوبة، بانتظار الإسناد. تم نشر هذا الاكتشاف باسم GHSA-6vhf-4wcm-2r83. عند إسناد CVE يُعاد تسمية هذا المستودع CVE-YYYY-NNNNN-gortex-PoC وتُستبدل هذه اللافتة برابط CVE.

الباحثDostxodjayev Abdullox (@squeeze440)
الاستشارةGHSA-6vhf-4wcm-2r83
CVSS 3.15.5 (متوسط)
الضعفCWE-59, CWE-200

متابعة الروابط الرمزية في مسار ملفات المفهرس → الكشف عن محتوى خارج المستودع عبر search_text

الملخص

يسمح خلل في حل الروابط (CWE-59) في مفهرس المستودعات الخاص بـ gortex بمرور مدخلة ملف مرتبطة رمزياً تشير إلى خارج جذر المستودع المفهرس بصمت، وفهرسة محتواها، ثم تقديمه لاحقاً كما هو — بما في ذلك إلى هدف خارج الجذر بالكامل — عبر مصرف إعادة القراءة الحية لأداة MCP المسماة search_text، والتي لا تطبّق أبداً فحص الحجز guardSymlinkWithinRepo الخاص بالمشروع نفسه.

المنتج

zzet/gortex — https://github.com/zzet/gortex

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

الالتزام b5a63e25c0719c8ce935e59abf85618e2b4b1e64 (git describe: v0.62.0-35-gb5a63e25)، وهو HEAD المستودع وقت التدقيق. تأكد أنه لا يزال موجوداً في أحدث إصدار موسوم v0.62.0، أي بعد أن استقر إصلاح الاستشارة غير ذات الصلة GHSA-w42c-h7hr-f67p (v0.55.0).

تقدير CVSS v3.1

CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N — 5.5 (متوسط)

  • AV:L — يتوافق مع سابقة التقييم الخاصة بهذا المشروع لواجهة MCP الخاصة به (GHSA-w42c-h7hr-f67p استخدم أيضاً AV:L): النشر الافتراضي هو خادم MCP محلي يُوصَل إليه عبر stdio بواسطة عملية الوكيل على نفس المضيف؛ AV:N لا ينطبق إلا على وضع --http-addr الاختياري.
  • UI:R — يتطلب أن يصدر عميل MCP (وكيل LLM، أو إنسان عبر gortex call) استدعاء search_text / search_text regexp:true. يكفي نص حرفي واسع (مثل حرف شائع واحد) أو تعبير نمطي مطابق (.) مقابل مستودع مفهرس يحتوي على الرابط الرمزي المزروع لإفراغ الملف الهدف — دون الحاجة إلى معرفة سرية بمحتواه — لذا فهذا عتبة منخفضة، لكنه إجراء متميز عن مجرد الفهرسة.
  • C:H — الكشف عن محتوى ملفات عشوائية، بلا حدود تحددها حدود المستودع، لأي شيء يستطيع مستخدم نظام التشغيل الخاص بالخادم قراءته (مفاتيح SSH، ملفات بيانات الاعتماد السحابية، مستودعات مستأجرين آخرين في خادم متعدد المستودعات، إلخ)، يُعاد كما هو في استجابة الأداة.
  • I:N / A:N — هذا خلل في مسار القراءة فقط؛ لا يُكتب شيء ولا يُجعل شيء غير متاح.

التفاصيل

السبب الجذري 1 — مسار المفهرس لا يفحص أبداً مدخلات الملفات المرتبطة رمزياً

internal/indexer/indexer.go:2549-2604، مسار قبول المجموعة الأساسي:

root@kitploit:~
err = filepath.WalkDir(absRoot, func(path string, d os.DirEntry, err error) error {
    if err != nil { return nil }
    if d.IsDir() {
        if idx.shouldPruneDir(path, absRoot) { return filepath.SkipDir }
        return nil
    }
    lang, ok := idx.effectiveLanguage(path, nil)   // detects by extension; symlink name is enough
    if !ok { return nil }
    if idx.shouldExclude(path, absRoot, false) { return nil }
    info, statErr := d.Info()                      // Lstat'd DirEntry.Info() — no ModeSymlink check
    ...
    files = append(files, walkedFile{path: path, lang: lang, size: info.Size(), mtimeNano: info.ModTime().UnixNano()})
    return nil
})

يستخدم filepath.WalkDir دلالات Lstat، لذا فإن المجلد المرتبط رمزياً محمي بطبيعته — d.IsDir() تكون false له ولا يُتكرر الدخول إليه أبداً. أما مدخلة الملف المرتبط رمزياً فلا تحصل على حماية مكافئة: لا شيء هنا (ولا في shouldExclude/shouldPruneDir، internal/indexer/indexer.go:5724-5813، التي تطابق فقط أنماط مسارات بنمط gitignore) يستدعي d.Type()&os.ModeSymlink أو os.Lstat أو filepath.EvalSymlinks على مدخلة الملف قبل إدراجها في قائمة الانتظار. ملف باسم مثل pwn.go هدفه /etc/passwd أو ~/.ssh/id_rsa أو مستودع مستأجر آخر يُقبل لمجرد أن اسمه يطابق امتداداً مسجلاً (effectiveLanguage، internal/parser DetectLanguageContent، قائم على المسار). قائمة الملفات الممرورة هذه بالضبط تصبح idx.fileMtimes (internal/indexer/indexer.go:3760: idx.fileMtimes[idx.relKey(f.path)] = f.mtimeNano).

السبب الجذري 2 — مصرف search_text يعيد قراءة الملفات الحية دون حارس حجز

internal/indexer/grep.go:17-64 (GrepText / warmTrigramSearcher) يبني باحث الـ trigram مباشرة من مفاتيح idx.fileMtimes:

root@kitploit:~
rels := ... // from idx.fileMtimes, i.e. includes the symlinked file's rel path
idx.trigramSearcher = trigram.Build(root, rels)

ثم يقوم internal/search/trigram/searcher.go:32-48 (Build) و:55-86/:102-176 (Grep/GrepRegexp) بما يلي:

root@kitploit:~
content, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(rel)))  // Build — follows symlink
...
f, err := os.Open(filepath.Join(s.root, filepath.FromSlash(rel)))          // Grep/GrepRegexp — follows symlink

يتبع os.ReadFile/os.Open الروابط الرمزية افتراضياً (بدون O_NOFOLLOW). لا هذه الدالة ولا internal/mcp/tools_search_text.go:35-126 (handleSearchText، معالج MCP الذي يستدعي s.indexer.GrepText/GrepRegexp) يستدعي أبداً resolveFilePath أو guardSymlinkWithinRepo. هاتان دالتا الحجز موجودتان وموصولتان بشكل صحيح في كل مسار آخر يقدّم المحتوى — internal/mcp/tools_fileops.go (read_file، write_file/edit_file اعتباراً من إصلاح v0.55.0)، internal/mcp/tools_coding.go:919 (get_symbol_source)، internal/mcp/tools_lsp.go:264، internal/mcp/tools_export.go:95 — لكن مسار grep بالـ trigram في search_text مصرف منفصل لم يُمنح نفس الحارس أبداً. يؤكد grep -rn guardSymlinkWithinRepo عبر الشجرة عدم وجود أي إشارات في tools_search_text.go أو grep.go أو trigram/searcher.go.

فحص خطر التكرار / التميز

هذا متميز حقاً عن GHSA-w42c-h7hr-f67p:

  • تلك الاستشارة: مسار الكتابة، resolveFilePath يعفي المسارات المطلقة + write_file/edit_file/move_inline لا تستدعي أبداً guardSymlinkWithinRepo (أُصلح في v0.55.0).
  • هذا الاكتشاف: مسار القراءة، مسار الاكتشاف الخاص بالمفهرس نفسه (وليس أي معالج MCP) يقبل ملفاً مرتبطاً رمزياً دون فحص Lstat، وأداة MCP مختلفة (search_text، وليس read_file/get_symbol_source) تقدّم محتواه عبر مصرف (internal/search/trigram) لا يستدعي بشكل مستقل أبداً guardSymlinkWithinRepo. لم يمس إصلاح مسار الكتابة أي من هذين الموقعين. هذا يطابق نمط الأدوات الشقيقة المؤكد بالفعل في ozgurcd/gograph (GHSA-6h6w-vhgr-2hp6) وvitali87/code-graph-rag (GHSA-85gg-2gfq-q95m): مسار اكتشاف ملفات دون حماية من الروابط الرمزية، مقترن بمصرف يقدّم المحتوى يفتقر بشكل مستقل إلى حارس حجز.

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

التتبع الساكن (مراجع file:line أعلاه) كامل وقابل لإعادة الإنتاج بالفحص. تمت محاولة التحقق الديناميكي وهو محجوب فعلاً بفجوة في سلسلة الأدوات في هذه البيئة المعزولة، موثّق بأمانة بدلاً من التغطية عليه:

root@kitploit:~
$ CGO_ENABLED=1 go test ./internal/mcp/... -run TestPoC_SymlinkFollow_SearchTextDisclosesOutOfRepoContent -v
mcp [build failed]
  cgo: C compiler "gcc" not found: exec: "gcc": executable file not found in $PATH

كل مستخرج لغة tree-sitter في قاعدة الشيفرة هذه (بما في ذلك مستخرج Go العادي، github.com/tree-sitter/tree-sitter-go's bindings/go/binding.go: // #cgo CFLAGS: -std=c11 -fPIC / import "C") يتطلب cgo للترجمة — لا يوجد مسار شيفرة Go خالص لتسجيل لغة واحدة حتى، لذا لا يمكن بناء parser.Registry دون مترجم C عامل. لا تحتوي هذه البيئة المعزولة على gcc/cc/clang في $PATH ولا صلاحية root بدون كلمة مرور (sudo -l يطلب كلمة مرور تفاعلية) لتثبيت أحدها عبر apt-get install gcc (موجود في ذاكرة apt المؤقتة، gcc-16/kali-last-snapshot، لكنه غير قابل للوصول دون صلاحية). هذا قيد بيئي، وليس اكتشافاً عن gortex.

اختبار PoC نفسه — المكتوب وفق نمط أداة الاختبار الموجودة في المشروع نفسه (internal/mcp/tools_search_text_test.go's TestSearchText، ونفس شكل newSingleRepoServer/callTool الذي استخدمته PoC الاستشارة السابقة) — متروك في مكانه عند internal/mcp/poc_symlink_search_disclosure_test.go ليُشغّله المشرف في بيئة تحتوي على سلسلة أدوات C:

root@kitploit:~
func TestPoC_SymlinkFollow_SearchTextDisclosesOutOfRepoContent(t *testing.T) {
	outsideDir := t.TempDir()
	secretPath := filepath.Join(outsideDir, "secret.txt")
	const secretMarker = "GORTEX-POC-SECRET-OUTSIDE-REPO-9f3a1c"
	os.WriteFile(secretPath, []byte("BEGIN OUTSIDE-REPO FILE\n"+secretMarker+"\nEND OUTSIDE-REPO FILE\n"), 0o600)

	repoDir := t.TempDir()
	os.WriteFile(filepath.Join(repoDir, "main.go"), []byte("package app\n\nfunc Hello() {}\n"), 0o644)
	os.Symlink(secretPath, filepath.Join(repoDir, "pwn.go"))   // symlink INSIDE repo -> OUTSIDE target

	idx := indexer.New(g, reg, cfg.Index, zap.NewNop())
	idx.Index(repoDir)                                          // walk admits pwn.go, no symlink check
	srv := NewServer(eng, g, idx, nil, zap.NewNop(), nil)

	res := callTool(t, srv, "search_text", map[string]any{"query": secretMarker})
	// asserts: res.IsError == false, exactly 1 match, Path == "pwn.go",
	// Text contains secretMarker — i.e. the OUTSIDE file's real content.
}

بالنظر إلى التتبع على مستوى الشيفرة أعلاه — كل خطوة مذكورة بالدالة الدقيقة ولا يوجد استدعاء حجز في أي مكان على المسار من filepath.WalkDir عبر trigram.Build/Grep إلى استجابة handleSearchText — يُقدَّم هذا على أنه متتبَّع ساكناً، غير مؤكد ديناميكياً، وفق القاعدة الأساسية لهذا التدقيق ضد تلفيق أدلة PoC.

التأثير

أي مستودع مفهرس يحتوي على رابط رمزي هدفه يُحلّ خارج جذر ذلك المستودع (مزروع بواسطة مساهم خبيث، أو موجود في مستودع تبعية/إضافة مستنسخ، أو مُدخَل عبر PR في سلسلة التوريد يفهرسه الخادم لاحقاً) يتيح لعميل MCP — استدعاء search_text عادي، أو استدعاء محقون بالتنبيه/عدائي، بنفس نموذج التهديد الخاص بـ GHSA-w42c-h7hr-f67p — قراءة المحتوى الحرفي لملف الهدف ذلك، بغض النظر عن حارس الحجز الذي يفرضه المشروع خلاف ذلك على read_file وget_symbol_source. في خادم متعدد المستودعات (internal/indexer/multi.go:3525-3562، الذي يوزّع الاستدعاء غير المحروس المطابق لكل مستودع متتبع) يعبر هذا أيضاً حدود المستأجرين/المستودعات داخل نفس نسخة الخادم.

نقاط الضعف

  • CWE-59 — حل الروابط غير الصحيح قبل الوصول إلى الملف ('متابعة الروابط'): مسار المفهرس (internal/indexer/indexer.go:2549) يقبل مدخلة ملف مرتبطة رمزياً دون فحص Lstat/ModeSymlink.
  • CWE-200 — كشف المعلومات الحساسة لفاعل غير مصرح له: search_text (internal/mcp/tools_search_text.go) → trigram.Build/Grep/GrepRegexp (internal/search/trigram/searcher.go) تقدّم المحتوى المحلول عبر الرابط الرمزي دون فحص الحجز guardSymlinkWithinRepo، بخلاف كل مصرف قراءة آخر في نفس قاعدة الشيفرة.

المعالجة

طبّق guardSymlinkWithinRepo الموجود في المشروع نفسه (internal/mcp/tools_fileops.go:353) — أو فحصاً مكافئاً قائماً على Lstat — عند النقطتين:

  1. وقت المسار: في دالة الاستدعاء filepath.WalkDir (internal/indexer/indexer.go:2549)، ارفض أو تخطَّ مدخلة ملف DirEntry التي d.Type()&os.ModeSymlink != 0 (محاكياً الحماية الضمنية التي يمنحها Walk للمجلدات بالفعل)، أو حلّل filepath.EvalSymlinks(path) وأكّد الاحتواء داخل absRoot قبل الإضافة إلى files.
  2. وقت القراءة (دفاع في العمق): في trigram.Build/Grep/GrepRegexp (internal/search/trigram/searcher.go)، أو عند موقع استدعاء handleSearchText، طبّق نفس فحص احتواء المسار الحقيقي بنمط guardSymlinkWithinRepo المستخدم بالفعل بواسطة read_file/get_symbol_source/tools_lsp/tools_export قبل إعادة Text الخاص بالمطابقة. أي إصلاح بمفرده يغلق هذا؛ وكلاهما معاً يطابق وضع الحجز الذي تلتزم به بقية قاعدة الشيفرة بالفعل.

الفضل

Dostxodjayev Abdullox (GitHub: @squeeze440)

قناة الإبلاغ

الإبلاغ الخاص عن الثغرات (PVR) مُفعَّل ومؤكد على zzet/gortex؛ ينطبق تدفق مسودة الاستشارة القياسي لـ GitHub Security Advisory (GHSA)، بما يتوافق مع إفصاح المشروع السابق GHSA-w42c-h7hr-f67p.

تنزيل الأداة