Sql Injection Blind in Adonisjs
How Sql Injection Blind Manifests in Adonisjs
Blind SQL injection in Adonisjs applications often occurs when developers use string concatenation or template literals to build SQL queries with user-controlled input, especially in route handlers or controller methods where input validation is omitted. Unlike error-based SQLi, blind SQLi does not return database errors directly; instead, attackers infer information through boolean-based or time-based responses. In Adonisjs, this commonly appears in custom query builders using the Database provider when query.where() or query.andWhere() methods receive unsanitized input.
For example, a route that retrieves user profile data based on a user-provided ID without proper validation might look like this:
// controllers/UserController.js
async show({ params, response }) {
const userId = params.id
const query = `SELECT * FROM users WHERE id = ${userId}`
const user = await Database.raw(query)
return response.json(user.rows[0])
}
This code is vulnerable because the params.id value is directly interpolated into the SQL string. An attacker can send payloads like 1 AND (SELECT SLEEP(5)) to trigger a time-delay blind SQLi, causing the response to delay by 5 seconds if the condition is true. Since Adonisjs uses Lucid ORM by default, vulnerabilities are less common when using User.find() or User.query() with parameter binding, but raw queries or manual string building in services, middleware, or custom validators can reintroduce risk.
Another vector involves search functionality where user input is used in LIKE clauses:
// routes/start.js
Route.get('/search', async ({ request, response }) => {
const term = request.input('q')
const results = await Database.from('posts')
.where('title', 'like', `%${term}%`)
.select()
return response.json(results)
})
Although the Lucid query builder uses parameter binding internally, if the term is incorrectly processed or if a developer mistakenly uses raw() or whereRaw() with concatenation, it becomes exploitable. For instance, using whereRaw('title LIKE \'%${term}%\'') would reintroduce the vulnerability. Blind SQLi via boolean responses can be detected by observing differences in response length, status code, or timing when injecting payloads like ' OR '1'='1 versus ' OR '1'='2.
Adonisjs-Specific Remediation
Remediating blind SQL injection in Adonisjs applications involves eliminating unsafe SQL construction and leveraging the framework’s built-in security mechanisms. The primary defense is to avoid string concatenation or template literals when building SQL queries and instead use parameterized queries via the Lucid ORM or the Database query builder with proper binding.
Adonisjs provides safe defaults through its Lucid ORM, which automatically uses parameter binding for all query methods. Developers should prefer ORM methods like User.find(), User.query(), or Database.table() with where() clauses that accept values as separate parameters.
For example, the vulnerable controller method from earlier should be rewritten as:
// controllers/UserController.js - Fixed
import User from 'App/Models/User'
async show({ params, response }) {
const user = await User.find(params.id)
if (!user) {
return response.notFound()
}
return response.json(user)
}
Or, if using the query builder directly:
// Using Database query builder with safe binding async show({ params, response }) { const user = await Database.from('users') .where('id', params.id) .first() return response.json(user) }In both cases, the
params.idvalue is treated as a data parameter, not executable SQL, preventing injection. ForLIKEclauses in search functionality, the same principle applies:// routes/start.js - Fixed Route.get('/search', async ({ request, response }) => { const term = request.input('q') const results = await Database.from('posts') .where('title', 'like', `%${term}%`) // Safe: term is bound as parameter .select() return response.json(results) })Note that even though the
%wildcards are concatenated to thetermvalue, the entire string is still passed as a single bound parameter, so the query remains safe. The danger arises only when the user input breaks out of the parameter context — such as when usingwhereRaw()orraw()with concatenation.If raw SQL is unavoidable (e.g., for complex queries), developers must use parameter binding explicitly:
// Safe use of raw SQL with binding const users = await Database.raw( 'SELECT * FROM users WHERE id = ? AND status = ?', [params.id, 'active'] )Never use string interpolation with
Database.raw(). Additionally, validate and sanitize input using Adonisjs’s built-in validator:// Using validator to ensure ID is a number import { schema, rules } from '@ioc:Adonis/Core/Validator' async show({ params, response }) { await request.validate({ schema: schema.create({ id: schema.number([rules.unsigned()]) }) }) const user = await User.find(params.id) return response.json(user) }By combining ORM usage, parameter binding, and input validation, Adonisjs applications can effectively mitigate blind SQL injection risks. middleBrick helps verify these fixes by rescanning endpoints and confirming that previously vulnerable inputs no longer produce exploitable responses.