Skip to content
Field Detail
Platform PortSwigger Web Security Academy
Type Insecure Deserialization — Arbitrary Object Injection, PHP Magic Method Abuse
Difficulty Practitioner
Objective Create and inject a malicious serialized object to delete morale.txt from Carlos's home directory

Arbitrary Object Injection in PHP

I logged in as wiener:peter and decoded the session cookie from /my-account:

O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"af6dsjup0a1wbxqsxvuv76vqsyjadyxq";}
Screenshot

I tried the b:1 data-type bypass from the previous lab. The server returned a 500 with a PHP debug error confirming strict comparison is in use — that approach doesn't apply here. More useful was a comment in the page source:

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

Navigating to /libs/CustomTemplate.php returned nothing, but editor backup files are sometimes left behind with a trailing tilde:

web-security-academy.net/libs/CustomTemplate.php~
Screenshot

That returned the PHP source. The relevant part:

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

PHP's __destruct() magic method runs automatically when an object is garbage-collected — no explicit call needed. It calls unlink($this->lock_file_path), deleting whatever file lock_file_path points to. If a CustomTemplate object can be injected with lock_file_path set to /home/carlos/morale.txt, the destructor fires and deletes it the moment the deserialized object goes out of scope. That makes __destruct() a prime gadget starting point — it contains a file operation designed to clean up lock files, but nothing validates that the path is within expected bounds.

The class name in the payload must match an actual class in the application's code — unserialize() only instantiates classes it knows about. Constructing the malicious object using the real class name from the source:

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

Base64-encoding and setting it as the session cookie:

Screenshot
Screenshot

The server deserialized the object, instantiating CustomTemplate with lock_file_path = /home/carlos/morale.txt. When the object was destroyed, __destruct() fired and called unlink("/home/carlos/morale.txt").

Screenshot

Lab solved 8.8

Dead Ends & Rabbit Holes

Using a made-up class name (TetoTemplate) in the payload failed — unserialize() only instantiates classes that actually exist in the application. The class name must match CustomTemplate exactly as found in the source.

Resources