What is SQL injection and how does it work?

What is SQL injection and how does it work?

SQL injection is one of the oldest bugs on the web, and it still shows up in real applications today. At its core, SQL injection happens when an application builds a database query by gluing user input directly into the query text, so an attacker can send input that changes what the query means. This post explains what SQL injection is and how it works, with a small login example you can read in a minute.

What is SQL injection in plain terms

A web app talks to its database using SQL, a language for asking questions like “find the user with this email.” When the app writes that question, it often needs to drop in a value the user typed, like an email or a password. The safe way is to keep that value as data. The unsafe way is to paste it straight into the query string. When the app pastes it in, the user controls part of the query, not just part of the answer.

Here is the key idea. The database cannot tell the difference between the query the developer meant to write and the extra query syntax the attacker typed. It just runs whatever text it receives. So if the input contains quotes, operators, or SQL keywords, those become part of the command.

If user input can change the structure of a query instead of just the values inside it, the user is writing your SQL for you.

How does SQL injection work in a login query

Imagine a login form on an invented app called Acme Notes. The server takes the email and password and builds a query by string concatenation. In a backend language this might look like the following.

query = "SELECT id FROM users WHERE email = '" + email + "' AND password = '" + password + "'"

If a normal user types alice@example.com and hunter2, the final query is exactly what the developer expected.

SELECT id FROM users WHERE email = 'alice@example.com' AND password = 'hunter2'

Now look at what happens when an attacker types ' OR '1'='1 into the email field and leaves the password blank or fills it with anything. The concatenation produces this.

SELECT id FROM users WHERE email = '' OR '1'='1' AND password = ''

The attacker’s quote closed the email string early, and the added OR '1'='1' is a condition that is always true. The query no longer asks “is this the right email and password.” It asks something the developer never wrote. Depending on how the rows come back, this can return a user record and let the attacker through the login without knowing any real credentials. The same trick, with different syntax, can read data the attacker should never see.

Why the quote matters

The single quote is the turning point. Inside the query, a quote marks the start and end of a text value. When user input is allowed to contain its own quote, it can break out of the value and into the command. Everything after the breakout is treated as SQL, not as data. That is the whole mechanism in one sentence.

What is the purpose of an SQL injection and what can an attacker do

The purpose of an SQL injection, from the attacker’s side, is to make the database run commands the application never intended. Once they can shape the query, the range of damage is wide.

  • Bypass login, as shown above, by forcing a condition to be true.
  • Read other people’s data, like dumping every row in the users table or pulling password hashes, order history, or private notes.
  • Change or delete data, by injecting an UPDATE or DELETE when the query allows it.
  • Probe blindly, where the app shows no data but behaves differently for true and false conditions, so the attacker reads the database one yes or no answer at a time.
  • Reach further in, since on some setups a database account has enough rights to read files or run system commands.

The common thread is trust. The app trusted that the email field held an email. The attacker proved that assumption wrong.

Why SQL injection still happens

This bug class has been understood for over twenty years, so it is fair to ask why it keeps appearing. A few honest reasons.

  • String building feels natural. Concatenating a query reads like normal code, and it works in testing because testers type ordinary input.
  • It hides in corners. The main login form might be safe while a search filter, an export feature, or an admin report still pastes input into a query.
  • ORMs are not a free pass. Many query builders are safe by default, but most also offer a raw query escape hatch, and that is where the bug sneaks back in.
  • Inputs you forget about. Headers, cookies, and JSON fields all reach the database too, not just visible form boxes.

How to fix and prevent it

The fix is direct, and it is the same idea every time. Keep user input as data, never as query structure. The standard tool for that is parameterized queries, also called prepared statements.

Use parameterized queries

With a parameterized query, you write the SQL once with placeholders, then pass the values separately. The database treats those values as pure data, so a quote in the input is just a quote, not a command. Here is the same login, done safely.

query = "SELECT id FROM users WHERE email = ? AND password_hash = ?"
db.execute(query, [email, password_hash])

Now if someone sends ' OR '1'='1, the database looks for a user whose email is literally the string ' OR '1'='1. It finds none, and the login fails as it should. The attacker lost the ability to change the query’s shape.

Back it up with more layers

  • Hash passwords and compare hashes, so a query never holds a raw password to begin with.
  • Validate input against what you expect, such as an email format, to reject obvious junk early. Treat this as a helper, not the main defense.
  • Limit database rights, so the account the app uses cannot drop tables or read files it never needs.
  • Review the raw query paths, since those escape hatches are where injection survives. Search the code for places that build a query from a string.

If you want more on this family of bugs and how to catch them, the injection and input category collects related explainers.

How to tell if your app has this bug

Finding SQL injection is less about throwing payloads and more about understanding which inputs reach a query and what the app assumes about them. A scanner can flag the obvious cases. The harder ones live in the assumptions, like a report filter that quietly trusts a sort parameter, or a search field that an ORM passes through as raw SQL. Those need someone, or something, that reads how the app is meant to work and then tests where that logic could break.

SQL injection is a clear example of one trusted assumption, that an input is only data, turning into full control of a query. This is exactly the kind of bug an autonomous researcher that tests an application’s assumptions is built to find and then prove with real evidence. You can read more about that approach on the about page.