In this section, we’ll cover some common ways of discovering SQL injection vulnerabilities in a web application. First, what is SQL injection? It’s when arbitrary input is used by an attacker to construct the remainder of a SQL query by the web application before executing the query on the web app’s underlying database. This arbitrary input can be used by an attacker to determine the format of the SQL query, and then subsequently use that to escape the query and execute arbitrary SQL commands on the SQL server.
This is particularly dangerous because this allows the attacker to leak sensitive web application information, or even execute arbitrary commands on the web application server. More on SQL injection can be found at the references below:
String delimiters
SQL query takes a user-provided string, for example:
SELECT * FROM myTable WHERE myValue = 'deadbeef'
Where deadbeef
was provided by the user. But if the user provides deadbeef'
,
the SQL server will run into an error - this string isn’t closed. With this lack
of sanitization on user input, we can attack and dump all content for the
current table by executing:
SELECT * FROM myTable WHERE myValue = 'deadbeef' OR 1=1--'
Where we entered the string, deadbeef' OR 1=1--
. The original SQL query
remains intact, and then we also add an OR 1=1
clause to leak the rest of the
values in the table. The --
characters create a comment - SQL will ignore the
remainder of the string.
Closing functions
In the same vein as string delimiters, the web application is taking our arbitrary input and providing it to a SQL function call when constructing the SQL query, e.g.:
SELECT * FROM myTable WHERE LOWER(myValue) == LOWER('deadbeef')
We can break this query with the following example:
SELECT * FROM myTable WHERE LOWER(myValue) == LOWER('deadbeef') OR 1=1--')
Where we provided the input: deadbeef') OR 1=1--
.
Sorting
Sometimes web applications sort the output of SQL queries to provide clean output to the user, e.g.:
SELECT myValue, myOtherValue FROM myTable ORDER BY 1
Where ORDER BY 1
instructs the SQL server to sort the output in descending
order based on the first column. We can detect whether or not we can control the
value given to the ORDER BY <NUM>
operation by testing out arbitrary integer
values, or just removing the integer value entirely from the browser’s request
to the server. This should, eventually, cause SQL to report an error, being
unable to find the column specified for the ORDER BY
operation.
Fuzzing
Using a familiar tool, wfuzz, we
can use a working POST request to fuzz for irregularities in the web
application’s response, attempting to detect a SQL injection vulnerability.
Provided a working POST request, we can use a wordlist with wfuzz
to try out
different inputs and test the server’s response:
wfuzz -c -z file,/usr/share/wordlists/wfuzz/Injections/SQL.txt -d "db=mysql&id=FUZZ" -u http://<HOSTNAME>/api
In the above bash
invocation, we’re directing wfuzz
to print results using
color, -c
, using a wordlist, -z file
, and providing POST request data, -d
,
to fuzz the target URL, -u
.
Error-based payloads
After discovering a SQL injection vulnerability, we want to gain more information about the target instead of just reading the error output from the SQL server when attempting to interpret our query. Below are some techniques we can use against different SQL server providers to leak version information. More on error based injection attacks:
PostgreSQL and Microsoft SQL Server:
cast(@@version as integer)
The victim SQL server retrieves the version string and attempts to cast
it as
an integer, however, the version string isn’t in the correct format for an
integer cast. This causes the SQL server to display an error, leaking the
version information.
MySQL
extractvalue('',concat('>',version()))
The victim SQL server retrieves the version string and attempts to store an
empty XML
fragment into the XPATH
variable represented by the version
string. This fails as the version string is not a valid XPATH
variable. The
SQL server displays an error, leaking the version information.
Oracle
to_char(
dbms_xmlgen.getxml(
'select "'||
(select substr(banner,0,30) from v$version where rownum=1)
||'" from sys.dual'
)
)
The victim SQL server retrieves the version string an truncates it to 30
characters. This is then concatenated to create a SQL query string that is the
first parameter to the getxml
function call. The result of getxml
is then
cast to a string. The getxml
function call fails because the parameter is not
a valid SQL query, and the SQL server displays an error leaking the version
information.
UNION-based payloads
UNION
-based payloads are useful when targeting a SQL injection
vulnerability that allows to append characters to the tail of a valid
SELECT
statement, e.g.:
SELECT myColumn FROM myTable <INJECTION_HERE>
Using UNION ALL
, we can then construct a query like:
SELECT myColumn FROM myTable UNION ALL SELECT victimColumn FROM victimTable
Exposing data from other tables that aren’t in the original query. This is a
powerful payload, but sometimes difficult to get working because the number of
columns in the original table is unknown. If we don’t request enough columns in
the UNION
statement, the query will be malformed and rejected by the SQL
server. In that case, we can use static values like NULL
to ensure our UNION
syntax is correct, e.g.:
SELECT myColumn1, myColumn2 FROM myTable UNION ALL SELECT victimColumn, NULL FROM victimTable
Two columns are requested in the original, vulnerable query - we’re only
interested in one column from the victimTable
, therefore we use NULL
as a
constant value for our SELECT
statement to replace the second column expected.
Some UNION
-based payload examples can be found here:
Stacked queries
Stacked queries are individual queries submitted at the same time and executed sequentially. Stacked queries are useful if you’re injecting at the tail end of a vulnerable query - not super useful in the middle of query that you’re potentially breaking up. Stacked queries also aren’t guaranteed to return information back to us, it’s likely the web application will only use the results of the first query. Stacked queries are useful for executing background tasks, however, allowing us to change data in the target database.
Stacked queries usually require us to terminate the previous query before injecting our stacked query, e.g.:
SELECT myColumn FROM myTable WHERE myColumn = 0; SELECT * FROM myTable;
Where we injected:
0; SELECT * FROM myTable;
Time-based blind injection
When attacking a PostgreSQL database, if we have the ability to execute time-based, blind SQL injection, here’s a couple of useful queries to gather information:
;SELECT case when (SELECT current_setting($$is_superuser$$))=$$on$$ then pg_sleep(10) end;--+
File manipulation
File manipulation on the host of the SQL server is possible if the SQL server has permissions to read / write to targeted directories, and the user also permissions to target specific data or files on the server and the host.
For PostgreSQL, we can use SQL commands like pg_read_file
to expose the
contents of files on the host that the SQL server can access, e.g.:
SELECT pg_read_file('/etc/passwd')
For MySQL, we similarly have the LOAD_FILE
function, however, it’s
important we check the server’s permissions with file_priv
:
SELECT file_priv FROM mysql.user WHERE user = <current_user>
We can also attempt to verify the permissions in the @@GLOBAL.secure_file_priv
variable. More on interacting with the filesystem via SQL can be found here:
Remote code execution (RCE)
Different SQL servers have different ways of enabling operating system command execution. Some SQL servers, like Microsoft SQL Server, have native ways of executing system commands, while servers like MySQL might require us to upload a webshell. More techniques on how to establish an OS command shell can be found here:
Automation tools
Once you’ve discovered an injectable SQL query, honestly the best and easiest
way to reliably exploit it is to use SQLMap
. First, use something like
BurpSuite to interact with the target website, gather cookies, valid queries,
etc. Identify the vulnerable parameter(s) and then pass this information to
SQLMap
. With SQLMap
, you can determine how invasive or quiet the attack is.
Regardless, SQLMap
is pretty powerful and has a variety of techniques to both
leak information as well as gain RCE on a target vulnerable to SQL injection.
Official documentation for SQLMap
can be found here: