SecuInside CTF 2012 – Beast Writeup

As usual, let me start this write up by saying thank you to my teammates, NCR and Archie!

In the Beast challenge of the SecuInside CTF 2012, we were presented with the following web page:

Note that I’ve added in red color the name of the fields for convenience.

The source code of this page was available:


<? include "conn.php"; ?>

<?

if($_POST[lid] && $_POST[lphone])
{
$q=mysql_fetch_array(mysql_query("select * from challenge4 where id='$_POST[lid]' and phone='$_POST[lphone]'"));

if($q[id])
{
$pw="????";

echo("id : $q[id]<br>lv : $q[lv]<br><br>");

if($q[lv]=="admin")
{
echo("<b>Password is $pw</b><br><br>");
mysql_query("delete from challenge4");
}

echo("<a href=index.php>back</a>");
exit();
}

}

if($_POST[id] && $_POST[phone])
{
if(strlen($_POST[phone])>=20) exit("Access Denied");
if(eregi("admin",$_POST[id])) exit("Access Denied");
if(eregi("load|admin|0x|#|hex|char|ascii|ord|from|select|union|infor|challenge",$_POST[phone])) exit("Access Denied");

@mysql_query("insert into challenge4 values('$_POST[id]',$_POST[phone],'guest')");
}

?>

<form method=post action=index.php>
<table border=1>
<tr><td>JOIN</td><td><input name=id></td><td><input name=phone></td><td rowspan=2><input type=submit style=height:50></td></tr>
<tr><td>LOGIN</td><td><input name=lid></td><td><input name=lphone></td></tr>
</table>
</form>
<br><center><a href=index.phps>source</a>

As you can see in the source code, when we fill the id and phone fields ( the “Join” feature) and click on the button, that data is added to the challenge4 table of the database. A third, non-controllable value, the ‘guest’ string, is added to the record togheter with our controlled id and phone values:


@mysql_query("insert into challenge4 values('$_POST[id]',$_POST[phone],'guest')");

On the other hand, when we fill the lid and lphone fields (the “Login” feature) and click on the button, a select query is performed against the challenge4 table of the database:


$q=mysql_fetch_array(mysql_query("select * from challenge4 where id='$_POST[lid]' and phone='$_POST[lphone]'"));

If we successfully log in to the web page with our id and phone, and our permissions level (“lv” field of the challenge4 table) is set to “admin“, then the application will show us the flag for this challenge:

if($q[lv]=="admin")
{
echo("<b>Password is $pw</b><br><br>");
mysql_query("delete from challenge4");
}

That “lv” field is always set to ‘guest‘ when we use the “Join” feature. So the challenge here is to add an account into the table with the “lv” field set to ‘admin’ instead of ‘guest’.

Note that there’s some validation (blacklist approach) on the id and phone parameters before inserting our data in the table of the database:


if($_POST[id] && $_POST[phone])
{
if(strlen($_POST[phone])>=20) exit("Access Denied");
if(eregi("admin",$_POST[id])) exit("Access Denied");
if(eregi("load|admin|0x|#|hex|char|ascii|ord|from|select|union|infor|challenge",$_POST[phone])) exit("Access Denied");

@mysql_query("insert into challenge4 values('$_POST[id]',$_POST[phone],'guest')");
}

Basically, we can send at most 19 chars in the phone field, we can’t include the ‘admin’ word in the id, and we can’t include a lot of useful words (names of MySQL functions and SQL reserved words) in the phone value. Also, as we realized, the server has magic_quotes_gpc set to On. Anyways, our goal is to inject some arbitrary SQL code in this insert query.

In order to better understand the scenario, we set up a Linux machine with a classical web configuration: Apache2 + PHP5 + MySQL. We created a database with a table named challenge4 having fields id, phone and lv and then dropped the PHP file in the DocumentRoot folder, so we had our own running copy of the web application.

Since the CTF server is running with magic_quotes_gpc = On, it would be difficult to do the injection on the id field, which is of TEXT type or similar, because it’s surrounded by quotes, and then we would need to add a quote to keep the SQL statement string balanced. Fortunately, the phone field is of INT type or similar, so it is not surrounded by quotes, opening a hole for the injection:

<pre>@mysql_query("insert into challenge4 values('$_POST[id]',$_POST[phone],'guest')");</pre>

Also the webapp is filtering the “#” character on the phone value (look at the blacklist validation shown before), so we can’t use it to comment out the rest of the query (that is, the ‘guest‘ part). The solution to this issue is to make our injection add more than one record to the table with the same query. For example, sending id = pepe, phone = 665,@@version),(9,9 we get the following SQL statement:

insert into challenge4 values('pepe',665,@@version),(9,9,'guest')

That’s nice, we are adding an account with id = ‘pepe’, phone = 665, and we have forged the lv field! Instead of being the hardcoded ‘guest’ string, now our lv value is the version string of the MySQL engine. And we also have added a second dummy record, with id = 9, phone = 9, lv = guest.

Now we need to replace the @@version with ‘admin‘, but remember that the webapp has a blacklist for the phone field: it  can’t contain the ‘admin‘ string, nor the name of some useful string manipulation functions provided by MySQL. That means that we can’t use, for example, something like CHAR(97, 100, 109, 105, 110) to insert the ‘admin‘ string. And we also have the length limitation: phone value can have 19 chars at most.

At some point we tried in our local webserver with something like id = pepe, phone = 665,abcd),(9,9 in order to see if it was possible to insert the string abcd without quotes surrounding it, and we got an error saying something like “Error: unknown column abcd referenced”. Then we realized that we can evaluate the value of any field (id, phone, lv) of the current record.

For example, sending id = pepe, phone = 665,id),(9,9 will insert a record in the table like this: (id:’pepe’, phone:665, lv: ‘pepe’).

Having this in mind, we finally came out with this solution: id = nimda, phone = 3,reverse(id)),(5,5 .That generates the folowing SQL statement:

insert into challenge4 values('nimda',3,reverse(id)),(5,5,'guest')

It reverses the value of the id field, which is ‘nimda, inserting a record like this: (id:’nimda’, phone:3, lv: ‘admin’). BTW, this phone value we are using takes exactly 19 chars, the maximum allowed 🙂

So then we just needed to login into the application with id = nimda, phone = 3, and we got the flag for this challenge: 57483f303a55fed3b40a11519abf38f4.

Advertisements

2 thoughts on “SecuInside CTF 2012 – Beast Writeup

    • Hi Plitvix,
      Nice solution, you were able to comment out the remaining text of the query!
      I’m heading to your blog right now. Thanks for your comment!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s