前言
Python因其在开发更大、更复杂应用程序方面独特的便捷性,使得它在计算机环境中变得越来越不可或缺。虽然其明显的语言清晰度和使用友好度使得软件工程师和系统管理员放下了戒备,但是他们的编码错误还是有可能会带来严重的安全隐患。
这篇文章的主要受众是还不太熟悉Python的人,其中会提及少量与安全有关的行为以及有经验开发人员遵循的规则。
输入函数
在Python2强大的内置函数中,输入函数完全就是一个大的安全隐患。一旦调用输入函数,任何从stdin中读取的数据都会被认定为Python代码:
$ python2
>>> input()
dir()
['__builtins__', '__doc__', '__name__', '__package__']
>>> input()
__import__('sys').exit()
$
显然,只要脚本stdin中的数据不是完全可信的,输入函数就是有危险的。Python 2 文件将 raw_input 认定为一个安全的选择。在Python3中,输入函数相当于是 raw_input,这样就可以完全修复这一问题。
assert语句
还有一条使用 assert 语句编写的代码语句,作用是捕捉 Python 应用程序中下一个不可能条件。
def verify_credentials(username, password):
assert username and password, 'Credentials not supplied by caller'
... authenticate possibly null user with null password ...
然而,Python在编译源代码到优化的字节代码 (如 python-O) 时不会有任何的assert 语句说明。这样的移除使得程序员编写用来抵御攻击的代码保护都形同虚设。
这一弱点的根源就是assert机制只是用于测试,就像是c++语言中那样。程序员必须使用其他手段才能确保数据的一致性。
可重用整数
在Python中一切都是对象,每一个对象都有一个可以通过 id 函数读取的唯一标示符。可以使用运算符弄清楚是否有两个变量或属性都指向相同的对象。整数也是对象,所以这一操作实际上是一种定义:
>>> 999+1 is 1000
False
上述操作的结果可能会令人大吃一惊,但是要提醒大家的是这样的操作是同时使用两个对象标示符,这一过程中并不会比较它们的数值或是其它任何值。但是:
>>> 1+1 is 2
True
对于这种行为的解释就是Python当中有一个对象集合,代表了最开始的几百个整数,并且会重利用这些整数以节省内存和对象创建。更加令人疑惑的就是,不同的Python版本对于“小整数”的定义是不一样的。
这里所指的缓存永远不会使用运算符进行数值比较,运算符也专门是为了处理对象标示符。
浮点数比较
处理浮点数可能是一件更加复杂的工作,因为十进制和二进制在表示分数的时候会存在有限精度的问题。导致混淆的一个常见原因就是浮点数对比有时候可能会产生意外的结果。下面是一个著名的例子:
>>> 2.2 * 3.0 == 3.3 * 2.0
False
这种现象的原因是一个舍入错误:
>>> (2.2 * 3.0).hex()
'0x1.a666666666667p+2'
>>> (3.3 * 2.0).hex()
'0x1.a666666666666p+2'
另一个有趣的发现就是Python float 类型支持无限概念。一个可能的原因就是任何数都要小于无限:
>>> 10**1000000 > float('infinity')
False
但是在Python3中,有一种类型的对象不支持无限:
>>> float > float('infinity')
True
一个最好的解决办法就是坚持使用整数算法,还有一个办法就是使用十进制内核模块,这样可以为用户屏蔽烦人的细节问题和缺陷。
一般来说,只要有任何算术运算就必须要小心舍入错误。详情可以参阅 Python 文档中的《发布和局限性》一章。