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 SELECTstatement, 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 SQLMapcan be found here: