来自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}
下安装了应用,并通过网址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
答案 0 :(得分:18)
您的代码不会阻止目录遍历。您可以使用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
答案 1 :(得分:5)
仅使用用户输入文件的基本名称:
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 :(得分:2)
这里有一个更简单的解决方案:
relative_path = os.path.relpath(path, start=self.test_directory)
has_dir_traversal = relative_path.startswith(os.pardir)
relpath
负责为我们规范道路。如果相对路径以..
开头,那么您不允许它。