Detective story about SQL injection, sometimes blind

    Good day!

    I would not think to write an article about it, because I thought that the topic is pretty hackneyed. But judging by this article, the audience is interesting. Finally I was convinced that I need to write this comment .

    This story happened with a "friend of a friend of my acquaintance", but for brevity I will write quotes from his words, using just "I". It was a week and a half ago. Go.

    I needed to learn one European language, in the light of a possible move to one European country. And I found a wonderful site on which it was proposed to learn a language using podcasts. Podcasts themselves are free, but you can buy PDFs with lesson notes and exercises. I don’t really need these notes, but my wife, unlike me, wasn’t at all an audience, and she also needed to learn the language. Before you buy something on the Internet, I carefully study the seller’s website - I do not want my data to leak somewhere. And in this case, everything turned out to be more than bad. The desire to buy something immediately disappeared. But to remain without PDF is unsportsmanlike. In the end, I decided to try to use one of the vulnerabilities found. I must say right away that I basically don’t use any automatic vulnerability scanners and basically don’t harm the users of the resource - they are not to blame for the fact that the owner of the resource wrote it clumsily. Therefore, my tools are an understanding and theoretical knowledge for the reasons for the emergence and use of vulnerabilities.


    First of all, I looked at a few demos available for download as a PDF. First, the user went to:


    At this moment, it is checked whether the current user has the right to download the specified PDF. If so, a redirect to the address is underway: It


    immediately turned out that this script gives the specified file without checking anything. Because an accessible example for lesson number 1 had the file name 001.pdf I decided to try to get all the files by brute force. If everything was so simple, then there would be nothing to write about. But in this way only the first 100 files were obtained. The rest had timestamps of creation time in their name and it became impossible to sort them out, because creation time differed by several months.

    We untwist SQL injection

    Pretty soon, the most banal SQL injection was discovered in the GET parameter:


    It seems to be further built using it very simply:
    1. Determine the number of parameters in the request
    2. Select table and field names (in case of MySQL 5.0 and higher - select them from information_schema)
    3. Get the desired file names
    4. Download the files themselves

    But the problems started from the first point - it was not possible to determine the number of fields in the request. With any number of fields in UNION SELECT and with any number in ORDER BY n, I received the message “You have error in your syntax ...”

    In fact, I accidentally guessed exactly what the problem was - by trying to do GROUP BY 1. I got a "cannot group by cnt" error. It turned out that the vulnerable parameter is used twice (well, at least I could not refute this assumption).

    First, the number of records with the specified id is selected:

    SELECT count(*) FROM table where id=123

    If the number of records is 0, then it is considered that the page is not found and a redirect to the main page occurs. If there are no records 0, information is pulled out:

    SELECT * FROM table where id=123

    Now it becomes clear why it was not possible to find out the number of fields in the query - there are 2 of them and one of them will always have the wrong number of fields in UNION. I failed to come up with a way to insert a different number of fields in UNION into the first and second request. And at that moment, SQL injection became blind. I could not find the name of the table with the paths to the files, but I managed to find the name of the table with user data (MySQL 4.1).

    Dear developers, do not make 2 requests, where you can make one! In this case, instead of SELECT count (*), it was possible to check the number of records returned by the SELECT * query.

    Now it remains to come up with a way to get useful information. I did this:

    /script.php?id=123 limit 0,0 union all select length(username)>4 from tablename limit 0,1--

    What we see here:
    • 123 limit 0,0 - since count (*) will always return exactly one record to us and we won’t know what exactly was returned by our part of the request, we need to remove it from the result
    • union all select length (username)> 4 from tablename limit 0,1-- - if the username is longer than 4, then the condition is true, MySQL will return one, and then an error when trying to execute the second query. If the condition is incorrect, it will return 0 and a redirect will occur. Well and '-' for a comment at the end

    Thus, using the HTTP header, you can determine whether we have passed the correct condition. First, we determine the length of the username, then, in a binary search, we pull out the name itself (lower (substr (username, 1,1)) in ('a', 'b', 'c')). Then we pull out the password letter by letter. But it turns out that the password is hashed in md5. Although the hashing was salt-free, it was still not possible to find the site administrators passwords (there weren’t any rainbow tables, but I didn’t want to do bruteforce on a netbook; and it’s not unsportsmanlike).

    After some thought, it was decided to go the other way. Because the database turned out to be more than 60,000 users, I assumed that many of them have popular passwords. And then it was only necessary to pull out the user names letter by letter whose password hash is md5 ('password') - there were more than 100 of them and among them there were people who bought the necessary PDF. And they kindly agreed to share them with me.

    All this was done using a very simple script that sent a HEAD request (why do we need the body of the page?) And looked at the response header. If 200, the condition is true; if 302, it is not true.


    Why is all this written? To show that you need to know the essence and causes of vulnerabilities, and not to memorize how to use them. All the methods of using SQL injection, which I saw on the Internet, suggested determining the number of fields through ORDER BY 5 or UNION SELECT 1,2,3 ... And a person who did not want to think would leave the site with nothing.

    Also, I'm slightly proud of my workaround instead of breaking a hash. Well, not so long ago , skepticism was expressed about the existence of such vulnerabilities on the modern Internet and about the practical use of blind SQL injection.

    PS All coincidences with reality are random. The voices of celebrities are simulated, and wretchedly.

    Also popular now: