如何仅使用相对路径进行连接?
为了创建一个简单的网页服务器脚本,我写了一个函数,用来把网址转化为文件系统中的路径。
def resolve(url):
url = url.lstrip('/')
path = os.path.abspath(os.path.join(os.path.dirname(__file__), url))
return path
下面是一些例子,展示了当__file__
变量是C:\projects\resolve.py
时的输出结果。
/index.html => C:\projects\index.html
/\index.html => C:\index.html
/C:\index.html => C:\index.html
第一个例子是正常的。网址被转化为脚本所在目录中的一个文件。但是,第二个和第三个例子让我感到意外。因为附加的路径被当作绝对路径来处理,这样就完全忽略了脚本文件所在的目录。
这就带来了安全隐患,因为这样可以访问文件系统中的所有文件,而不仅仅是脚本子目录中的文件。为什么Python的os.path.join
会允许和绝对路径连接?我该如何防止这种情况发生呢?
1 个回答
3
os.path.join()
这个函数不适合处理不安全的输入,没错。它的设计就是这样的:当你给它一个绝对路径时,它会忽略之前的参数。这种设计让你可以在配置文件中同时支持绝对路径和相对路径,而不需要去检查用户输入的路径。只要用 os.path.join(standard_location, config_path)
,它会自动帮你处理好。
你可以看看 Flask的 safe_join()
,这个函数可以用来处理不可信的文件名:
import posixpath
import os.path
_os_alt_seps = list(sep for sep in [os.path.sep, os.path.altsep]
if sep not in (None, '/'))
def safe_join(directory, filename):
# docstring omitted for brevity
filename = posixpath.normpath(filename)
for sep in _os_alt_seps:
if sep in filename:
raise NotFound()
if os.path.isabs(filename) or \
filename == '..' or \
filename.startswith('../'):
raise NotFound()
return os.path.join(directory, filename)
这个函数使用 posixpath
(这是一个与平台无关的 os.path
模块的实现)来先规范化URL路径;这样可以去掉任何嵌入的 ../
或 ./
路径部分,确保它是一个完全规范的相对路径或绝对路径。
然后,它会排除任何其他的分隔符,比如说你不能用 /\index.html
。最后,绝对文件名和相对文件名也是明确禁止的。