
C-based proof-of-concept exploit для CVE-2021-4034 (PwnKit), позволяющий повысить локальные привилегии через out-of-bounds write в pkexec. Включает инструкции по сборке и описание уязвимости.
CVE-2021-4034
debian@debian:~/PwnKit-Exploit$ make
cc -Wall exploit.c -o exploit
debian@debian:~/PwnKit-Exploit$ whoami
debian
debian@debian:~/PwnKit-Exploit$ ./exploit
Current User before execute exploit
hacker@victim$whoami: debian
Exploit written by @luijait (0x6c75696a616974)
[+] Enjoy your root if exploit was completed succesfully
root@debian:/home/debian/PwnKit-Exploit# whoami
root
root@debian:/home/debian/PwnKit-Exploit#
| Команда | Использование |
|---|---|
sudo chmod 0755 pkexec | Исправление CVE 2021-4034 |
git clone https://github.com/luijait/PwnKit-Exploit
cd PwnKit-Exploit
make
./exploit
whoami
| Команда | Назначение |
|---|---|
make clean | Очистка сборки для тестирования изменённого кода |
Основано на blog.qualys.com
Начало функции main() в pkexec обрабатывает аргументы командной строки (строки 534-568) и ищет программу для выполнения, если её путь не абсолютный, в каталогах переменной окружения PATH (строки 610-640):
435 main (int argc, char *argv[])
436 {
...
534 for (n = 1; n < (guint) argc; n++)
535 {
...
568 }
...
610 path = g_strdup (argv[n]);
...
629 if (path[0] != '/')
630 {
...
632 s = g_find_program_in_path (path);
...
639 argv[n] = path = s;
640 }
К сожалению, если количество аргументов командной строки argc равно 0 – то есть если список аргументов argv, который мы передаём в execve(), пуст, например {NULL} – то argv[0] равен NULL. Это терминатор списка аргументов. Следовательно:
в строке 534 целое n навсегда устанавливается в 1; в строке 610 указатель path читается за пределами границ из argv[1]; в строке 639 указатель s записывается за пределами границ в argv[1]. Но что именно читается и записывается в этот вышедший за границы argv[1]?
Чтобы ответить на этот вопрос, нужно сделать небольшое отступление. Когда мы выполняем execve() новую программу, ядро копирует наши аргументы, строки окружения и указатели (argv и envp) в конец стека новой программы; например:
|---------+---------+-----+------------|---------+---------+-----+------------|
| argv[0] | argv[1] | ... | argv[argc] | envp[0] | envp[1] | ... | envp[envc] |
|----|----+----|----+-----+-----|------|----|----+----|----+-----+-----|------|
V V V V V V
"program" "-option" NULL "value" "PATH=name" NULL
Очевидно, что поскольку указатели argv и envp расположены в памяти последовательно, если argc равно 0, то вышедший за границы argv[1] на самом деле является envp[0] – указателем на нашу первую переменную окружения, "value". Следовательно:
В строке 610 путь к программе для выполнения читается за пределами границ из argv[1] (то есть envp[0]) и указывает на "value"; В строке 632 этот путь "value" передаётся в g_find_program_in_path() (поскольку "value" не начинается с косой черты, строка 629); Затем g_find_program_in_path() ищет исполняемый файл с именем "value" в каталогах нашей переменной PATH; Если такой исполняемый файл находится, его полный путь возвращается в функцию main() pkexec (строка 632); Наконец, в строке 639 этот полный путь записывается за пределами границ в argv[1] (то есть envp[0]), тем самым перезаписывая нашу первую переменную окружения. Итак, говоря более точно:
Если наша переменная PATH равна "PATH=name" и каталог "name" существует (в текущем рабочем каталоге) и содержит исполняемый файл с именем "value", то указатель на строку "name/value" записывается за пределами границ в envp[0]; ИЛИ
Если наш PATH равен "PATH=name=.", и каталог "name=." существует и содержит исполняемый файл с именем "value", то указатель на строку "name=./value" записывается за пределами границ в envp[0]. Другими словами, эта запись за пределами границ позволяет нам повторно ввести «небезопасную» переменную окружения (например, LD_PRELOAD) в окружение pkexec. Эти «небезопасные» переменные обычно удаляются (библиотекой ld.so) из окружения SUID-программ до вызова функции main(). Мы воспользуемся этим мощным примитивом в следующем разделе.
Примечание в последнюю минуту: polkit также поддерживает операционные системы, отличные от Linux, такие как Solaris и *BSD, но мы не исследовали их эксплуатацию. Однако отметим, что OpenBSD не эксплуатируется, поскольку его ядро отказывается выполнять execve() программу, если argc равен 0.