lunes, 21 de junio de 2010

Directory Transversal en descarga de Archivo

Me encontre con una web la cual nesesitaba descargar un fichero que me resultaba de interes, donde la direccion de descarga era similar a esto:
http://www.sitio.com/descarga/download.php?file=archivo.extension, por lo que me propuse verificar si dicho downloader tenia alguna validación, para provocar un Directory Transversal.
Lo que realize fue la peticion de este modo http://www.sitio.com/descarga/download.php?file=download.php, para descargarme el archivo download.php
y heureca :)



Al abrir el archivo download.php podemos empezar a verificar las fallas de la validaciones:


$filename = $_GET['file'];

// addition by Jorg Weske
$file_extension = strtolower(substr(strrchr($filename,"."),1));

if ( ! file_exists( $filename ) )
{
echo "not file";
exit;
};
switch( $file_extension )
{
case "pdf": $ctype="application/pdf"; break;
case "exe": $ctype="application/octet-stream"; break;
case "zip": $ctype="application/zip"; break;
case "doc": $ctype="application/msword"; break;
case "xls": $ctype="application/vnd.ms-excel"; break;
case "ppt": $ctype="application/vnd.ms-powerpoint"; break;
case "gif": $ctype="image/gif"; break;
case "png": $ctype="image/png"; break;
case "jpeg":
case "jpg": $ctype="image/jpg"; break;
default: $ctype="application/force-download";

header("Pragma: public"); // required
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false); // required for certain browsers
header("Content-Type: $ctype");
// change, added quotes to allow spaces in filenames, by Rajkumar Singh
header("Content-Disposition: attachment; filename=\"".basename($filename)."\";" );
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($filename));
readfile("$filename");


?>


en la primera linea, podemos ver que toma el nombre del archivo por la variable $_GET['file'] y lo guarda en $filename, luego en la linea 4 lo que hace esta funcion es tomar todo el nombre del archivo y solo obtener su extension por ejemplo, si el archivo a descargar es archivo.pdf la variable $file_extension va a contener pdf, aca fue lo que me parecio algo muy bueno cuando estaba leyendo el codigo, pero solo lo usa para colocar el tipo de MIME(linea 12), lo cual esta muy bien pero tambien ubiera filtrado con ellos una black-list para que no se pueda descargar entensiones peligrosas.
En la linea 31 mostramos el nombre del archivo que le aparecera al usuario cuadno descarga y en la linea 33 el Content-Lenght es el tamaño del archivo, ah y la linea 29 es el MIME del que hablamos anteriormente. :)
Como la descarga se realizo con un php podemos llegar a pensar que podemos descargar cualquier php de la web, entonces buscaremos descargar el index que es donde puede haber datos interesantes de conexion de base de datos :)

http://www.sitio.com/descarga/download.php?file=../index.php



Al abrir el archivo index.php vemos esta informacion un poco mas interesante que la anterior



Nos descargamos el archivo class_DB.php, y dentro de ese archivo vemos una linea mucho mas interesante


include("conectar.php");


Y como piensan que sigue esto? ........... Si correcto, nos descargamos el archivo conectar.php




Bien ahora queda buscar el phpmyadmin, colocando www.sitio.com/phpmyadmin de la forma facil, y si no encontramos ahi podemos largar algun bruteador. O tambien que es la que mas me gusta por el cliente de mysql, primero comprobamos si el puerto esta abierto hacia el publico con Nmap :)



y ahora nos conectamos




Creo que queda entendida la peligrosidad de lo que se expone a no validar estas entradas asi como todas las entradas que se le da al usuario, siempre hay que verificarlas. Desde una simple bajada de archivo pudimos ingresar a sus datos, romper la confidencialidad de ellos, podriamos romper su integridad y hasta la disponibilidad de su sitio Web.

Para completar, al mismo script, le coloque seguridad, para que no se pueda descargar alguna extension peligrosa como php, asp, inc, etc. y para que no se pueda escalar directorios.



$index="http://".$_SERVER['SERVER_NAME']."/index.php";
$filename = basename($_GET['file']);
$ruta="descargas/".$filename;
// addition by Jorg Weske
$file_extension = strtolower(substr(strrchr($filename,"."),1));
#------------Bloque Seguridad ------------------
if(preg_match('/(php|asp|ini|inc|bak)/',$file_extension)){
header("Location:".$index);
}

elseif( $filename == "" )
{
header("Location:".$index);
}
elseif( ! file_exists( $ruta ) )
{
header("Location:".$index);
};

#-----------Fin Bloque Seguridad--------------
switch($file_extension )
{
case "pdf": $ctype="application/pdf"; break;
case "exe": $ctype="application/octet-stream"; break;
case "zip": $ctype="application/zip"; break;
case "doc": $ctype="application/msword"; break;
case "xls": $ctype="application/vnd.ms-excel"; break;
case "ppt": $ctype="application/vnd.ms-powerpoint"; break;
case "gif": $ctype="image/gif"; break;
case "png": $ctype="image/png"; break;
case "jpeg":
case "jpg": $ctype="image/jpg"; break;
default: $ctype="application/force-download";
}

header("Pragma: public"); // required
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false); // required for certain browsers
header("Content-Type: $ctype");
header("Content-Disposition: attachment; filename=\"".$filename."\";" );#nombre del archivo en la descarga
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($ruta));
readfile("$ruta");



?>


La segunda linea usamos la funciona basename, que logra que si el usuario coloca por ejemplo /carpeta/carpeta2/archivo.pdf, nos devilvera archivo.pdf, eliminado toda la ruta anterior y con la ayuda de la linea 3 ponemos una carpeta llamada descargas, la cual en ella contendra todos los archivos para descargas (valga la redundancia), y donde el usuario nunca podra escaparse de ella.
Asi por ejemplo si el usuario intenta descargar de esta forma
www.sitio.com.ar/download.php?file=/etc/passwd la variable $filename quedara con el valor passwd, y concatenando con la carpeta descargas la variable $ruta contendra descargas/passwd
En el bloque de seguridad, la primer linea verifica con una expresion regular que si existe esas extensiones, te direcciones al index (se puede colocar tantas como guste en esa lista).


Eso es todo

Saludos

4 comentarios:

  1. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  2. Yo obtaria por:
    poner un mod de apache
    .htaccess

    Header set Content-Disposition attachment


    este archivo debera estar ubicado en la carpeta
    descargas y no en la carpeta principal de tal forma que el mod solo aplique a esa carpeta y a sus subcarpetas

    Mucho mas facil, rapido, efectivo y estetico

    haha y todavia corrigues el fallo en el codigo php xD

    ResponderEliminar
  3. Muy buen artículo Magno. Una clara demostración que la cadena se rompe por el eslabón más débil.
    Hay varias cosas que no puedo entender el por qué hacen algo así. La más importante es "por qué dejar el port MySQL abierto para cualquier host", o de última "por qué dejar que el usuario MySQL que se conecta localhost, pueda conectarse desde otros host".

    El gran problema de la programación es que no capacitan a los programadores en seguridad. Obvio que hay muchos programadores que trabajan freelance y no tienen la posibilidad de tener una empresa que se interese en capacitarlos, pero en estos casos, ellos deberían preocuparse por aprender.

    En fin, supongo que es mejor así, sino nos quitarían la diversión =P

    ResponderEliminar
  4. Hola d3m4s1@d0v1v0, coincido con vos, con respecto al servicio mysql dejando abierto al publico y dejandolo a cualquier HOST, algunza razon habran tenido, sabiendo tambien que MySQL por default deja escuchando solo a LOCALHOST.
    Je tienes razon con la diversion.. _P

    Un abrazo

    ResponderEliminar