
Скрипты воспроизведения и PoC для CVE-2026-96512 (SudoTimeWarp), где TZ вызывающего сдвигает окна NOTBEFORE/NOTAFTER в sudoers, а также проверки мер защиты.
sudo: TZ вызывающего решает NOTBEFORE/NOTAFTER
Материалы для воспроизведения SudoTimeWarp (CVE-2026-96512). Правило sudoers, в метке времени Date_Spec которого опущен завершающий Z, преобразуется через mktime(), а тот при каждом вызове заново читает getenv("TZ").
Поскольку sudo имеет бит setuid-root и environ вызывающего без изменений проходит через execve(), непривилегированный вызывающий сам выбирает часовой пояс, в котором оценивается его собственное окно действия.
| Название | SudoTimeWarp |
| CVE | CVE-2026-96512 |
| CVSS v3.1 | AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H — 7.8 High |
| CWE | CWE-863 (зарегистрировано); механизм — CWE-807 |
| Затронуто | sudo 1.8.20 по 1.9.17p2, а также main до 1820a349 |
| Исправлено в | 1820a349 (2026-08-29) |
| Сдвиг | до 24 ч 59 м 59 с в каждую сторону; общий интервал 49 ч 59 м 58 с |
Базовая проблема — влияние TZ на NOTBEFORE/NOTAFTER — была сообщена приватно
XlabAI Team of Tencent Xuanwu Lab, Atuin Automated Vulnerability Discovery
Engine и Guannan Wang, Zhanpeng Liu и Guancheng Li, и упомянута в коммите
db669167c (2026-03-14).
SudoTimeWarp (CVE-2026-96512) — это обнаружение того, что db669167c неполон. Устанавливаемая им защита
покрывает кэш часовых поясов glibc — а вместе с ним и метки времени в журналах — но не
доходит до mktime() в gentime.c:156, где принимается решение об авторизации. На дереве, которое
уже содержит этот коммит, сдвиг окна полностью воспроизводится. Сопровождающий фиксирует этот момент в сообщении 1820a349:
предыдущее изменение «было неэффективным, поскольку функция mktime() заново читает переменную окружения TZ при каждом вызове»
Правило sudoers, которое (1) предоставляет вызывающему команду, (2) содержит NOTBEFORE= или
NOTAFTER= и (3) записывает метку времени без суффикса Z и без явного
смещения.
Пункт (3) не экзотичен: руководство sudoers описывает форму без суффикса как поддерживаемое
расширение, приводит 20151201235900 как одну из четырёх примерных меток времени
(docs/sudoers.man.in:1820), и эта форма встречается в собственном регрессионном корпусе проекта
(plugins/sudoers/regress/testsudoers/test13.sh).
Всегда в одноразовом контейнере или ВМ. Каждый скрипт здесь перезаписывает /etc/sudoers.
Они делают резервную копию и восстанавливают его, но ошибка там запрёт вас на реальной машине —
скрипты отказываются работать вне контейнера, если не передать --i-know.
docker build -t sudotimewarp .
docker run --rm -it sudotimewarp
Или без сборки образа:
docker run --rm -it -v "$PWD:/m" debian:trixie bash -c \
'apt-get update >/dev/null && apt-get install -y sudo >/dev/null && bash /m/poc.sh'
Ожидаемый вывод на уязвимой сборке:
=== probes ===
rule valid, no TZ : uid=0(root) gid=0(root) groups=0(root)
rule expired, no TZ : sudo: a password is required
rule expired, TZ=UTC : sudo: a password is required
rule expired, TZ=XXX24 : uid=0(root) gid=0(root) groups=0(root)
expired + 'Z', TZ=XXX24 : sudo: a password is required
XXX — произвольная трёхбуквенная аббревиатура часового пояса, а 24 — смещение POSIX. Никакой
файл не задействован и не должен существовать — эксплуатируемый канал — только встроенная строка
POSIX. Форма с tzfile (TZ=:/tmp/evil.tz и варианты) отклоняется защитой glibc
__libc_enable_secure при setuid и даёт ровно 0 с сдвига, что и отличает это от CVE-2014-9680.
Строки 1–3 — контрольные, и они важны: ошибка разбора дала бы тот же ALLOW, что и баг.
| Строка | Что проверяет |
|---|---|
| 1 | Правило вообще работает внутри своего окна |
| 2 | Отказ при отсутствии строки 4 действительно исходит от NOTAFTER |
| 3 | Установка TZ сама по себе не причина — TZ=UTC решает так же, как отсутствие TZ |
| 4 | Баг: истёкшее правило выполняется от root |
| 5 | Граница: с документированным Z выбирается ветка timegm() и всё падает |
poc.sh завершается с 0, когда уязвимо, 1 — когда нет, 2 — когда контрольные проверки не выполнились.
poc.sh использует NOPASSWD, чтобы работать неинтерактивно. Это не условие
бага. Разделённые скрипты явно показывают границу привилегий — часть 1 делает только то, что
администратор делает законно, часть 2 выполняется как непривилегированный пользователь и не использует
никаких привилегий:
bash repro-admin.sh escalation # as root: writes the policy
su - poc -c 'bash /poc/repro-attacker.sh'
bash repro-admin.sh --cleanup
repro-admin.sh принимает четыре сценария:
| Сценарий | Политика |
|---|---|
expired (по умолчанию) | одно правило, NOTAFTER на час в прошлом, без зоны |
valid | то же правило ещё внутри своего окна — контроль |
expired-z | то же истёкшее правило с документированным Z — контроль, незатронутая форма |
escalation | узкое постоянное разрешение плюс истёкшее широкое — форма, которую реально принимает разрешение на обслуживание или аварийный доступ |
С правилом, требующим пароль, вызывающий всё равно аутентифицируется через PAM, и неверный пароль по-прежнему не проходит. Это не обход аутентификации — сдвигается решение об авторизации.
PR:L, а не PR:N.TZ дают отказ.C:L/I:N/A:N). Вектор 7.8 оценивает случай, когда датированное правило
шире постоянного доступа вызывающего.Ограничение ~25 ч относится к окну доступа, а не к длительности влияния: одного успешного использования внутри него достаточно, чтобы установить персистентность, переживающую это окно.
Добавьте Z к каждой метке времени NOTBEFORE/NOTAFTER — это принудительно выбирает ветку timegm().
grep -rE 'NOT(BEFORE|AFTER)=' /etc/sudoers /etc/sudoers.d/
Не проводите аудит с помощью sudo -l. Он форматирует каждый Date_Spec через gmtime() и всегда
добавляет Z (plugins/sudoers/display.c:229), поэтому правило, записанное как
NOTAFTER=20260827221423, отображается как NOTAFTER=20260827221423Z. Вывод нормализует
именно ту деталь, которая всё решает. cvtsudoers и fmtsudoers ведут себя так же.
Читайте /etc/sudoers напрямую.
1820a349: https://github.com/sudo-project/sudo/commit/1820a349687522f51023d1ae5925125f59679a8cСообщено сопровождающему 2026-08-28, без крайнего срока. Патч-кандидат пришёл в тот же день; публичное исправление вышло 2026-08-29. CVE назначен Red Hat, действующим как CNA-LR, и опубликован 2026-09-23. Эмбарго нет: всё здесь публично с момента коммита с исправлением.
SudoTimeWarp / CVE-2026-96512: Ermenson Junior, независимое исследование, зафиксировано Red Hat как «Independent security research». Первоначальное сообщение о лежащей в основе проблеме принадлежит XlabAI Team of Tencent Xuanwu Lab, Atuin Automated Vulnerability Discovery Engine и Guannan Wang, Zhanpeng Liu и Guancheng Li.
Опубликовано для защитного применения: проверки, затронут ли хост, и валидации смягчения с помощью Z.