18

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},并使用 URLhttp://localhost:8000/file/../alarm.gifhttp://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
4

3 回答 3

19

您的代码不会阻止目录遍历。您可以使用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
于 2011-07-23T22:26:46.273 回答
4

仅使用用户输入文件的基本名称:

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'
于 2011-07-23T23:35:49.440 回答
2

这里有一个更简单的解决方案:

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

relpath为我们处理规范化路径。如果相对路径以 开头..,那么你不允许它。

于 2016-11-08T17:24:15.020 回答