2011-07-23 31 views
15

¿El siguiente fragmento de código de una aplicación Python WSGI está a salvo del recorrido del directorio? Lee un nombre de archivo pasado como parámetro y devuelve el archivo nombrado.¿Mi código impide el cruce del directorio?

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 

I montado la aplicación bajo http://localhost:8000/file/{file} y enviado solicitudes con las URL http://localhost:8000/file/../alarm.gif y http://localhost:8000/file/%2e%2e%2falarm.gif. Pero ninguno de mis intentos entregó el archivo (existente). Entonces, ¿mi código ya está a salvo del cruce de directorios?

Nuevo enfoque

Parece que el siguiente código impide recorrido de directorio:

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 
+1

¿Qué sucede si le das una ruta absoluta? – katrielalex

+0

¡Buena idea! Tu sugerencia me llevó a la respuesta: ¡es inseguro! El recorrido del directorio fue "accidentalmente" prevenido por otra parte del marco utilizado. – deamon

Respuesta

18

Su código no impide recorrido de directorio. Puede evitar 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 el camino para ir fuera de. Ahora digamos que obtenemos un nombre de archivo del usuario y nos dan el /etc/passwd malicioso.

>>> 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. Como no estaba en la ruta inicial, no tiene el prefijo de nuestra ruta de inicio.

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

Puede verificar esto en su código. Si la función de prefijo común 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 
+2

Simplemente no confíe en hacer cosas relativas al directorio de trabajo actual, ya que puede ser cualquier cosa en una aplicación web. Siempre debe basarse en una ruta absoluta como punto de partida, ya sea cableada o calculada desde \ _ \ _ archivo \ _ \ _. –

+0

@ graham-dumpleton Esto no tiene nada que ver con relativo o absoluto. Un camino siempre puede surgir a menos que haga este tipo de control de cordura. – jterrace

+0

Parece que no acaba de obtener lo que me refiero.Usted dio en su ejemplo 'startdir = os.path.abspath (os.curdir)'. Eso establecerá 'startdir' en el directorio de trabajo actual. En una aplicación web de Python no hay garantías de cuál será el directorio de trabajo actual. Por lo tanto, es un ejemplo pobre de usar porque la gente cortará y pegará el código ciegamente sin comprender que deben estar anclando en un camino absoluto que tenga significado para su aplicación en lugar de depender de lo que os.getcwd() va a devolver. cuando se llama a os.path.abspath (os.curdir). –

1

Uso sólo el nombre base del archivo inputed 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 tiras ../ del camino :

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

Esto no impide el cruce del directorio ya que 'file_name' puede contener' ../ '! Pero tu código fue útil de todos modos. – deamon

+1

Lo siento, su solución parece funcionar también. Pero estaría limitado a un único nivel de directorio (sin subdirectorios). Corregiré mi voto negativo si modifica ligeramente su respuesta para poder votar nuevamente). – deamon

+0

@daemon 'os.path.basename' strips' ../ 'de la ruta de acceso. Verifique la respuesta de actualización. –

2

Hay una solución mucho más simple aquí:

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

relpath se encarga de la trayectoria de normalización para nosotros. Y si la ruta relativa comienza con .., entonces no la permite.

Cuestiones relacionadas