我的代码是否会阻止目录遍历?

dea*_*mon 17 python security wsgi directory-traversal

来自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
Run Code Online (Sandbox Code Playgroud)

我安装的应用程序下http://localhost:8000/file/{file}并送与URL的请求http://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
Run Code Online (Sandbox Code Playgroud)

jte*_*ace 18

您的代码不会阻止目录遍历.您可以使用os.path模块来防范这种情况.

>>> import os.path
>>> os.curdir
'.'
>>> startdir = os.path.abspath(os.curdir)
>>> startdir
'/home/jterrace'
Run Code Online (Sandbox Code Playgroud)

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'
Run Code Online (Sandbox Code Playgroud)

我们现在将它们的路径转换为相对于起始路径的绝对路径.由于这不在起始路径中,因此它没有我们的起始路径的前缀.

>>> os.path.commonprefix([requested_path, startdir])
'/'
Run Code Online (Sandbox Code Playgroud)

您可以在代码中检查这一点.如果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
Run Code Online (Sandbox Code Playgroud)

  • 只是不要依赖于相对于当前工作目录的事情,因为它可以是Web应用程序中的任何内容.应始终将其作为起始点的绝对路径,从硬连线或从\ _\_ _ file\_\_ _计算. (2认同)

Clo*_*eto 5

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

file_name = request.path_params["file"]
file_name = os.path.basename(file_name)
file = open(os.path.join("/path", file_name), "rb")
Run Code Online (Sandbox Code Playgroud)

os.path.basename../从路径中剥离:

>>> os.path.basename('../../filename')
'filename'
Run Code Online (Sandbox Code Playgroud)

  • os.path.basename确实会剥离../`,但也会剥离所有与路径有关的信息。os.path.basename(“ a / b / c”)返回“ c” (2认同)
  • @Flimm:是的,这是有意的。 (2认同)