Vulnerabilities

SQL Injection in 2024: Why It Still Works

SQL injection attacks remain devastatingly effective. Learn why this 25-year-old vulnerability persists and how to eliminate it from your codebase.

Hacker Bot Team

Security Team

SQL injection attack visualization

SQL injection was first documented in 1998. Twenty-five years later, it’s still in the OWASP Top 10 and responsible for major breaches. Why can’t we kill this bug?

The Anatomy of SQL Injection

At its core, SQL injection is about breaking context. When user input crosses the boundary from “data” to “code,” bad things happen.

// The classic vulnerable pattern
const query = `SELECT * FROM users WHERE email = '${email}'`;

// If email is: ' OR '1'='1
// Query becomes: SELECT * FROM users WHERE email = '' OR '1'='1'
// Result: Returns ALL users

Why It Persists

1. Legacy Code

Organizations have millions of lines of code written before secure coding was mainstream. Refactoring it all is expensive and risky.

2. Framework Misuse

Modern frameworks provide protection, but developers bypass it:

// Framework provides safe queries
User.where({ email: userEmail }); // Safe

// But developer goes raw for "performance"
db.query(`SELECT * FROM users WHERE email = '${userEmail}'`); // Vulnerable

3. Dynamic Query Requirements

Sometimes business logic seems to require dynamic queries:

// "We need dynamic column sorting"
const query = `SELECT * FROM products ORDER BY ${sortColumn} ${sortDirection}`;

The solution isn’t raw SQL—it’s whitelisting:

const allowedColumns = ['name', 'price', 'created_at'];
const allowedDirections = ['ASC', 'DESC'];

if (!allowedColumns.includes(sortColumn)) {
  throw new Error('Invalid sort column');
}
if (!allowedDirections.includes(sortDirection)) {
  throw new Error('Invalid sort direction');
}

Types of SQL Injection

In-Band SQLi

The classic: results appear directly in the response.

Blind SQLi

No direct output, but you can infer information:

  • Boolean-based: Different responses for true/false
  • Time-based: Delays indicate successful injection
-- Time-based blind injection
SELECT * FROM users WHERE id = 1 AND SLEEP(5)
-- If page takes 5 seconds, injection worked

Out-of-Band SQLi

Data exfiltrated through secondary channels (DNS, HTTP).

Complete Prevention

1. Parameterized Queries (Always)

// Node.js with prepared statements
const result = await db.query(
  'SELECT * FROM users WHERE email = $1 AND status = $2',
  [email, status]
);

2. ORM Usage (Correctly)

// Sequelize - safe by default
const user = await User.findOne({
  where: { email: userEmail }
});

// Still vulnerable if using raw:
await sequelize.query(`SELECT * FROM users WHERE email = '${email}'`);

3. Stored Procedures (With Caution)

Stored procedures can help, but they’re not immune:

-- STILL VULNERABLE
CREATE PROCEDURE GetUser(IN userEmail VARCHAR(255))
BEGIN
  SET @sql = CONCAT('SELECT * FROM users WHERE email = "', userEmail, '"');
  PREPARE stmt FROM @sql;
  EXECUTE stmt;
END

4. Input Validation (Defense in Depth)

Validation shouldn’t be your only defense, but it helps:

const emailSchema = z.string().email().max(254);
const validatedEmail = emailSchema.parse(userInput);

Testing for SQL Injection

Manual testing patterns:

  • Single quote: '
  • Double quote: "
  • SQL comments: --, #, /* */
  • Boolean tests: ' OR '1'='1, ' AND '1'='2
  • Time delays: '; WAITFOR DELAY '0:0:5'--

Automated tools like Hacker Bot test these patterns and hundreds more against every input vector in your application.

Conclusion

SQL injection persists because security is hard and legacy code is everywhere. The fix is simple—use parameterized queries everywhere—but the discipline to maintain that standard is the real challenge.


Hacker Bot automatically tests for SQL injection across your entire attack surface. Find out what you’re missing.

Related Articles