
Scripts de reprodução e PoC para CVE-2026-96512 (SudoTimeWarp), em que o TZ do chamador desloca as janelas NOTBEFORE/NOTAFTER do sudoers, além de verificações de mitigação.
sudo: o TZ do chamador decide NOTBEFORE/NOTAFTER
Material de reprodução para SudoTimeWarp (CVE-2026-96512). Uma regra do sudoers cujo timestamp Date_Spec omite
o Z final é convertida por mktime(), que relê getenv("TZ") a cada chamada.
Como o sudo é setuid-root e o environ do chamador atravessa execve() intacto, o
chamador sem privilégios escolhe o fuso horário no qual sua própria janela de validade é avaliada.
| Nome | 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 Alto |
| CWE | CWE-863 (registrado); o mecanismo é CWE-807 |
| Afetado | sudo 1.8.20 até 1.9.17p2, e main antes de 1820a349 |
| Corrigido em | 1820a349 (2026-08-29) |
| Deslocamento | até 24 h 59 m 59 s por direção; intervalo total 49 h 59 m 58 s |
A questão base — TZ influencia NOTBEFORE/NOTAFTER — foi relatada em privado pela
XlabAI Team of Tencent Xuanwu Lab, pela Atuin Automated Vulnerability Discovery
Engine, e por Guannan Wang, Zhanpeng Liu e Guancheng Li, e creditada no commit
db669167c (2026-03-14).
SudoTimeWarp (CVE-2026-96512) é a descoberta de que db669167c está incompleto. A proteção que ele
instala cobre o cache de fuso horário da glibc — e com ele os timestamps de log — mas não
alcança o mktime() em gentime.c:156, onde a autorização é decidida. Em uma árvore que
já contém esse commit, o deslocamento da janela permanece totalmente reproduzível. O mantenedor
registra o ponto na mensagem de 1820a349:
a alteração anterior "não foi eficaz, pois a função mktime() relê a variável de ambiente TZ a cada vez que é chamada"
Uma regra do sudoers que (1) concede um comando ao chamador, (2) contém NOTBEFORE= ou
NOTAFTER=, e (3) escreve o timestamp sem o sufixo Z e sem um deslocamento
explícito.
O item (3) não é exótico: o manual do sudoers documenta a forma sem sufixo como uma extensão
suportada, imprime 20151201235900 como um de seus quatro timestamps de exemplo
(docs/sudoers.man.in:1820), e a forma aparece no próprio corpus de regressão do projeto
(plugins/sudoers/regress/testsudoers/test13.sh).
Sempre em um contêiner ou VM descartável. Todo script aqui reescreve /etc/sudoers.
Eles fazem backup e restauram, mas um erro ali bloqueia seu acesso a uma máquina real — os
scripts se recusam a rodar fora de um contêiner a menos que você passe --i-know.
docker build -t sudotimewarp .
docker run --rm -it sudotimewarp
Ou sem construir uma imagem:
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'
Saída esperada em uma build afetada:
=== 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 é uma abreviação de fuso horário arbitrária de três letras e 24 é um deslocamento POSIX. Nenhum
arquivo está envolvido e nenhum precisa existir — o canal explorável é apenas a string POSIX
inline. A forma tzfile (TZ=:/tmp/evil.tz e variantes) é recusada pela guarda
__libc_enable_secure da glibc sob setuid e mede exatamente 0 s de deslocamento, o que
separa isto do CVE-2014-9680.
As linhas 1–3 são controles, e importam: um erro de parsing produziria o mesmo ALLOW que o bug.
| Linha | Verifica |
|---|---|
| 1 | A regra funciona de todo dentro de sua janela |
| 2 | A negação na ausência da linha 4 realmente vem de NOTAFTER |
| 3 | Definir TZ não é a causa em si — TZ=UTC decide como nenhum TZ decide |
| 4 | O bug: a regra expirada executa como root |
| 5 | A fronteira: com o Z documentado, o ramo timegm() é tomado e ela morre |
poc.sh sai com 0 quando afetado, 1 quando não, 2 quando os controles não se mantiveram.
poc.sh usa NOPASSWD para poder rodar de forma não interativa. Isso não é uma condição do
bug. Os scripts divididos mostram a fronteira de privilégio explicitamente — a parte 1 faz apenas o que
um administrador legitimamente faz, a parte 2 roda como o usuário sem privilégios e não usa nenhum
privilégio de qualquer tipo:
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 aceita quatro cenários:
| Cenário | Política |
|---|---|
expired (padrão) | uma regra, NOTAFTER uma hora no passado, sem zona |
valid | a mesma regra ainda dentro de sua janela — controle |
expired-z | a mesma regra expirada com o Z documentado — controle, forma não afetada |
escalation | uma concessão permanente estreita mais uma ampla expirada — a forma que uma concessão de manutenção ou break-glass realmente assume |
Com uma regra que exige senha, o chamador ainda se autentica via PAM, e uma senha errada ainda falha. Isto não é um bypass de autenticação — o que se move é a decisão de autorização.
PR:L, não PR:N.TZ negam.C:L/I:N/A:N). O vetor 7.8 pontua o caso em que a regra datada é
mais ampla que o acesso permanente do chamador.O limite de ~25 h se aplica à janela de acesso, não à duração do impacto: um uso bem-sucedido dentro dela é suficiente para estabelecer persistência que sobrevive à janela.
Anexe Z a todo timestamp NOTBEFORE/NOTAFTER — isso força o ramo timegm().
grep -rE 'NOT(BEFORE|AFTER)=' /etc/sudoers /etc/sudoers.d/
Não audite com sudo -l. Ele formata todo Date_Spec através de gmtime() e sempre
anexa um Z (plugins/sudoers/display.c:229), então uma regra escrita
NOTAFTER=20260827221423 é exibida como NOTAFTER=20260827221423Z. A saída normaliza
exatamente o detalhe que a decide. cvtsudoers e fmtsudoers se comportam da mesma forma.
Leia /etc/sudoers diretamente.
1820a349: https://github.com/sudo-project/sudo/commit/1820a349687522f51023d1ae5925125f59679a8cRelatado ao mantenedor em 2026-08-28, sem prazo. Um patch candidato voltou no mesmo dia; a correção pública chegou em 2026-08-29. CVE atribuído pela Red Hat, atuando como CNA-LR, e publicado em 2026-09-23. Não há embargo: tudo aqui é público desde o commit de correção.
SudoTimeWarp / CVE-2026-96512: Ermenson Junior, pesquisa independente, registrado pela Red Hat como "Independent security research". O relato original da questão subjacente pertence à XlabAI Team of Tencent Xuanwu Lab, à Atuin Automated Vulnerability Discovery Engine, e a Guannan Wang, Zhanpeng Liu e Guancheng Li.
Publicado para uso defensivo: verificar se um host é afetado, e validar a mitigação
do Z.