Learning terminal: SQL Injection

visitor@learn:~$ cat sql-injection.html

SQL Injection (SQLi)

SQL injection happens when an application mixes user-controlled text directly into a database query. The database can no longer reliably tell the difference between data and instructions.

01 // Why is SQL injection a thing?

01

Frontend sends input

A form collects a username, email address, password, or search term.

02

Backend builds a query

An unsafe backend inserts that value directly into SQL using string concatenation.

03

Input changes logic

If the input contains SQL syntax, the finished query may behave differently from intended.

SELECT email FROM users WHERE username = '" + userInput + "'
SELECT * FROM articles WHERE author LIKE '%" + authorText + "%'

02 // Why are there two types of quotation marks?

The double quotes belong to the programming language building the text string. The single quotes will appear inside the final SQL statement to mark a text value.

"SELECT email FROM users WHERE username = '" + userInput + "'"
"... username = '" + "student01" + "'" = username = 'student01'

03 // Query construction playground

simulation only — no database connected

Unsafe concatenated query unsafe


              

Parameterized query preferred

SELECT email FROM users WHERE username = ?

Separate parameter value:

The value is treated as data, not SQL code.

04 // Login bypass example

A typical login query checks whether one row matches both the submitted username and password. If the application builds this query by concatenating strings, SQL syntax entered into either field can change the logic.

SELECT * FROM users
WHERE username = 'clint'
  AND password = 'SuperSecretPassword'

What attackers try to change

They may add an OR condition that is always true, or comment out the rest of the password check.

username = 'administrator' --
password = doesNotMatter
Many vulnerable systems only check whether the query returned any record, or they authenticate as the first returned record.
SELECT * FROM users
WHERE username = 'administrator'
-- AND password = 'doesNotMatter'

05 // Dangers beyond login forms

URL GET parameters

Search pages often place user input directly in the URL, then use it in a LIKE query.

/search?term=eggs

SELECT * FROM recipes
WHERE content LIKE '%" + searchText + "%'

An injected value like %' OR '1'='1' -- can force the search to match everything.

Stacked queries

Some database drivers allow more than one statement. That can turn one input into a read plus a destructive or unauthorized write.

' OR '1'='1';
INSERT INTO users (username, password)
VALUES ('attacker', 'password');
--

Whether stacked queries work depends on the database, driver, and server configuration.

06 // Identifying vulnerable inputs

Treat every value that travels from the client to the server as untrusted, even if it comes from a hidden field, cookie, header, or authenticated user.

High-risk inputs

Forms, search bars, URL parameters, hidden fields, cookies, HTTP headers, API parameters, and uploaded metadata.

Warning signs

Unexpected results, empty result sets, full SQL errors, database names, table names, file paths, or stack traces.

Reality check

Some successful SQLi attempts are blind: the page may show no obvious error or confirmation at all.

Identification belongs in authorized testing environments only. The lesson examples here are simulations for understanding defensive design.

SQL comment styles

SQL comments cause a parser to ignore part of a statement. The accepted comment forms differ slightly between database products.

MySQL / MariaDB

-- comment
# comment
/* comment */

-- requires whitespace after the dashes.

PostgreSQL

-- comment
/* comment */

SQL Server

-- comment
/* comment */

Oracle

-- comment
/* comment */

SQLite

-- comment
/* comment */

07 // Prevention

Unsafe: concatenate input

const query =
  "SELECT * FROM users WHERE username='"
  + username + "'";

The user's value becomes part of the instruction text.

Safe: parameterize input

const query =
  "SELECT * FROM users WHERE username = ?";

db.all(query, [username], callback);

The query structure stays fixed while the value is supplied separately.

Validate

Reject invalid input before use. Check allowed characters, length, formats, and fields that should not contain spaces or SQL syntax.

Filter carefully

Escaping or removing risky characters can reduce risk, but filtering is not a replacement for prepared statements.

Limit errors

Do not expose raw SQL errors, database versions, table names, internal file paths, or stack traces to users.

Apply the principle of least privilege: if a web server only needs to read from a database, use a read-only account. If it needs writes, restrict permissions to the specific tables and actions required.
Remember: user input is data, not code. Use parameterized queries or prepared statements. Input validation and limited error messages are useful extra controls, but they are not replacements for parameterization.
<- return to /learn