Como implementar una descarga correctamente (y II)

Para proseguir con esta interesante serie, vamos a hablar de la subida de ficheros. Pues sin esta no habría descarga posible. Cuando dejamos a los usuarios subir archivos programáticamente, no debemos confiar para nada en ellos. Cuando se tiene algo de conocimiento de la seguridad informática, se sabe que es la primera regla: todos los usuarios son malvados en potencia. Aunque voy a enfocarlo a un caso muy general, es aplicable para el resto de casos y se puede aprender del tema.

Cuando permitimos subir al usuario contenido, hay que tener en cuenta dos factores esenciales:

  • El contenido no debe ser directamente accesible.
  • El contenido no puede tener el mismo nombre de fichero que originalmente se le dió.

Ambos factores se basan en lo que se conoce hasta actualmente. Vamos a suponer, por ejemplo, que nuestra aplicación web permite a cualquiera registrarse (lo cual es muy normal en la web 2.0 :P). Supongamos también que no hemos tenido en cuenta los dos factores anteriores. ¿Qué podría pasar?

¡Pues podría pasar de todo! Aunque lo más general es que si el atacante está buscando hacer el mayor daño posible subiría lo que se conoce como una Consola Web y podría llegar a controlar el sitio web (eliminando, modificando, etc. a gusto) y en el peor de los casos: el hosting (por una mala configuración de permisos).

También podría darse el caso del reemplazo de archivos. Por ejemplo, si alguién sube un fichero de nombre “cuenta.txt” que contiene un número de cuenta bancaria para que la gente haga donaciones al webmaster y alguién lo reemplaza por el suyo propio con otro número de cuenta… Imaginad que podría pasar :P

En fin, estas son varias de los problemas que esto podría generarnos, aunque todo está en la imaginación del atacante (y suele ser mucha).

Dicho esto, empecemos a ver cómo solucionar estos dos problemas. Para solucionar el primero, la idea más efectiva suele ser la siguiente. Crearemos un directorio en nuestra aplicación web que no sea accesible para ningún usuario. En ASP.NET, tendríamos que utilizar la herramienta de configuración o bien añadimos un web.config con el siguiente contenido al directorio:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
    <system.web>
        <authorization>
            <deny users="*" />
        </authorization>
    </system.web>
</configuration>

Así, será imposible acceder al directorio desde fuera del servidor.

En este directorio guardaremos todos los archivos que sean subidos por los usuarios. En el IIS, nos “molestaremos” en quitar todos los permisos de ejecución de este directorio. :)

En lo que se refiere al segundo problema, la solución estratégica es renombrarlos y guardar una lista de equivalencia, ya sea en un .txt, .xml o en una tabla de la base de datos. Mi recomendación aquí es la siguiente:

A cada archivo subido, renombrarlo con un Guid (un número con este aspecto: 4b15c156-0df5-41d9-83d9-8eda5f5b864b) (en .NET la clase System.Guid y en Sql Server es el tipo uniqueidentifier) y guardar la equivalencia entre el nombre original del archivo y el su correspondiente código Guid.

Para los no iniciados, sólo decir que los Guid son números de 128 bits que se generan automáticamente y la posibilidad de producir dos iguales es bastante escasa, con lo cual nos quitamos los problemas de colisiones que pudieran aparecer. Recomiendo su uso ya que es más seguro que generar números consecutivos entre otras cosas. Si quereis más información:
[links]

Además, al renombrarlos con el Guid, no tendrán la extensión original y si fueran *.aspx dejarían de serlo, por lo que no podrían ser ejecutados. :)

Ha llegado el momento en el que veamos la parte de la descarga. En esta ocasión veremos como validar correctamente la petición y así evitar ataques de canonicalización.

Tomadas en cuenta las recomendaciones anteriores, lo que tendríamos que hacer sería leer el Guid de la petición (GET o POST), validar que es un valor Guid y no otra cosa, encontrar el archivo y devolverlo con su nombre original.

En caso de que ya hubieramos desarrollado otra solución para esto y, al menos, almacenamos los ficheros en un directorio no accesible (es decir, cumplimos la primera recomendación), la manera de garantizar de que las peticiones son correctas las podemos llevar acabo así:

Comprobamos que el nombre de fichero no sea nulo (utilizando la función string.IsNullOrEmpty). Eliminamos todos los caracteres “malignos” (es decir que no van a existir en el nombre de un fichero (\/?*) del string de la petición y la subcadena “../” si existe. Comprobamos que la petición se hace dentro del directorio de los archivos. Para asegurarnos de esto, podemos hacer lo siguiente: Creamos una instancia de System.IO.FileInfo con la ruta absoluta en el servidor del fichero a descargar y luego comprobamos mediante la propiedad FileInfo.Directory.FullName que el último directorio es el directorio de los ficheros (lo podemos hacer con string.Split()) así como que el fichero existe. ^^’

Y con éste artículo doy por concluida la miniserie de implementar una descarga como d*** manda. Si teneis alguna duda, curiosidad o pregunta, utilizad los comentarios.


2 Responses to “Como implementar una descarga correctamente (y II)”


  1. 1 polvo January 24, 2008 at 16:24

    kisierra ser un haker q devo aser para eso espero tu respuesta

  2. 2 Vargas January 24, 2008 at 16:28

    Aprende a utilizar correctamente el castellano. Luego aprende inglés y a partír de ahí… recorrer un largo camino de estudio y auto-aprendizaje. Te recomiendo que leas: How to become a Hacker (Si sabes inglés. Si no, siempre puedes echar mano a una traducción al castellano).


Comments are currently closed.



About me


My name is Rafa Vargas. I'm an undergraduate student of Computer Science at University of Seville, Spain. I am mainly interested in computer security, usability and the business of software.

Click here to read the full story.

Twitter subscription

Error: Twitter did not respond. Please wait a few minutes and refresh this page.

Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Archives