
CVE-2023-4911 (Looney Tunables) analysis report and Docker reproduction lab
| Item | Conteúdo |
|---|---|
| CVE ID | CVE-2023-4911 |
| Tipo de ataque | Estouro de buffer heap → Escalonamento local de privilégios (Local Privilege Escalation) |
| CVSS 3.1 | 7.8 (Alto) |
| Data de divulgação | 2023-10-03 |
| Ponto vulnerável | Analisador GLIBC_TUNABLES do carregador dinâmico do glibc (ld.so) |
| Versões vulneráveis | glibc 2.34~2.38 |
CVE-2023-4911 é uma vulnerabilidade de estouro de buffer heap que ocorre quando o carregador dinâmico da GNU C Library (glibc) analisa a variável de ambiente GLIBC_TUNABLES. Um atacante pode usar esse estouro para manipular o caminho de busca de bibliotecas (RPATH) do carregador dinâmico, fazendo com que, quando um binário SUID root (como su, sudo etc.) seja executado, uma biblioteca compartilhada maliciosa preparada pelo atacante seja carregada em seu lugar, permitindo a execução de código arbitrário com privilégios de root. Como o glibc é um componente essencial de praticamente todas as principais distribuições Linux, essa vulnerabilidade afetou a maioria das distribuições baseadas em glibc lançadas a partir de abril de 2021.
while (true)
{
char *name = p;
size_t len = 0;
/* 이름(name) 길이 찾기 */
while (p[len] != '=' && p[len] != ':' && p[len] != '\0')
len++;
/* '=' 없이 끝나면 종료 */
if (p[len] == '\0')
{
if (__libc_enable_secure)
tunestr[off] = '\0';
return;
}
/* ':'를 먼저 만나면 잘못된 항목 */
if (p[len] == ':')
{
p += len + 1;
continue;
}
/* '='를 만났으므로 value 시작으로 이동 */
p += len + 1;
/* 원본 문자열에서 value 계산 */
char *value = &valstring[p - tunestr];
len = 0;
/* value 길이 찾기 */
while (p[len] != ':' && p[len] != '\0')
len++;
...
/* tunestr에 복사 */
...
if (p[len] != '\0')
p += len + 1;
}
__tunables_init() encontra GLIBC_TUNABLES na lista de variáveis de ambiente.tunables_strdup() aloca um buffer com __minimal_malloc() e copia a string original (neste ponto, o malloc é uma implementação muito inicial, ainda não totalmente inicializada).parse_tunables() percorre esse buffer usando : (dois-pontos) como separador, separando cada par key=value e atribuindo o valor ao tunable correspondente.parse_tunables() processa um tunable na ordem: análise do nome → mover p → análise do valor → mover p. Em uma entrada normal, após processar todo o valor, p é movido para o início do próximo tunable e o próximo item é analisado.
No entanto, quando uma entrada no formato name=name=value é fornecida, como:
GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=AAAA...(string longa)
No primeiro processo de análise, todo glibc.malloc.mxfast=AAAA... é reconhecido como um único valor e copiado para o buffer tunestr. Em seguida, como não há dois-pontos (:) separando o próximo tunable após o valor, o ponteiro de análise (p) não avança para o próximo item, e acaba apontando novamente para o início do valor já copiado.
O problema é que esse valor em si tem o formato name=value. Na próxima iteração, o analisador o reconhece erroneamente como um novo tunable e grava dados duplicados no buffer. Como tunestr foi alocado apenas com o tamanho da string original, essa gravação duplicada causa um estouro de buffer heap.
O estouro de buffer heap ocorrente sobrescreve áreas de heap adjacentes alocadas consecutivamente por __minimal_malloc(). O atacante pode usar isso para modificar o ponteiro l_info[DT_RPATH] da estrutura interna do carregador dinâmico (link_map) para um endereço de pilha controlado pelo atacante.
Nessa área da pilha, uma struct Elf64_Dyn previamente manipulada é posicionada, e essa estrutura define um diretório desejado pelo atacante como o novo caminho de busca de bibliotecas (RPATH). Como resultado, o ld.so carregará prioritariamente uma biblioteca compartilhada preparada pelo atacante em vez das bibliotecas normais do sistema.
GLIBC_TUNABLES no formato name=name=value.su).parse_tunables() do ld.so.l_info[DT_RPATH] do link_map é manipulado para apontar para um endereço de pilha onde uma struct Elf64_Dyn falsa preparada pelo atacante está posicionada.libc.so.6 maliciosa preparada pelo atacante.libc.so.6 maliciosa é executado com privilégios de root, realizando setuid(0), setgid(0) e execução de /bin/sh.O PoC utiliza um método de força bruta que executa repetidamente execve() até que o layout de memória desejado seja formado, devido ao efeito da randomização de endereços (ASLR). Portanto, o sucesso e o tempo necessário do ataque podem variar conforme o ambiente, geralmente exigindo centenas a milhares de tentativas.
Primeiro, clone o conteúdo do git para um diretório
git clone https://github.com/baeseungwon1010/CVE-2023-4911

Construa a imagem docker com o comando abaixo
cd C* && docker compose run --rm cve-2023-4911-lab

Após entrar no container, execute o código de exploração
cd /home/student/exploit && ./exp
Após executar e aguardar, é possível ver que o usuário comum mudou para sudo(0)

Atualize o glibc para uma versão superior às versões vulneráveis. Após a atualização, se possível, reinicie/reinicie para garantir que nenhuma versão anterior do glibc permaneça na memória. Caso a correção imediata não seja possível, uma medida temporária é remover processos SUID, SGID desnecessários.
su, permitindo obter um shell root sem verificação de senha.