我在某处发现了类似的代码:
USER_CONTROLLED = 'a'
open("settings.py", "w").write("USER_CONTROLLED = %s" % eval(repr(a)))
在另一个档案中:
import settings
x = settings.USER_CONTROLLED * [0]
这是一个安全漏洞吗?
答案 0 :(得分:2)
与你在IRC上被告知的情况相反,肯定会有x
使eval(repr(x))
变得危险,所以说没有任何限制也是如此。
想象一下以不同方式实现__repr__
的自定义对象。文档在__repr__
上说它“应该看起来像一个有效的Python表达式,可用于重新创建具有相同值的对象”。但是没有什么可以强制执行这个指南。
相反,我们可以创建一个具有自定义__repr__
的类,该类返回一个字符串,在计算时运行任意代码。例如:
class MyObj:
def __repr__ (self):
return "__import__('urllib.request').request.urlopen('http://example.com').read()"
在该类型的对象上调用repr()
表明它返回的字符串肯定可以被评估:
>>> repr(MyObj())
"__import__('urllib.request').request.urlopen('http://example.com').read()"
在这里,这只涉及向example.com发出请求。但正如您所看到的,我们可以在此处导入任意模块并使用它们运行代码。而且该代码可能有任何副作用。所以这绝对是危险的。
但是,如果我们将x
限制为我们知道调用它们repr()
的已知类型,那么我们确实可以说什么时候用它来运行任意代码是不可能的。例如,如果x
是一个字符串,那么unicode_repr
的实现可确保所有内容都被正确转义,并且评估该对象的repr()
将始终返回正确的字符串(甚至等于x
),没有任何副作用。
因此我们应该在评估之前检查类型:
if type(a) is not str:
raise Exception('Only strings are allowed!')
something = eval(repr(a))
请注意,我们不会在此处使用isinstance
来执行继承感知类型检查。因为我绝对可以使MyObj
继承自str
:
>>> x = MyObj()
>>> isinstance(x, str)
True
>>> type(x)
<class '__main__.MyObj'>
所以你应该在这里测试具体的类型。
请注意,对于字符串,实际上没有理由调用eval(repr(x))
,因为如上所述,这将导致x
本身。所以你可以直接指定x
。
然而,在您的实际用例中,您确实存在一个非常大的安全问题。您希望创建变量赋值并将该代码存储在Python文件中,以便稍后由实际的Python解释器运行。因此,您应该绝对确保赋值的右侧不是任意代码,而是实际上是字符串的repr:
>>> a = 'runMaliciousCode()'
>>> "USER_CONTROLLED = %s" % eval(repr(a))
'USER_CONTROLLED = runMaliciousCode()'
>>> "USER_CONTROLLED = %s" % repr(a)
"USER_CONTROLLED = 'runMaliciousCode()'"
如您所见,评估repr()
会将实际内容放在作业的右侧(因为它等同于"…" % a
)。但是,当您导入该文件时,这可能导致恶意代码运行。所以你应该在那里插入字符串的repr,完全忘记完全使用eval
。