Eval可以用来转换String表达式到具体的表达式
配一些动态规则等,eval会用的比较多,但eval安全性比较低,容易被执行恶意代码。
例如下面的代码执行结果
对应的FastAPI如下:
@app.get("/")
async def root():
try:
ast_res = eval("__import__('os').system('dir /b')")
except Exception as e:
logger.error(e)
return {"code":"0x5",
"message": str(e)
}
return {"message": ast_res}
dir /b命令被成功执行了...
之所以能被执行,是因为eval可以访问内置函数,也就是__import__方法,import了os并执行了命令。
globals是没法限制的,虽然说比较直接的方法是 {‘builtins’: None} ,但只能防脚本小子,也可以通过().__class__.__bases__[0].__subclasses__()执行一些恶意代码,对于稍微懂一点python底层知识的用户来说,完全没有任何阻挡效果,所以聊胜于无
那么,其实拒绝__XXXX__并且给一些比较危险的方法上黑名单就好了。
所以很简单用正则匹配就好了,但不要直接匹配表达式。
def my_safe_eval(string,dict) :
code = compile(string,'<user input>','eval')
reason = None
banned = ('eval','compile','exec','getattr','hasattr','setattr','delattr',
'classmethod','globals','help','input','isinstance','issubclass','locals',
'open','print','property','staticmethod','vars')
for name in code.co_names:
if re.search(r'^__\S*__$',name):
reason = 'dunder attributes not allowed'
elif name in banned:
reason = 'arbitrary code execution not allowed'
if reason:
raise NameError(f'{name} not allowed : {reason}')
return eval(code,dict)
原理:co_names过滤
其实目前发现有Evalidate库,但好像对很多表达式解析不好,不过也很强大
还有基于ast的,但是限制也比较多
总而言之上面是比较完善的一种方法,当然没有万无一失的策略。还是最好从根源限制能访问eval的用户,确保是可信的。
参考:A simple, kind-of "safe" eval ? : learnpython (reddit.com)