SQL injections are my favorite vulnerabilities. Of course, every penetration tester loves them since they are (in most cases) critical, however what I like with them is that there are so many ways to exploit even the apparently-looking remote or unexploitable cases.
The particular case I will describe below is very closely related to a previous diary I posted in 2016 – you can read it here. Let’s dig into it!
POST /api/customers HTTP/1.1
As shown above, we have three parameters which we should test for SQL injection. Due to specifics of the tested web application, and the way the results are displayed back (i.e. it is partially out of band), automated tools such as Burp or sqlmap did not find any vulnerable parameters.
Manual testing, however, indicated that the last parameter, internal_id, is potentially vulnerable to SQL injection. By inserting our favorite ‘ character it was possible to cause an error (displayed on a different web page). Enough to make every penetration tester’s hand sweat!
The next step is typically to get sqlmap working with this – no point in doing all the extraction manually. Now that we know where is the SQL injection, the easiest way to pass this to sqlmap is to save the request with Burp and then mark the injection place with * - this will indicate sqlmap where the injection is.
Even with this, sqlmap did not work correctly, so I had to dig further manually. It turned out that there was some kind of filtering on the server side – attempts to add delays (i.e. WAITFOR DELAY) or CASE keywords all failed: the only keyword that actually worked was SELECT.
On the other (out of band) web page I was able to see some errors – in cases when a banned keyword was used there was a generic error. Also, it was possible to induce the division by zero error by modifying the SQL query to something like this:
3298’ AND 1=1/0 --
Ok, so we’re getting somewhere. As with all blind SQL injection vulnerabilities, we need to have a true and a false case. By being able to cause a division by zero error we have a false case (and the true case is when the application works).
How to extract a byte now? Let’s see: when we guess a byte, we want to cause a division by zero error, while in other cases we want the application to work OK. Another limitation is that we can use only the SELECT keyword (for some reason it was not blocked/filtered by the backend application). And this is the solution:
3298’ OR 1=1/(SELECT ASCII(SUBSTRING(HOST_NAME(),1,1))-X) --
What are we doing here? We are taking the first character returned from the HOST_NAME() method which returns (obviously) the local host’s name. Then we convert that character to its ASCII value and subtract a number X from it.
As with previous SQL injection examples, this again shows how proper, strict filtering (and using parametrized queries) is the only way to prevent exploitation.
Want to learn more about web application security? Join me in Paris, 12th -17th of March for the fantastic SEC542: Web App Penetration Testing and Ethical Hacking (GWAPT) course!Web App Penetration Testing and Ethical Hacking - SANS Northern VA - Reston Spring 2020
Feb 8th 2018
2 years ago