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.