Мой код предотвращает обратный путь в каталогах? - PullRequest
17 голосов
/ 24 июля 2011

Безопасен ли следующий фрагмент кода из приложения Python WSGI от обхода каталога? Он читает имя файла, переданное в качестве параметра, и возвращает указанный файл.

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

Я смонтировал приложение под http://localhost:8000/file/{file} и отправил запросы с URL http://localhost:8000/file/../alarm.gif и http://localhost:8000/file/%2e%2e%2falarm.gif. Но ни одна из моих попыток не доставила (существующий) файл. Так мой код уже защищен от обхода каталога?

Новый подход

Кажется, что следующий код предотвращает обратный путь в каталогах:

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

Ответы [ 3 ]

18 голосов
/ 24 июля 2011

Ваш код не предотвращает обратный путь в каталогах. Вы можете защититься от этого с помощью модуля os.path .

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

startdir теперь является абсолютным путем, где вы не хотите, чтобы этот путь выходил за пределы. Теперь предположим, что мы получаем имя пользователя от пользователя, а нам дается злонамеренный /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'

Мы теперь преобразовали их путь в абсолютный путь относительно нашего начального пути. Поскольку этого не было в начальном пути, у него нет префикса нашего начального пути.

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

Вы можете проверить это в своем коде. Если функция commonprefix возвращает путь, который не начинается с startdir, то этот путь недопустим, и вы не должны возвращать содержимое.


Вышеприведенное может быть преобразовано в статический метод следующим образом:

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
5 голосов
/ 24 июля 2011

Использовать только базовое имя пользовательского файла:

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 полосы ../ с пути:

>>> os.path.basename('../../filename')
'filename'
2 голосов
/ 08 ноября 2016

Здесь есть гораздо более простое решение:

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

relpath заботится о нормализации пути для нас.И если относительный путь начинается с .., то вы его не разрешаете.

...