Sequelize raw query injection

sequelize.query() executes raw SQL against the configured dialect (PostgreSQL, MySQL/MariaDB, SQLite, SQL Server). It accepts replacements and bind options for safe parameterization, but when application code concatenates request data into the SQL string, none of that applies and the call is a SQL injection sink.

Scope. For authorized penetration tests, CTF labs, and code review of systems you own or are contracted to assess.

Vulnerable pattern

// name from the request
const rows = await sequelize.query(
  "SELECT * FROM users WHERE name = '" + name + "'"
);
await sequelize.query(`SELECT * FROM users WHERE id = ${req.query.id}`);

The safe forms are { replacements: { name } } with :name, or { bind: [name] } with $1; the injectable form interpolates into the string.

Exploitation

Standard injection in the value's context. Numeric id:

1 OR 1=1
0 UNION SELECT id, username, password FROM users

Quoted name:

' OR '1'='1
' UNION SELECT username, password, NULL FROM users --

Dialect matters for weaponization. MSSQL (tedious) runs stacked queries (; UPDATE/; INSERT after the SELECT), and PostgreSQL via pg can execute multiple statements in a single simple query. The MySQL mysql2 driver disables multiple statements by default, so ;-stacked payloads only fire when the application explicitly sets multipleStatements: true; against a default MySQL-backed app, fall back to UNION and boolean/time-based inference. Use SLEEP()/pg_sleep() for time-based blind and dialect string functions for substring extraction. When query() is called with { type: QueryTypes.SELECT } the rows are returned directly, making UNION read straightforward.

References

Cookie Consent

We use cookies to enhance your experience. Learn more