In a recent penetration test that I carried out, I faced an unusual form of SQL injection that fortunately (for me!) let me gain access to sensitive data in the backend database. I would like to share how I found this and exploited it with you. After doing the typical information gathering phase of the penetration test, I noticed that “dirb” (a command line alternative to DirBuster) had flagged a couple of interesting files as accessible (view Table 1). These files seemed to belong to the admin panel of the website, and thus they should not be accessible unless you had the right privileges.
https://victim/manager/do.php https://victim/manager/do2.php
Table 1 – File found by dirb. I double checked that both files were actually accessible by directly browsing to the given URL, and to my surprise I found what looked like an uploading form
Image 1 – Uploading form. As shown in Image 1, the application seemed to only accept Excel files. It didn’t seem possible to upload script files and get a remote shell, not through lack of trying, so I decided to put all my efforts in finding what kind of Excel file the application was expecting and see if I could take advantage of it. I tried creating a very basic Excel file, with just one row and a few cells with some random content in it, but it didn’t work. I thought that if it was expecting an Excel file it should be parsing it and then inserting it in the database, so it may be some sort of bulk import functionality. Usually when dealing with bulk import, the application uses a standard format like CSV (Comma -separated Values) or some sort of variation. The common thing about all this formats is that most of them use the first row as a header, where they indicate the name of the fields to be read in the rows below it. An example is shown in the following table.
Piece ID | Price | Description |
---|---|---|
123456 | 25 | RAM – 2Mb |
123457 | 89 | Graphic Card |
… | … | … |
Table 2 – Example of CSV-like format In the example we can see how the file contains a first row with three columns, each of one is used to identify the fields that will later be read. And below that first row, we can see the actual values that will be parsed and inserted in the database. With this in mind, I thought it would be worth trying a similar structure for my Excel file, and… it worked!
Image 2 – Message after successful Excel file upload. After some testing I came to the conclusion that the application just needed an Excel file with a header row with two fields (it did not actually matter the name of each field as long as they were there), and then a number of rows with the actual values to be parsed and inserted to the database.
Field 1 | Field 2 |
---|---|
Value 1 | Value 2 |
… | … |
Table 3 – Example of accepted Excel file. Curiously enough, if I tried to upload exactly the same file, without modifying any of the fields (not the header but the values), I got a different message, shown in the image below. Although at that moment I didn’t give it the importance it deserved, this would be a key factor, but I’ll come back to it in a minute.
Image 3 – Message after duplicated Excel file upload. After playing for a while with the upload form, I was kind of stuck. I had credentials for the admin panel, but I did not want to use them, I wanted to keep it as real as possible. A real attacker would not have access to the admin panel, so I wouldn’t either. While thinking what I could possibly do with this, I thought it could be worth entering a harmless single quote in one of the values, for example in the second field, something like: “Value2’”. And to my surprise this returned the same error message as the one shown in Image 3. interestingly, it looked like the single quote was breaking the SQL query used in the back-end to insert new values. A SQL injection through an Excel Spread Sheet used to import data in bulk, this was going to be different! Once my initial happiness was gone I was ready to get my hands dirty, so I started thinking about a valid approach to exploit this specific SQL injection. A very important nuance is that the values read from the Excel file were used in an INSERT statement, cutting down the exploitation methods. And to make things even worse, I was probably injecting in the last parameter of the INSERT clause, reducing even more my chances of exploitation. Let me explain you why:
INSERT INTO Table_name (Field1, Field2) VALUES ('+ Value1_from_XLS +','+ Value2_from_XLS +');
In the piece of code above we can see my idea of what the SQL query they were using looked like. As mentioned before, I was injecting in the last part of that query, more concretely where it says “Value2_from_XLS”. That meant that apart from having to exploit the SQL injection in an INSERT clause (not as usual and not as easy as in a SELECT statement), I had to do it by crafting a request that didn’t break the syntax of the INSERT clause but doing something useful for me at the same time, and the fact that the injection point was at the last parameter did not help at all! But hold on, why don’t you use the first parameter (Value1), so you can close it with a single quote and then use the second field of the clause to insert something useful that hopefully you can later retrieve?. Well, I wondered the same thing, so I proceeded to use the first value to carry on with the exploitation, but… it did not work! Yeah, you are reading right, it did NOT work! I was not able to craft a single request that injected my own SQL code and that at the same time did not break the original query. After giving it some thought, going for a walk, doing some push ups, asking to my colleagues and listening to Justin Bieber to get some inspiration… something crossed my mind. I told to myself “Why is it giving me a different message if I use the same exact values in two following uploads?” And I answered to myself something like “Well, maybe it is because it is doing some sort of verification to ensure that there are no duplicate entries in the database, it sounds reasonable after all, right?” So with this in mind, I drafted a quick pseudo-code with what in theory the application was doing in the back-end: q1 =
SELECT * FROM Table_name WHERE Field1='+ Value1_from_XLS +'; if (q1 > 0) already exists else q2 = INSERT INTO Table_name (Field1, Field2) VALUES ('+ Value1_from_XLS +','+ Value2_from_XLS +');
If my theory was correct I could use the first field as the injection point in a SELECT statement and take advantage of the two different messages shown back by the application to perform a strange form of Blind SQL injection. To prove my theory I crafted two different Excel files, one that would force the overall SELECT clause to evaluate as true, and one that would do the opposite. To do this all I had to do is use a value that did not exist in the database, and combine it with an OR statement that I could modify to my need:
Field 1 | Field 2 |
---|---|
invalidValue’ OR 1=1 | NotImportant |
Table 4 – Overall evaluation of TRUE
Field 1 | Field 2 |
---|---|
invalidValue’ OR 1=2 | NotImportant |
Table 5 – Overall evaluation of FALSE I crossed my fingers, tested both Excel files, and… it worked!. After all my efforts and almost the whole day gone I had a shiny working SQL injection. The next morning, with a fresh mind, I successfully retrieved some sample data to show the client in the report (I won’t go through the details of it as it is out of the scope of this post). Note that although the XLS format is somehow encoded and has tons of clutter, you can still see the raw strings of your data, so with the help of Burp Intruder the process was not too painful. To conclude, while carrying out a penetration test, and as shown in this post, you can find vulnerabilities anywhere. Sometimes it will be easier and sometimes it will be tougher, just don’t give up! Antonio.