I encountered what is known as Routed SQL Injection a couple of times but it was never required to exploit the vulnerability to the full extent. Recently, I discovered an online challenge on the topic and decided to look at it in depth. An explanation of the vulnerability with a vulnerability code which is described in the beginning this post, can be found here.
To better understand the vulnerability, let's examine the following piece of code:
The magic here is that - the output of the first query is used as the input of the second query and it is the second query which displays output back to us. Or in other words, in order to exploit the SQL Injection vulnerability, we need to control the input for the second query - the variable sec_code (which happens to be the output of the first query). You may have already noticed that, due to the fact that the first query is vulnerable to SQL Injection, we can control and set sec_code to an arbitrary value.
Let's exploit that in the online challenge (the source code is different but similar in nature). To begin with, starting by confirming that it indeed is vulnerable to SQL Injection by sending a single string ' as payload:
Next, we'll send as payload ' union select 1 -- - the injection will escape the first SQL query and set both the values, the second one (representing sec_code in our example) to the value 1 - therefore, since it actually has a value, the second query will run successfully and return output for the value 1:
If we were to send a payload that sets the second value to 2 e.g. ' union select 2 -- - , we'll get the result that corresponds to it:
If we set the second query to a true statement, it will evaluate every record in the database and return output from the final one it finds. The query would be (in theory) ' union select ' or true -- - -- -. However, we get an alert "Attack detected" - there are filters in place!
To bypass the filter, we'll HEX encode the value (which also helps with the injection itself because it encodes the comments characters and are therefore only evaluated in the second query once decoded) that we pass to the second query (in the previous query it is the ' or true -- - part) as shown on the payload below:
It appears that the final item it found had an ID 3 (or there are 3 items). But what is important is that we found a bypass for the filter and we can control the second part of the query. Next we utilizing "ORDER BY" sql clause to determine that the SQL table contains 2 columns because when we try to order by the third, we get an error, that it is unknown - as shown below:
Query plain text: ' union select ' order by 3 -- - -- -
Query with hexed second part: ' union select 0x27206f726465722062792033202d2d202d -- -
This error is very good news because the results we got previously have "ID" and "Email" as output fields. If both values are returned from the second query that we injected into, then we may be able to return any output that we desire in both fields by utilizing union select once again, this time in the second query as well OR run any SQL Query instead of setting the output to a fixed value. For example, let's try to set the output fields to 1 and 2:
Query plain text: ' union select ' union select 1,2 -- - -- -
Query with hexed second part: ' union select 0x2720756e696f6e2073656c65637420312c32202d2d202d -- -
As we can observe from the output, we can successfully control the values of the output from the second query - just as we have been doing from the very begging with the first one! With this kind of control, we can replace the fixed values 1 and 2 with SQL functions e.g. get current user and version:
Query plain text: ' union select ' union select current_user(),@@version -- - -- -
Query with hexed second part: ' union select 0x2720756e696f6e2073656c6563742063757272656e745f7573657228292c404076657273696f6e202d2d202d -- -
Basically, we are now in a situation that resembles a normal SQL Injection. After some queries to discover the existing tables, columns etc, we can finally execute a payload that will dump the information in the users table - note group_concat is used because the result has multiple rows:
Query plain text: ' union select ' union select 1,(select group_concat(login, ':' ,password) from users) -- -
Query with hexed second part: ' union select 0x2720756e696f6e2073656c6563742063757272656e745f7573657228292c404076657273696f6e202d2d202d -- -