路径表达式中使用不受控制的数据¶
ID: py/path-injection
Kind: path-problem
Security severity: 7.5
Severity: error
Precision: high
Tags:
- correctness
- security
- external/cwe/cwe-022
- external/cwe/cwe-023
- external/cwe/cwe-036
- external/cwe/cwe-073
- external/cwe/cwe-099
Query suites:
- python-code-scanning.qls
- python-security-extended.qls
- python-security-and-quality.qls
使用从用户控制的数据构建的路径访问文件可能允许攻击者访问意外的资源。这会导致敏感信息被泄露或删除,或者攻击者能够通过修改意外的文件来影响行为。
建议¶
在使用用户输入构建文件路径之前验证用户输入,可以使用像 werkzeug.utils.secure_filename
这样的现成库函数,或者执行自定义验证。
理想情况下,遵循以下规则
不允许出现超过一个 “.” 字符。
不允许出现目录分隔符,例如 “/” 或 “\”(取决于文件系统)。
不要依赖于简单地替换有问题的序列,例如 “../”。例如,将此过滤器应用于 “…/…//” 后,结果字符串仍然是 “../”。
使用已知良好模式的白名单。
示例¶
在第一个示例中,文件名从 HTTP 请求中读取,然后用于访问文件。但是,恶意用户可以输入一个绝对路径的文件名,例如 "/etc/passwd"
。
在第二个示例中,似乎用户被限制在打开 "user"
主目录中的文件。但是,恶意用户可以输入包含特殊字符的文件名。例如,字符串 "../../../etc/passwd"
将导致代码读取位于 "/server/static/images/../../../etc/passwd"
的文件,即系统的密码文件。然后,此文件将被发送回用户,使他们能够访问系统的所有密码。请注意,用户也可以在这里使用绝对路径,因为 os.path.join("/server/static/images/", "/etc/passwd")
的结果是 "/etc/passwd"
。
在第三个示例中,用于访问文件系统的路径在检查已知前缀之前被规范化。这确保了无论用户输入是什么,结果路径都是安全的。
import os.path
from flask import Flask, request, abort
app = Flask(__name__)
@app.route("/user_picture1")
def user_picture1():
filename = request.args.get('p')
# BAD: This could read any file on the file system
data = open(filename, 'rb').read()
return data
@app.route("/user_picture2")
def user_picture2():
base_path = '/server/static/images'
filename = request.args.get('p')
# BAD: This could still read any file on the file system
data = open(os.path.join(base_path, filename), 'rb').read()
return data
@app.route("/user_picture3")
def user_picture3():
base_path = '/server/static/images'
filename = request.args.get('p')
#GOOD -- Verify with normalised version of path
fullpath = os.path.normpath(os.path.join(base_path, filename))
if not fullpath.startswith(base_path):
raise Exception("not allowed")
data = open(fullpath, 'rb').read()
return data