
CVE-2021-3156 POC y Docker y informe de análisis
[toc]
ID de vulnerabilidad: CVE-2021-3156
Puntuación de vulnerabilidad:
Producto vulnerable: linux sudo
Versiones afectadas: 1.8.2-1.8.31sp12; 1.9.0-1.9.5sp1
Condiciones de explotación: local en linux; sudo con SUID y ejecutable
Efecto de explotación: escalada de privilegios local
Obtención del código fuente: https://www.sudo.ws/getting/source/
Entorno Docker: chenaotian/cve-2021-3156
He creado un docker propio que proporciona:
Todo está en el directorio /root:
image-20220124223312224
Prueba del exp:``` cd exp su test ./exp whoami
Consulte más adelante para contenido relacionado con depuración [algunos comandos de depuración](#一些调试命令)
## Principio de la vulnerabilidad
payload de activación de vulnerabilidad```shell
sudoedit -s '\' `python3 -c "print('A'*80)"`
Análisis del código fuente (sudo-1.8.21): Primero está la función main en 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);
··· ···
··· ···
}
··· ···
··· ···
}
- Primero, se llama a la función parse_args para procesar los parámetros que ingresamos, en realidad aquí solo ingresamos un `-s`, no hay nada que configurar, se establece sudo_mode en MODE_EDIT y MODE_SHELL.
- Luego, dependiendo de sudo_mode, MODE_EDIT llama a policy_check
A continuación, la función policy_check en 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);
···
}
Se llamó a la función de callback plugin->u.policy->check_policy ,se puede depurar para ver la función real de esta función:
image-20220123113326096
La función llamada es sudoers_policy_check en policy.c (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);
··· ···
··· ···
}
Luego se invoca la función sudoers_policy_main en 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();
··· ···
··· ···
··· ···
}
Aquí se configuran algunas variables globales, NewArgc y NewArgv, como se muestra a continuación, que son los parámetros pasados.
image-20220123113819116
Después se entra en la función set_cmnd en 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';
}
··· ···
}
}
··· ···
··· ···
}
El desbordamiento también ocurre aquí, según se puede ver en los comentarios del código, el desbordamiento del montón ocurre al copiar al montón. La intención original de este código no es difícil de entender: copiar todos los parámetros de NewArgv al montón, separados por espacios, y cuando se encuentra `\+<carácter no espaciado>`, solo copiar ese carácter.
**Pero no considera un caso: si un elemento de NewArgv termina con `\`, entonces es una estructura como `\+\x00`, y `\x00` no pertenece a la clase de caracteres espaciados (¡increíble!). Esto significa que, después de copiar `\x00` al montón, la variable `from` se incrementa (dos veces en un bucle) y se salta la oportunidad de que el bucle while termine con el marcador `\x00`, pensando que los parámetros no se han copiado por completo y continúa copiando hasta encontrar el siguiente `\x00`.**
En este escenario se puede ver que `\+\x00` va seguido inmediatamente del siguiente parámetro `A*80`, por lo que continúa copiando hasta el final de `A*80`. Pero no hay que olvidar que luego se procesará realmente el parámetro `A*80`, y se copiará de nuevo, por lo que aquí se copian dos veces `A*80`, pero el chunk se asignó según el tamaño de solo una cadena `A*80`, mucho menor que la longitud copiada.
image-20220123113907744
Luego se produce el desbordamiento, antes de copiar:
image-20220123114036691
Después de copiar:
image-20220123114137794
La ruta general de activación de la vulnerabilidad es (al depurar, basta con poner puntos de interrupción directamente según estas funciones):
- sudo.c : main
- sudo.c : policy_check
- policy.c : sudoerrs_policy_check
- sudoers.c : sudoers_policy_main
- sudoers.c : set_cmnd
- sudoers.c : 859
## Principio de explotación de la vulnerabilidad