¿Mi código impide el recorrido del directorio?

¿Es seguro el siguiente fragmento de código de una aplicación Python WSGI del recorrido de directorios? Lee un nombre de archivo pasado como parámetro y devuelve el archivo nombrado.

file_name = request.path_params["file"] file = open(file_name, "rb") mime_type = mimetypes.guess_type(file_name)[0] start_response(status.OK, [('Content-Type', mime_type)]) return file 

Monté la aplicación en http://localhost:8000/file/{file} y envié solicitudes con las URL http://localhost:8000/file/../alarm.gif y http://localhost:8000/file/%2e%2e%2falarm.gif . Pero ninguno de mis bashs entregó el archivo (existente). Entonces, ¿mi código ya está a salvo del recorrido del directorio?

Nuevo enfoque

Parece que el siguiente código impide el cruce de directorios:

 file_name = request.path_params["file"] absolute_path = os.path.join(self.base_directory, file_name) normalized_path = os.path.normpath(absolute_path) # security check to prevent directory traversal if not normalized_path.startswith(self.base_directory): raise IOError() file = open(normalized_path, "rb") mime_type = mimetypes.guess_type(normalized_path)[0] start_response(status.OK, [('Content-Type', mime_type)]) return file 

Su código no impide el recorrido del directorio. Puede protegerse contra esto con el módulo os.path .

 >>> import os.path >>> os.curdir '.' >>> startdir = os.path.abspath(os.curdir) >>> startdir '/home/jterrace' 

startdir es ahora una ruta absoluta en la que no desea permitir que la ruta salga. Ahora digamos que obtenemos un nombre de usuario del usuario y nos dan el malicioso /etc/passwd .

 >>> filename = "/etc/passwd" >>> requested_path = os.path.relpath(filename, startdir) >>> requested_path '../../etc/passwd' >>> requested_path = os.path.abspath(requested_path) >>> requested_path '/etc/passwd' 

Ahora hemos convertido su ruta en una ruta absoluta en relación con nuestra ruta de inicio. Dado que esto no estaba en la ruta de inicio, no tiene el prefijo de nuestra ruta de inicio.

 >>> os.path.commonprefix([requested_path, startdir]) '/' 

Puedes verificar esto en tu código. Si la función de startdir devuelve una ruta que no comienza con startdir , entonces la ruta no es válida y no debe devolver el contenido.


Lo anterior se puede envolver a un método estático así:

 import os def is_directory_traversal(file_name): current_directory = os.path.abspath(os.curdir) requested_path = os.path.relpath(file_name, start=current_directory) requested_path = os.path.abspath(requested_path) common_prefix = os.path.commonprefix([requested_path, current_directory]) return common_prefix != current_directory 

Use solo el nombre base del archivo ingresado por el usuario:

 file_name = request.path_params["file"] file_name = os.path.basename(file_name) file = open(os.path.join("/path", file_name), "rb") 

os.path.basename strips ../ desde la ruta:

 >>> os.path.basename('../../filename') 'filename' 

Aquí hay una solución mucho más simple:

 relative_path = os.path.relpath(path, start=self.test_directory) has_dir_traversal = relative_path.startswith(os.pardir) 

relpath se encarga de normalizar el camino para nosotros. Y si el camino relativo comienza con .. , entonces no lo permites.