
Технический анализ и разработка эксплойта для CVE-2021-3156, переполнения буфера на основе кучи в Sudo, включая три рабочих эксплойта для локального повышения привилегий на основных дистрибутивах Linux.
Qualys Security Advisory
Baron Samedit: Переполнение буфера в куче в Sudo (CVE-2021-3156)
Краткое описание Анализ Эксплуатация Благодарности Временная шкала
Мы обнаружили переполнение буфера в куче в Sudo (https://www.sudo.ws/). Эта уязвимость:
эксплуатируется любым локальным пользователем (обычные пользователи и системные пользователи, sudoers и не-sudoers), без аутентификации (т.е. атакующему не нужно знать пароль пользователя);
была внесена в июле 2011 года (коммит 8255ed69) и затрагивает все устаревшие версии с 1.8.2 по 1.8.31p2 и все стабильные версии с 1.9.0 по 1.9.5p1, в их конфигурации по умолчанию.
Мы разработали три различных эксплойта для этой уязвимости и получили полные привилегии root на Ubuntu 20.04 (Sudo 1.8.31), Debian 10 (Sudo 1.8.27) и Fedora 33 (Sudo 1.9.2). Другие операционные системы и дистрибутивы, вероятно, также уязвимы.
Если Sudo выполняется для запуска команды в «командном» режиме (shell -c command):
либо через опцию -s, которая устанавливает флаг MODE_SHELL в Sudo;
либо через опцию -i, которая устанавливает флаги MODE_SHELL и MODE_LOGIN_SHELL;
то в начале функции main() в Sudo, parse_args() перезаписывает argv (строки 609-617), объединяя все аргументы командной строки (строки 587-595) и экранируя все метасимволы обратными слешами (строки 590-591):
Позже, в sudoers_policy_main(), set_cmnd() объединяет аргументы командной строки в буфер в куче "user_args" (строки 864-871) и снимает экранирование метасимволов (строки 866-867), «для целей сопоставления sudoers и ведения журнала»:
К сожалению, если аргумент командной строки заканчивается одним обратным слешем, то:
в строке 866 "from[0]" является обратным слешем, а "from[1]" — нулевым терминатором аргумента (т.е. не пробелом);
в строке 867 "from" инкрементируется и указывает на нулевой терминатор;
в строке 868 нулевой терминатор копируется в буфер "user_args", а "from" снова инкрементируется и указывает на первый символ после нулевого терминатора (т.е. за пределы аргумента);
цикл "while" в строках 865-869 читает и копирует символы за пределами границ в буфер "user_args".
Другими словами, set_cmnd() уязвим к переполнению буфера в куче, поскольку символы за пределами границ, которые копируются в буфер "user_args", не были включены в его размер (вычисленный в строках 852-853).
Однако теоретически ни один аргумент командной строки не может заканчиваться одним обратным слешем: если установлены MODE_SHELL или MODE_LOGIN_SHELL (строка 858, необходимое условие для достижения уязвимого кода), то MODE_SHELL установлен (строка 571) и parse_args() уже экранировал все метасимволы, включая обратные слеши (т.е. он экранировал каждый обратный слеш вторым обратным слешем).
Однако на практике уязвимый код в set_cmnd() и код экранирования в parse_args() окружены слегка разными условиями:
по сравнению с:
Наш вопрос, следовательно, таков: можем ли мы установить MODE_SHELL и либо MODE_EDIT, либо MODE_CHECK (чтобы достичь уязвимого кода), но не MODE_RUN по умолчанию (чтобы избежать кода экранирования)?
Ответ, по-видимому, отрицательный: если мы устанавливаем MODE_EDIT (опция -e, строка 361) или MODE_CHECK (опция -l, строки 423 и 519), то parse_args() удаляет MODE_SHELL из "valid_flags" (строки 363 и 424) и завершает работу с ошибкой, если мы указываем недопустимый флаг, такой как MODE_SHELL (строки 532-533):
Но мы нашли лазейку: если выполнить Sudo как "sudoedit" вместо "sudo", то parse_args() автоматически устанавливает MODE_EDIT (строка 270), но не сбрасывает "valid_flags", и "valid_flags" включают MODE_SHELL по умолчанию (строки 127 и 249):