【发布时间】:2011-04-18 06:58:58
【问题描述】:
我喜欢用 Python 编写一个模板系统,它允许包含文件。
例如
这是一个模板 您可以使用 safe_include`othertemplate.rst` 安全地包含文件如您所知,包含文件可能很危险。例如,如果我在允许用户创建自己的模板的 Web 应用程序中使用模板系统,他们可能会执行类似
我想要你的密码:safe_include`/etc/password`因此,我必须将文件的包含限制为例如位于某个子目录中的文件(例如/home/user/templates)
现在的问题是:如何检查/home/user/templates/includes/inc1.rst 是否在/home/user/templates 的子目录中?
下面的代码会工作并且安全吗?
import os.path
def in_directory(file, directory, allow_symlink = False):
#make both absolute
directory = os.path.abspath(directory)
file = os.path.abspath(file)
#check whether file is a symbolic link, if yes, return false if they are not allowed
if not allow_symlink and os.path.islink(file):
return False
#return true, if the common prefix of both is equal to directory
#e.g. /a/b/c/d.rst and directory is /a/b, the common prefix is /a/b
return os.path.commonprefix([file, directory]) == directory
只要allow_symlink 为False,我认为它应该是安全的。如果用户能够创建此类链接,则允许符号链接当然会使其不安全。
更新 - 解决方案
如果中间目录是符号链接,则上面的代码不起作用。
为了防止这种情况,您必须使用realpath 而不是abspath。
更新:在目录后面添加 / 以解决 commonprefix() Reorx 指出的问题。
这也使得allow_symlink 变得不必要,因为符号链接会扩展到它们的真实目的地
import os.path
def in_directory(file, directory):
#make both absolute
directory = os.path.join(os.path.realpath(directory), '')
file = os.path.realpath(file)
#return true, if the common prefix of both is equal to directory
#e.g. /a/b/c/d.rst and directory is /a/b, the common prefix is /a/b
return os.path.commonprefix([file, directory]) == directory
【问题讨论】:
-
最后一个函数也不行,看这个输入输出:
>>> in_directory('/usr/var2/log', '/usr/var') True ',commonprefix不应该被信任。 -
最佳答案在问题中......
-
os.path.commonprefix(..)被编写为逐个字符操作的原因违反常识,但这确实是documentation for it 所说的。 -
tl;dr: 对于那些仍然停留在 Python 3.4 上的人,请参阅 jme 的 inefficient
pathlib-based answer;对于其他所有人,请参阅 Tom Bull 的 efficientcommonpath-based answer。忽略嵌入在这个问题中的答案和接受的答案——所有这些都是公然错误的。
标签: python security validation filesystems