Safeguard
Vulnerabilities

SQL Injection Examples: Classic and Modern Attack Patterns

Real SQL injection examples from classic login bypass to blind, time-based, and ORM-era attacks, plus how to test for each one safely.

Safeguard Research Team
Research
6 min read

A SQL injection example in its simplest form is a login box where typing ' OR '1'='1 into the username field logs you in as the first user in the table. It works because the application glued your input directly into a SQL query string, so your quote closed the intended string and your OR clause rewrote the query's logic. That same flaw, in more sophisticated shapes, still ranks among the most damaging web vulnerabilities because a single injectable parameter can expose an entire database.

Below are concrete examples spanning the classic patterns and the ones that show up in modern, ORM-heavy stacks, along with how to run a safe SQL injection test against your own code.

What does a classic SQL injection example look like?

Start with the vulnerable code, because the fix only makes sense once you see the flaw. A typical string-concatenated query:

query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'"

If an attacker submits admin'-- as the username, the query becomes:

SELECT * FROM users WHERE username = 'admin'--' AND password = ''

The -- comments out the password check, and the attacker is authenticated as admin. That is the canonical sql injection attack example: input that breaks out of the intended string and rewrites the statement.

A second classic is the UNION-based data grab. If a product page runs SELECT name, price FROM products WHERE id = <input>, an attacker supplies:

1 UNION SELECT username, password FROM users

and the page that was meant to show a product now renders credentials, as long as the column count and types line up.

How do blind and time-based examples work?

Real applications rarely echo query results and error messages straight back, so attackers switch to blind SQL injection, where they infer data one true/false question at a time.

A boolean-based blind example toggles page behavior:

1 AND (SELECT SUBSTRING(password,1,1) FROM users WHERE id=1) = 'a'

If the page renders normally, the first character is a; if it errors or returns nothing, the attacker tries b, and so on. Tedious by hand, instant with tooling.

A time-based blind example uses a database delay function as the signal when there is no visible difference at all:

1; IF (1=1) WAITFOR DELAY '0:0:5'--

or on MySQL:

1 AND SLEEP(5)

A five-second response confirms the condition is true. This is how injection gets confirmed even against APIs that return a bland 200 OK no matter what.

Do ORMs and prepared statements make you immune?

No, and this is the most common misconception in modern code. ORMs like Hibernate, Sequelize, Django ORM, and Entity Framework prevent injection only when you use their parameterized paths. The moment a developer drops to raw SQL or builds a query with string interpolation, the ORM offers no protection.

A modern sql injection example in an ORM:

// Vulnerable: user input interpolated into a raw query
db.query(`SELECT * FROM orders WHERE status = '${req.query.status}'`);

Even inside a "safe" ORM, that template literal is string concatenation. The same bug appears with Django's .raw() and .extra(), Hibernate's dynamic HQL, and any sprintf-style query builder. Second-order injection is subtler still: input is stored safely, then later concatenated into a query by a different code path that assumes stored data is trustworthy.

Because injection sinks hide behind ORM abstractions, static analysis that traces user input to a query construction site catches far more than a grep for SELECT. Pairing SAST and DAST lets you confirm both that the tainted flow exists in source and that a payload reaches the live query.

How do you run a safe SQL injection test?

A responsible sql injection test targets systems you own or are authorized to assess, in a non-production environment with a backup. The practical progression:

  1. Fuzz the input with a single quote. Submit ' in each parameter. A database error, a 500, or a changed response is a strong first signal that input reaches SQL unescaped.
  2. Try a boolean pair. Send 1 AND 1=1 then 1 AND 1=2. If the two produce different responses, the parameter is likely injectable.
  3. Confirm with a benign time delay. A SLEEP(3) or WAITFOR DELAY payload that measurably delays the response confirms execution without exfiltrating data.
  4. Automate carefully. Tools like sqlmap and DAST scanners systematize this, but run them against staging with rate limits so you do not corrupt data or trip production alarms.

Log every test, scope it to authorized targets, and never run destructive payloads like DROP against systems you do not fully control.

How do you prevent every one of these examples?

The single fix that neutralizes all the examples above is parameterized queries (prepared statements), where user input is passed as a bound value the database never interprets as code:

cursor.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))

Layer these on top:

  • Use the ORM's parameterized API and treat every raw-SQL escape hatch as a review-required change.
  • Apply least privilege so the app's database account cannot read tables or run statements it does not need.
  • Validate input types and allowlist where the shape is known (an id should be an integer), which shrinks the payload space.
  • Keep database drivers and ORM libraries patched; injection-adjacent CVEs do appear, and an SCA scan tells you which versions are actually in your build.

Safeguard's static analysis traces untrusted input from request handlers to query construction and flags the concatenation sites that ORMs hide, so a raw-SQL escape hatch shows up as a specific file and line rather than something you find after an incident.

FAQ

What is the simplest SQL injection example to understand?

A login bypass. Entering ' OR '1'='1 (or admin'--) into a username field that concatenates input into a query rewrites the SQL so the password check is always true or is commented out. It illustrates the core mechanic: input escaping its string context and changing the query's logic.

Are prepared statements enough to stop SQL injection?

Yes, for the queries that actually use them. Parameterized queries send input as data, not code, so the classic, UNION, and blind examples all fail. The catch is coverage: one raw concatenated query anywhere reopens the risk, which is why teams pair prepared statements with static analysis that finds the stragglers.

How can I test my own app for SQL injection safely?

Work in a non-production environment with a backup, submit a single quote to each parameter to spot errors, then use benign boolean and time-delay payloads to confirm without exfiltrating or destroying data. Automated tools like DAST scanners and sqlmap help, but scope them to systems you are authorized to test.

Does using an ORM mean I am safe from SQL injection?

Not automatically. ORMs protect you only on their parameterized query paths. Raw SQL, string-interpolated query builders, and methods like .raw() or dynamic HQL bypass that protection entirely. Treat every raw-query escape hatch inside an ORM as a potential injection sink.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.