
مختبر CVE-2020-13277: ثغرة منطقية في GitLab - وصول غير مصرح به لأي مستخدم إلى المستودعات الخاصة
مختبر CVE-2020-13277: ثغرة منطقية في Gitlab - وصول غير مصرح به لأي مستخدم إلى مستودع خاص
CVE-2020-13277
├── README.md ............... [此 README 说明]
├── imgs .................... [辅助 README 说明的图片]
├── gitlab .................. [Gitlab 容器的挂载目录]
│ ├── Dockerfile .......... [Gitlab 的 Docker 构建文件]
│ ├── config .............. [Gitlab 配置挂载目录]
│ ├── data ................ [Gitlab 数据挂载目录]
│ ├── logs ................ [Gitlab 日志挂载目录]
│ ├── keys ................ [Gitlab 破解 License 存储目录]
│ └── runner .............. [Runner 容器的挂载目录]
├── license ................. [破解 License 的容器构建目录]
│ ├── Dockerfile .......... [License 的 Docker 构建文件]
│ └── license.rb .......... [生成破解 License 的 Ruby 脚本]
├── docker-compose.yml ...... [Docker 的构建配置]
├── keygen.ps1 .............. [Windows: 一键生成破解 License]
├── keygen.sh ............... [Linux: 一键生成破解 License]
├── run.ps1 ................. [Windows: 一键运行 Gitlab 靶场]
├── run.sh .................. [Linux: 一键运行 Gitlab 靶场]
├── register.ps1 ............ [Windows: 一键注册 Runner]
├── register.sh ............. [Linux: 一键注册 Runner]
├── stop.ps1 ................ [Windows: 一键停止 Gitlab 靶场]
└── stop.sh ................. [Linux: 一键停止 Gitlab 靶场]
يعتمد جوهر هذه الثغرة بشكل أساسي على استخدام Mirror Repository - وظيفة النسخ الاحتياطي لمزامنة المستودعات المتطابقة.
ينقسم اتجاه مزامنة Mirror إلى نوعين:
تستغل هذه الثغرة اتجاه Pull في Mirror Repository
من الجدير بالذكر أن Gitlab ينقسم إلى نسختين: CE (الإصدار المجتمعي المجاني) و EE (إصدار المؤسسات المدفوع)، وقد صرّح Gitlab رسميًا بأن هذه الثغرة تؤثر في الإصدارات التالية من CE و EE معًا:
>=10.6, <12.9.10>=12.10, <12.10.11>=13.0, <13.0.6لكن هذا لا يعني أن كل Gitlab Docker Image لهذه الإصدارات يمكن استخدامها لبناء المختبر، وذلك للأسباب التالية:
بعبارة أخرى، لبناء المختبر باستخدام Docker، لا بد من اختيار إصدار Gitlab-EE وتكسيره (أو يمكن لمن يملك المال شراء License) لتفعيل ميزة Mirror Repository - Pull.
ولكن حتى بعد تكسير Gitlab-EE، سواء كان الإصدار 10.x أو 12.x أو ، عندما يتضمن عنوان URL الخاص بـ Mirror Repository - Pull مسارًا محليًا، ستظهر رسالة الخطأ .
./keygen.sh أو ./keygen.ps1./run.sh أو ./run.ps1عند توليد زوج مفاتيح التكسير سابقًا، تمت كتابة المفتاح العام في خلفية حاوية Gitlab، وما تبقى هو رفع المفتاح الخاص عبر الواجهة الأمامية إلى Gitlab لإتمام التكسير:
./gitlab/keys/، انسخ محتوى .gitlab-license (المفتاح الخاص)Enter license key وألصق المفتاح الخاص، ثم انقر على زر Upload license لإتمام التكسيربهذا تكون ميزة Mirror Repository - Pull قد تم تفعيلها

Outbound requests في أسفل الصفحة وحدد خيار Allow requests to the local network from hooks and services ثم احفظبهذا أصبح Mirror Repository - Pull قادرًا على سحب Repository المحلية

./register.sh $TOKEN أو ./register.ps1 $TOKENبهذا يمكن لجميع المستودعات استخدام هذا Runner لتنفيذ سكربتات CI (Pipeline Jobs)

يمكن الرجوع إلى Issue الرسمي أثناء التحقق، لكن عملية التحقق أدناه ستُعدّل بعض الخطوات لتناسب هذا المختبر
باستخدام مستخدم root افتح الصفحة http://127.0.0.1/admin/users لإنشاء 3 حسابات:
victim: حساب الضحيةattacker1: حساب المهاجم 1attacker2: حساب المهاجم 2عند إنشاء الحساب لا يمكن تعيين كلمة مرور أولية، إذ يقوم Gitlab افتراضيًا بإرسال كلمة المرور الأولية إلى البريد الإلكتروني المُعيّن. للتسهيل، يمكن إدخال أي بريد إلكتروني، وإنشاء الحساب أولًا، ثم تعديل الحساب فورًا؛ وعندها يمكن استخدام root لتعيين كلمة المرور الأولية لهذا الحساب دون الحاجة إلى البريد الإلكتروني

victimNew Project:
targetPrivateREADME.md داخل المستودع، واضبط محتواه على mykey is abcxyzمن الواضح أن
targetهو المستودع الخاص للضحيةvictim، وهدفنا هو استغلال الثغرة للحصول على محتوى هذا المستودع

attacker1New Project:
pocPublic.gitlab-ci.yml داخل المستودع، ومحتواه كالتالي:image: "ruby:2.6"
rspec:
script:
- git clone http://gitlab-ci-token:[email protected]/victim/target.git
- cd target
- ls -lah .
- cat README.md
【الهدف】 في الخطوات التالية، سيتم استخدام بعض الأساليب لجعل victim ينفّذ سكربت CI هذا بصلاحياته دون علمه.
نظرًا لعدم إعداد شهادات في هذا المختبر، يمكن استخدام بروتوكول
httpفقط؛ و172.168.30.2هو عنوان IP الذي يخصصهdocker-compose.ymlلحاوية Gitlab. ولأن سكربت CI هذا سيُنفَّذ في النهاية عبر Runner، وحاوية Runner في هذا المختبر ليست نفس حاوية Gitlab، لذا يجب استخدام عنوان IP الذي يخصصه Docker

attacker2New Group:
testPublictest مستودعًا فارغًا تمامًا New Project:
pocPublic
قم بتكوين المستودع المتطابق من خلال Settings => Repository => Pull from a remote repository:
Mirror repository: حدد الخيارGit repository URL: أدخل http://GITLAB/attacker1/pocPassword: (اتركه فارغًا، فمستودع Pull بمستوى سرية Public ولا يتطلب كلمة مرور)Trigger pipelines for mirror updates: حدد الخيار (لتشغيل سكربتات CI عند مزامنة المرآة)بعد نجاح التكوين، يتم كل 30 دقيقة التحقق مما إذا كان المستودع المصدر قد تغيّر، وفي حال وجود تغيير تتم المزامنة القسرية.
Git repository URLيشير إلى مستودع poc الذي تم إنشاؤه سابقًا. والسبب في عدم استخدام 127.0.0.1 هو أن خاصية Pull تمنع سحب المستودعات المحلية، لكن يمكن تجاوز ذلك باستخدام DNS:GITLABهو اسم المضيف (hostname) الذي يخصصهdocker-compose.ymlلحاوية Gitlab، ويتم تعيينه افتراضيًا في/etc/hosts. على الرغم من أن المضيف لا يمكنه تحليلGITLAB، إلا أنه داخل الحاوية يُعتبر الوصول إلىhttp://127.0.0.1/attacker1/poc

attacker2testvictim إلى المجموعة:
Add new member to test: victimPermissions: OwnerExpiration date: (اتركه فارغًا، أي دون انتهاء)=> Setting => Account => Delete account لحذف الحساب الحالي (attacker2)【الهدف】 لا يوجد في مجموعة test سوى مالكَين اثنين: victim وattacker2. وعند حذف مستخدم attacker2، تنتقل ملكية مجموعة test وجميع المستودعات التابعة لها قسريًا إلى victim. وحاليًا يوجد في مجموعة test مستودع test/poc تتم مزامنته من attacker1/poc، أي أن ذلك يعادل نقل ملكية مستودع test/poc قسريًا إلى مستخدم victim.


دعنا نستعرض الوضع الحالي حتى الآن:
test/poc للضحية victimtest/poc تتم مزامنته من مستودع attacker1/poc (يتم التحقق كل 30 دقيقة من وجود تغييرات تستدعي المزامنة)attacker1/poc يتحكم فيه المهاجم، بما في ذلك سكربت CI .gitlab-ci.ymltest/poc يمتلك إعداد Trigger pipelines for mirror updates، فسيتم تنفيذ سكربت CI عند كل مزامنةOwner لمستودع test/poc هو victim، فسيتم تنفيذ سكربت CI بصلاحيات victim
بالعودة إلى محتوى سكربت CI .gitlab-ci.yml الذي تم تعيينه سابقًا، فإن هذا الـ poc يستخدم صلاحيات victim عبر Runner للوصول إلى بنية دليل المستودع الخاص target وملف README.md:
image: "ruby:2.6"
rspec:
script:
- git clone http://gitlab-ci-token:[email protected]/victim/target.git
- cd target
- ls -lah .
- cat README.md
بعد ذلك، يحتاج المهاجم فقط إلى تعديل بعض المحتويات غير المهمة في مستودع attacker1/poc، وفي أسوأ الحالات عليه الانتظار 30 دقيقة فقط، ليتم تنفيذ سكربت CI المحدد بصلاحيات victim.
على الرغم من أن المهاجم لا يملك صلاحية مباشرة للوصول إلى نتائج تنفيذ Pipeline Jobs، إلا أنه يحتاج فقط إلى تعديل وجهة إخراج السكربت، مثل إرسال محتوى المستودع إلى بريد إلكتروني أو خادم FTP محدد، وبذلك يمكنه سرقة كود المستودع.

13.xImport url is blocked: Requests to localhost are not allowedعلى الرغم من أنه يمكن تكوين عنوان URL محلي بعد ضبط Allow requests to the local network from hooks and services من خلال Admin area => Settings => Network => Outbound requests، إلا أنه عند مزامنة المرآة تظهر رسالة الخطأ 2:Fetching remote upstream failed: fatal: unable to access http://127.0.0.1/xxxx/: The requested URL returned error: 301. بعبارة أخرى، فقط Pull Remote Repository هي المتاحة.
لكن لحسن الحظ، على الرغم من أن الإصدارين 12.x و 13.x صارمان جدًا في طريقة التحقق من عناوين URL المحلية، إلا أن الإصدار 10.x لديه حل: عند تكوين عنوان Pull URL، يكفي تكوين اسم خدمة DNS المحلي لتجاوز هذا الحظر.
مما سبق يتضح أن الخيار النهائي الوحيد هو بناء هذا المختبر باستخدام إصدار Docker Image gitlab-ee:10.6.0-ee.0.
في الواقع، من خلال ما سبق يمكن أيضًا إدراك أن شروط استغلال هذه الثغرة قاسية نسبيًا، وبشكل أساسي يصعب على الفقراء أن يتأثروا بهذه الثغرة