避免使用危险的文件解析和对象序列化库

许多常用的用于读取配置文件和反序列化对象的库都非常危险,因为它们可能允许执行任意代码。默认情况下,PyYAML 和 pickle 等库没有提供数据和代码的强隔离,因此允许将代码嵌入到输入中。

通常,这些库的输入是不可信的或仅部分可信的。这些不安全的输入可能来自配置文件或通过 REST API 提供。例如,我们经常使用 YAML 作为配置文件,但 YAML 文件也可以包含嵌入的 Python 代码。这可能会为攻击者提供执行代码的方法。

这些库中,许多(但不是全部)都提供了禁用启用代码执行功能的安全接口。您应该始终使用安全功能来加载输入。通常,显而易见的功能并非安全功能,我们应该查阅此处未涵盖的库的文档。

Python 库

我们经常使用 YAML、pickle 或 eval 将数据加载到我们的 Python 程序中,但这很危险。PyYAML 有一种安全的方式来加载代码,但 pickle 和 eval 没有。

模块

问题

使用

避免

PyYAML

允许创建任意 Python 对象。

yaml.safe_load

yaml.load

pickle

允许创建任意 Python 对象。

请勿使用

pickle.load, pickle.loads

cPickle

允许创建任意 Python 对象。

请勿使用

cPickle.load, cPickle.loads

eval

将所有输入作为 Python 代码运行

请勿使用

eval

exec

将所有输入作为 Python 代码运行 (Python 3.x)

请勿使用

exec

错误

yaml.load 是显而易见的功能,但它很危险

import yaml
import pickle
conf_str = '''
!!python/object:__main__.AttackerObj
key: 'value'
'''
conf = yaml.load(conf_str)

将 pickle 或 cPickle 与不可信输入一起使用可能导致任意代码执行。

import pickle
import cPickle

user_input = "cos\nsystem\n(S'cat /etc/passwd'\ntR.'\ntR."
cPickle.loads(user_input) # results in code execution
pickle.loads(user_input)  # results in code execution

同样,eval 和 exec 很难安全地用于来自不可信源的输入。

user_input = "os.system('cat /etc/passwd')"
eval(user_input) # execute python expressions

user_input = "import os; os.system('cat /etc/passwd')"
exec(user_input) # execute _any_ python code

正确

这里我们使用 PyYAML 的安全 YAML 加载功能

import yaml
conf_str = '''
- key: 'value'
- key: 'value'
'''
conf = yaml.safe_load(conf_str)

对于 pickle.load 没有安全的替代方案。然而,在大多数情况下,完全可以避免使用 pickle 来序列化数据对象。

后果

  • 任何能够控制传递给危险库的输入的人都可以在运行危险库的系统上获得任意代码执行。

参考