El libro de Django 1.0

19.7. Directory Traversal

Directory traversal se trata de otro ataque del tipo inyección, en el cual un usuario malicioso subvierte código de manejo de sistema de archivos para que lea y/o escriba archivos a los cuales el servidor Web no debería tener acceso.

Un ejemplo podría ser una vista que lee archivos desde disco sin limpiar cuidadosamente el nombre de archivo:

def dump_file(request):
    filename = request.GET["filename"]
    filename = os.path.join(BASE_PATH, filename)
    content = open(filename).read()

    # ...

A pesar que parece que la vista restringe el acceso a archivos que se encuentren más allá que BASE_PATH (usando os.path.join), si la atacante envía un filename que contenga .. (esto es, dos puntos, una notación corta para "el directorio padre"), podría acceder a archivos que se encuentren "más arriba" que BASE_PATH. De allí en más es sólo una cuestión de tiempo el hecho que descubra el número correcto de puntos para acceder exitosamente, por ejemplo a ../../../../../etc/passwd.

Todo aquello que lea archivos sin el escaping adecuado es vulnerable a este problema. Las vistas que escriben archivos son igual de vulnerables, pero las consecuencias son doblemente calamitosas.

Otra permutación de este problema yace en código que carga módulos dinámicamente a partir de la URL u otra información de la petición. Un muy público ejemplo se presentó en el mundo de Ruby on Rails. Con anterioridad a mediados del 2006, Rails usaba URLs como http://example.com/person/poke/1 directamente para cargar módulos e invocar métodos. El resultado fué que una URL cuidadosamente construida podía cargar automáticamente código arbitrario, ¡incluso un script de reset de base de datos!

19.7.1. La solución

Si tu código necesita alguna vez leer o escribir archivos a partir de datos ingresados por el usuario, necesitas limpiar muy cuidadosamente la ruta solicitada para asegurarte que un atacante no pueda escapar del directorio base más allá del cual estás restringiendo el acceso.

Nota ¡Nunca debes escribir código que pueda leer cualquier área del disco!

Un buen ejemplo de cómo hacer este escaping yace en la vista de publicación de contenido estáticos (en django.view.static). Este es el código relevante:

import os
import posixpath

# ...

path = posixpath.normpath(urllib.unquote(path))
newpath = ''
for part in path.split('/'):
    if not part:
        # strip empty path components
        continue

    drive, part = os.path.splitdrive(part)
    head, part = os.path.split(part)
    if part in (os.curdir, os.pardir):
        # strip '.' and '..' in path
        continue

    newpath = os.path.join(newpath, part).replace('\\', '/')

Django no lee archivos (a menos que uses la función static.serve, pero en ese caso está protegida por el código recién mostrado), así que esta vulnerabilidad no afecta demasiado el código del núcleo.

Adicionalmente, el uso de la abstracción de URLconf significa que Django solo cargará código que le hayas indicado explícitamente que cargue. No existe manera de crear una URL que cause que Django cargue algo no mencionado en una URLconf.