Back to blog
webSQL injectiondatabase

SQL injection: concrete examples and modern defenses

Published on 2026-04-038 min readFlorian

SQL injection is not dead

Despite 25 years of awareness, SQL injection remains in the top 3 most exploited vulnerabilities. ORMs have reduced the attack surface but haven't eliminated the problem. Every time a developer writes a raw query, concatenates user input into SQL, or uses an ORM in unsafe mode, the risk comes back.

How it works

The attacker inserts SQL into a field that will be incorporated into a server-side query. If the value isn't parameterized, the SQL engine interprets it as code.

Basic example: the login form executes SELECT * FROM users WHERE email = '[input]' AND password = '[input]'. The attacker enters admin'-- as the email. The query becomes SELECT * FROM users WHERE email = 'admin'--' AND password = ''. The SQL comment -- skips the password check.

The variants

Union-based

The attacker uses UNION SELECT to extract data from other tables. If the original query returns 3 columns, the attacker injects ' UNION SELECT username, password, email FROM users-- to retrieve every user's credentials.

Blind (boolean)

When the application doesn't return results directly, the attacker asks yes/no questions. ' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE id=1)='a'-- tests whether the first character of the password is a. Automated, this extracts the database character by character.

Time-based

Same principle as blind, but based on response time. ' AND IF(1=1, SLEEP(5), 0)-- causes a 5-second delay if the condition is true. Slower but works even when responses are identical.

Second-order

The injection isn't exploited immediately. The malicious value is stored (e.g., in a user profile), then used later in another unparameterized query. Harder to detect because input and output are separated.

What ORMs don't protect

  • DB::raw() in Laravel: any raw expression reintroduces the risk
  • .extra() in Django: injections in additional WHERE clauses
  • $where in MongoDB: server-side JavaScript evaluation
  • Dynamic ORDER BY clauses: often built by concatenation even in ORMs
  • Dynamic column names: not parameterizable in most drivers
  • Modern defenses

  • Parameterized queries: the only reliable defense. Always use your driver's placeholders (? or $1). Never concatenate.
  • Allowlists for structural elements: for column names or sort directions, validate against an explicit list of permitted values.
  • Least privilege principle: the application's database user should only have the permissions it needs. No DROP, no FILE, no access to system tables.
  • WAF as a complement: a WAF can block obvious injection patterns, but never substitute it for query parameterization. WAF bypasses are publicly documented.
  • Automated testing: integrate SQLMap or fuzzing tests in your CI/CD to catch regressions.
  • What we find in audits

    At CleanIssue, we find SQL injection in roughly 15% of the applications we audit. The majority involve search queries with dynamic sorting or CSV exports with custom filters. Request your audit call to check your most exposed endpoints.

    Related articles

    Three adjacent analyses to keep exploring the same attack surface.

    Sources

    Written by Florian
    Reviewed on 2026-04-03

    Editorial analysis based on official vendor, project, and regulator documentation.

    Related services

    If this topic maps to a real risk in your stack, these are the most relevant CleanIssue audits.

    Need an external review of your HR SaaS?

    Share your product, stack, and client context. We will come back with the right review scope.

    Discuss your audit