
PoC وشرح لتصعيد الصلاحيات في HackTheBox "Nexus": خدمة مزامنة قوالب Gitea التي تعمل بصلاحيات root ومعرضة لاجتياز المسار عبر كائنات Git المزورة. للاستخدام التعليمي فقط.
اجتياز مسار مزامنة قوالب Gitea عبر كائنات Git المزوّرة → root
إثبات مفهوم لاستغلال تصعيد الصلاحيات لجهاز HackTheBox Nexus.
تقوم خدمة systemd مملوكة لـ root بمزامنة مستودعات القوالب في Gitea إلى القرص
دون تعقيم أسماء المدخلات، مما يسمح لمستخدم مصادَق عليه ذي صلاحيات منخفضة
بكتابة ملفات عشوائية بصلاحيات root عبر اجتياز المسار — يُستخدم هنا لزرع
مفتاح SSH في /root/.ssh/authorized_keys.
يُقدَّم هذا الكود لأغراض تعليمية واختبار أمني مصرّح به فقط — أي بيئات المختبرات مثل HackTheBox، أو الأنظمة التي تملكها أو لديك إذن كتابي صريح لاختبارها. الاستخدام غير المصرّح به ضد أنظمة لا تتحكم بها غير قانوني. لا يتحمل المؤلف أي مسؤولية عن سوء الاستخدام.
إذا كان جهاز Nexus لا يزال نشطًا على HackTheBox، فلا تنشر أو تشارك هذا الاستغلال — قواعد HTB تحظر توزيع الحلول للأجهزة النشطة.
| الفئة | اجتياز المسار (CWE-22) → تصعيد الصلاحيات المحلية |
| المكوّن | gitea-template-sync.service (ملف مخصص /etc/gitea/template-sync.py) |
| يعمل بصلاحيات | root، يُشغَّل كل ~60 ثانية بواسطة gitea-template-sync.timer |
| السبب الجذري | os.path.join() على أسماء غير معقّمة مأخوذة من كائنات شجرة Git |
| التأثير | مستخدم مصادَق عليه ذو صلاحيات منخفضة → root |
يقرأ سكربت المزامنة الملفات من المستودعات المُعلَّمة كـ قوالب في Gitea ويكتبها إلى نظام الملفات المحلي. يُبنى مسار الوجهة تقريبًا كما يلي:
dest = os.path.join(base_dir, name) # name comes straight from the Git tree
open(dest, "w").write(content)
لا يقوم os.path.join() بـ تحييد تسلسلات ... لذلك فإن مدخلة شجرة يكون
اسمها مسار اجتياز (مثل ../../../../../root/.ssh/authorized_keys) تجعل
العملية ذات الصلاحيات العالية تكتب خارج الدليل المقصود.
يخزّن Git المحتوى والأسماء بشكل منفصل:
name → object (هنا يوجد اسم الملف)؛اسم الملف في مدخلة الشجرة هو مجرد نص، لذا يمكن أن يكون .. — لكن عميل Git
القياسي يرفض إنشاء مثل هذه المدخلة، تحديدًا لمنع هذا الهجوم.
يتجاوز إثبات المفهوم هذا الفحص من جانب العميل عبر كتابة كائنات Git خام
مباشرةً في .git/objects/ (بحساب معرّفات SHA-1 يدويًا) وصياغة refs/heads/main
يدويًا. يحتوي المستودع المدفوع بعد ذلك على مدخلة شجرة باسم ..، والتي تحلّها
عملية المزامنة ذات صلاحيات root وتتبعها.
www-data..env الخاص بالإنتاج، مُعاد استخدامها
ككلمة مرور نظام → SSH كمستخدم ذي صلاحيات منخفضة.root.الموضوع المتكرر عبر المراحل الثلاث: عملية ذات صلاحيات عالية تثق ببيانات يتحكم بها المهاجم دون تحقق (نوع MIME مزوّر، كلمة مرور مُعاد استخدامها، اسم ملف غير معقّم).
http://localhost:3000.git وpython3 وcurl وssh (افتراضيًا على الجهاز).chmod +x nexus_privesc.sh
./nexus_privesc.sh -t <TARGET_IP> -u <GITEA_USER> -p <GITEA_PASS> [-d DEPTH]
مثال:
./nexus_privesc.sh -t 10.129.21.192 -u jones -p 'REDACTED' -d 5
/tmp/.exploit_key)... التي تُطابق
/root/.ssh/authorized_keys، مع المفتاح العام للمهاجم كمحتوى.root بالمفتاح المزروع.راقب المزامنة على الهدف باستخدام:
tail -f /var/log/template-sync.log
تسجّل عملية ناجحة سطرًا مثل:
synced: ../../../../../root/.ssh/authorized_keys
DEPTHDEPTH هو عدد مستويات .. اللازمة للصعود من دليل المزامنة/التجهيز
إلى جذر نظام الملفات (/). إذا نجحت المزامنة في السجل لكن فشل SSH بصلاحيات
root، فهذا يعني أن الاجتياز وصل إلى المستوى الخاطئ — أعد المحاولة بـ -d 4 أو
-d 6. يمكن تأكيد القيمة الدقيقة بقراءة مسار التجهيز في
/etc/gitea/template-sync.py.
لا تبنِ مسارات نظام الملفات من أسماء غير موثوقة باستخدام os.path.join() وحدها.
طبّع المسارات واحصرها، مثل:
base = os.path.realpath(base_dir)
dest = os.path.realpath(os.path.join(base, name))
if not dest.startswith(base + os.sep):
raise ValueError("path traversal blocked")
ارفض أسماء مدخلات الشجرة التي تحتوي على .. أو مسارات مطلقة أو فواصل مسارات.
شغّل خدمة المزامنة تحت حساب مخصص ذي صلاحيات منخفضة، وليس root.
فعّل receive.fsckObjects = true في Gitea لرفض الكائنات المدفوعة المشوّهة.
<your name / handle> — للاستخدام التعليمي على HackTheBox.
| الخيار | الوصف | الافتراضي |
|---|
-t | عنوان IP الهدف (لجلسة SSH النهائية بصلاحيات root) | — (مطلوب) |
-u | اسم مستخدم Gitea | — (مطلوب) |
-p | كلمة مرور Gitea | — (مطلوب) |
-d | عدد مستويات .. للوصول إلى / من دليل المزامنة | 5 |