Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2021-3156 — CVE-2021-3156 POC and Docker and Analysis write up | Kitploit
Tools/GitHubGitHub/chenaotian/cve-2021-3156
Privilege EscalationVulnerability AnalysisCode AnalysisExploitationReverse EngineeringDebuggersFuzzingPenetration TestingLearning & EducationBinary ExploitationLabs & Practice
112254 years agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
GitHub
chenaotian/cve-2021-3156

CVE-2021-3156

CVE-2021-3156 POC and Docker and Analysis write up

View Repository

CVE-2021-3156

[toc]

Vulnerability Overview

Vulnerability ID: CVE-2021-3156

Vulnerability Score:

Affected Product: Linux sudo

Affected Versions: 1.8.2-1.8.31sp12; 1.9.0-1.9.5sp1

Exploitation Conditions: Linux local; sudo is suid and executable

Impact: Local privilege escalation

Source Code: https://www.sudo.ws/getting/source/

Environment Setup

Docker Environment: chenaotian/cve-2021-3156

The Docker I set up provides:

  1. A self-compiled sudo with source debugging support
  2. glibc with debug symbols
  3. gdb and gdb plugins pwngdb & pwndbg
  4. exp.c and its compiled exploit

Everything is in the /root directory:

image-20220124223312224

  • The exp directory contains the exploit code and compiled binary, can be run directly in this Docker
  • glibc-2.27 is the source directory of the libc version in this environment
  • sudo-1.8.21 is the source directory of sudo in this environment, which I used to compile it.

Testing the exploit:``` cd exp su test ./exp whoami

For debugging related content, see later [some debugging commands](#一些调试命令)


## Vulnerability Principle

Vulnerability trigger payload```shell
sudoedit -s '\' `python3 -c "print('A'*80)"`

Source code analysis (sudo-1.8.21): First is the main function in sudo.c (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);
    ··· ···
    ··· ···
}

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

}

- First, the `parse_args` function is called to process the input parameters. In fact, we only input `-s` here, nothing much to set, setting `sudo_mode` to `MODE_EDIT` and `MODE_SHELL`.

- Then, depending on the `sudo_mode`, `MODE_EDIT` will call `policy_check`.

Next is the `policy_check` function in `sudo.c` (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);
··· ···
··· ···

}

Then called the sudoers_policy_main function in sudoers.c (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();
    ··· ···
    ··· ···
    ··· ···
}

Some global variables are set here, NewArgc and NewArgv as shown below; they are essentially the passed parameters.

image-20220123113819116

Then it enters the set_cmnd function in sudoers.c (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';
    } 
    ··· ···
}
}
··· ···
··· ···

}

The overflow also occurs here. According to the comments in the code, the heap overflow occurs when copying to the heap. The original intention of this code is not difficult to understand: it copies all the arguments in NewArgv to the heap, separated by spaces. When encountering `\` followed by a non-space character, it only copies that character.

**But it does not consider a situation where a NewArgv element ends with `\`, resulting in a `\` + `\x00` structure. Since `\x00` is not a space character (absurd), this means that after copying `\x00` to the heap, the `from` variable is incremented again (incremented twice in one loop), directly bypassing the opportunity to check the while loop's end marker `\x00`, so it assumes the argument has not been fully copied and continues copying until encountering the next `\x00`.**

In this scenario, you can see that `\` + `\x00` is immediately followed by the next argument `A*80`, so it will continue copying to the end of `A*80`. But don't forget that the program will then process the `A*80` argument normally and copy it again. So here `A*80` is copied twice in total, but the chunk was allocated based on the size of only one `A*80` string, far exceeding the allocated chunk length.

image-20220123113907744

Then an overflow occurs. Before copying:

image-20220123114036691

After copying:

image-20220123114137794

The overall vulnerability trigger path (you can set breakpoints directly on these functions when debugging) is:

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

## Vulnerability Exploitation Principle

Referenced [blasty/CVE-2021-3156](https://github.com/blasty/CVE-2021-3156) , **but his heap layout method is not always achievable. Here we analyze the heap layout method in detail.** By passing environment variables `LC_*` to lay out the heap, then making the overflowing chunk exactly overwrite the `service_user` structure needed by `nss_load_library` to load a shared object. Overwrite the shared object name string in this structure, then make the program load the specified shared object to achieve arbitrary code execution.

Although the logic seems clear, the details to handle are still troublesome:

1. Related data structures and mechanisms in nss_load_library
2. How setlocale uses the environment variable `LC_*` for heap layout
Download Tool