
Образовательный 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) │
└─────────────────────────────────────┘