October 1, 2026
SQL Injection: How the Attack Works and How to Prevent It
One of the oldest bugs on the web, and still one of the easiest to introduce by accident.

By Faizan Baig
6 min read
Here's a login form. You type a username, you type a password, the app checks them against a database.
Now imagine you type this into the username box instead:
' OR '1'='1' OR '1'='1And you're in. No password needed. The app logs you in as the first user in the table, which is often the admin.
That's SQL injection. It's been around for more than two decades, it's been on the OWASP Top 10 since the list began, and it still shows up in real applications today. It's one of the first web attacks anyone in this field learns, and it's worth understanding properly, because once you see why it works, you'll never write a database query the old way again.
What SQL injection actually is
SQL is the language applications use to talk to a database. "Give me the user named alice" becomes a query. "Save this order" becomes a query.
The trouble starts when an application builds those queries by gluing text together, with user input in the middle. The database receives one long string and has to decide which parts are instructions and which parts are just data. If the user can sneak instructions into the data, the database follows them.
That's the whole idea. SQL injection happens when user input stops being treated as data and starts being treated as code.
How it works, step by step
Say the app has a login check written like this:
query = "SELECT * FROM users WHERE username = '" + username + "'"
cursor.execute(query)query = "SELECT * FROM users WHERE username = '" + username + "'"
cursor.execute(query)If someone types alice, the database receives a clean, normal query. But look at what happens with the input from the start of this post.
The developer wanted the input to sit inside the quotes as a name. The attacker included a quote of their own, which closed the string early. Everything after that quote was read as part of the query. And since one equals one is always true, the condition matches every row in the table.
The same trick can do a lot more than log you in:
- Read data you shouldn't see: customer records, emails, password hashes, anything the database account can access
- Bypass authentication: like the login example above
- Change or delete data: if the query allows writes
- In some setups, go further: depending on how the database is configured, injection can sometimes lead to running commands on the server itself
How bad it gets depends on what the app's database account is allowed to do. That's why the prevention section matters as much as the attack.
The different flavors
You'll hear a few terms when people talk about SQL injection. They mostly describe how the attacker gets information back.
In-band. The result shows up directly in the page or in an error message. This is the easiest kind to spot and exploit. Error messages that leak database details make it even easier.
Blind. The app doesn't show any data or errors, but you can still learn things by watching how it behaves. A page loads normally if a condition is true and differently if it's false. Another version watches for delays: if the query is made to pause when a condition is true, the response time gives the answer away. It's slow, but it works.
Out-of-band. The data is sent somewhere else entirely, like a request to a server the attacker controls. This is rarer and depends on the database allowing it.
Second-order. The malicious input is saved safely at first, like in a profile field, and only causes trouble later when a different part of the app uses it in a query. These are easy to miss because the dangerous query isn't anywhere near the input form.
How people find it (the legal way)
Only test apps you own, intentionally vulnerable practice labs, or programs where you have written permission. Running injection tests against a real site without authorization is illegal, even if you "were just checking."
For practice, there are good places built for exactly this:
- PortSwigger Web Security Academy. Free, with step-by-step SQL injection labs.
- OWASP Juice Shop. A deliberately vulnerable app you run yourself.
- DVWA (Damn Vulnerable Web Application). A classic practice target, also run locally.
In a lab, the first check is simple. Put a single quote into an input and see what happens. If the page throws a database error or behaves strangely, the input probably ends up inside a query unsafely. From there, you test how the app reacts to conditions that are true versus false. Tools like sqlmap can automate a lot of this, but only point them at targets you're authorized to test, and learn to do the basics by hand first so you understand what the tool is doing.
How to prevent it
This is the part that matters most, and the good news is that the main fix is simple and solid.
1. Use parameterized queries (also called prepared statements).
This is the real fix. Instead of building the query by joining strings, you write the query with a placeholder and send the user's value separately.
# Vulnerable: input is glued into the query text
query = "SELECT * FROM users WHERE username = '" + username + "'"
cursor.execute(query)
# Safe: query and input travel separately
cursor.execute("SELECT * FROM users WHERE username = %s", (username,))# Vulnerable: input is glued into the query text
query = "SELECT * FROM users WHERE username = '" + username + "'"
cursor.execute(query)
# Safe: query and input travel separately
cursor.execute("SELECT * FROM users WHERE username = %s", (username,))The placeholder style depends on your database driver. Some use %s, others use ?. What matters is the pattern: the structure of the query is fixed first, and the input is only ever treated as a value. Even if someone types a quote or a whole fake query, the database just sees a strange username.
2. Be careful with ORMs and raw queries.
ORMs and query builders usually parameterize for you, which is great. But most of them also let you write raw SQL, and the moment you start joining strings inside a raw query, you've brought the bug back. "I use an ORM" is not the same as "I'm safe."
3. Allowlist what can't be parameterized.
You can't use placeholders for things like table names, column names, or sort direction. If you let users choose those, don't pass their input through. Compare it against a fixed list of allowed values and reject everything else.
4. Give the database account the least privilege it needs.
The web app's database user shouldn't be able to drop tables or read every schema. If an injection does slip through, a limited account keeps the damage small.
5. Don't show database errors to users.
Detailed error messages are a gift to anyone probing your app. Log the details on the server and show the user something generic.
6. Treat a WAF as a backup, not the fix.
A web application firewall can block a lot of obvious attacks, but it can be bypassed. It's a useful extra layer. It doesn't replace writing safe queries.
Things that don't work (but people try)
Escaping quotes by hand. Writing your own code to put a backslash before every quote feels reasonable, and it fails in edge cases, especially with different character encodings. Don't invent your own defense when parameterization already exists.
Client-side validation. Checking input in the browser only stops honest users. Anyone can send requests directly and skip it.
Blocking certain words. Filtering out words like SELECT or OR is a losing game. There are always other ways to write the same thing.
Why it's still around
You'd think a bug with such a clear fix would be gone by now. It isn't, for boring reasons: old code that nobody wants to touch, quick scripts that grew into real products, developers copying examples that build queries with strings, and raw queries added "just this once" for a complicated report.
None of it is exotic. It's usually one line where someone took a shortcut.
The takeaway
If you build things: never put user input into a query by joining strings. Use parameterized queries every time, keep your database account limited, and keep error messages quiet.
If you test things: remember that the bug isn't clever. It's a place where input and code were allowed to mix. Learn to spot those places, and practice on labs built for it.
And if you're just starting out, SQL injection is a perfect first vulnerability to really understand. The attack is easy to grasp, the cause is clear, and the fix fits in one line. Not many bugs are that honest about how they work.