- Preface 前言
- 第1章 引论
- 第2章 编程惯用法
- 第3章 基础语法
- 建议19:有节制地使用 from…import 语句
- 建议20:优先使用 absolute import 来导入模块
- 建议21:i+=1 不等于 ++i
- 建议22:使用 with 自动关闭资源
- 建议23:使用 else 子句简化循环(异常处理)
- 建议24:遵循异常处理的几点基本原则
- 建议25:避免 finally 中可能发生的陷阱
- 建议26:深入理解 None 正确判断对象是否为空
- 建议27:连接字符串应优先使用 join 而不是 +
- 建议28:格式化字符串时尽量使用 .format 方式而不是 %
- 建议29:区别对待可变对象和不可变对象
- 建议30:[]、() 和 {}:一致的容器初始化形式
- 建议31:记住函数传参既不是传值也不是传引用
- 建议32:警惕默认参数潜在的问题
- 建议33:慎用变长参数
- 建议34:深入理解 str() 和 repr() 的区别
- 建议35:分清 staticmethod 和 classmethod 的适用场景
- 第4章 库
- 建议36:掌握字符串的基本用法
- 建议37:按需选择 sort() 或者 sorted()
- 建议38:使用 copy 模块深拷贝对象
- 建议39:使用 Counter 进行计数统计
- 建议40:深入掌握 ConfigParser
- 建议41:使用 argparse 处理命令行参数
- 建议42:使用 pandas 处理大型 CSV 文件
- 建议43:一般情况使用 ElementTree 解析 XML
- 建议44:理解模块 pickle 优劣
- 建议45:序列化的另一个不错的选择 JSON
- 建议46:使用 traceback 获取栈信息
- 建议47:使用 logging 记录日志信息
- 建议48:使用 threading 模块编写多线程程序
- 建议49:使用 Queue 使多线程编程更安全
- 第5章 设计模式
- 第6章 内部机制
- 建议54:理解 built-in objects
- 建议55:init() 不是构造方法
- 建议56:理解名字查找机制
- 建议57:为什么需要 self 参数
- 建议58:理解 MRO 与多继承
- 建议59:理解描述符机制
- 建议60:区别 getattr() 和 getattribute() 方法
- 建议61:使用更为安全的 property
- 建议62:掌握 metaclass
- 建议63:熟悉 Python 对象协议
- 建议64:利用操作符重载实现中缀语法
- 建议65:熟悉 Python 的迭代器协议
- 建议66:熟悉 Python 的生成器
- 建议67:基于生成器的协程及 greenlet
- 建议68:理解 GIL 的局限性
- 建议69:对象的管理与垃圾回收
- 第7章 使用工具辅助项目开发
- 第8章 性能剖析与优化
建议14:警惕 eval() 的安全漏洞
如果你了解JavaScript或者PHP等,那么你一定对eval()所有了解。如果你并没有接触过也没关系,eval()函数的使用非常简单。
>>> eval("1+1==2") # 进行判断 True >>> >>> eval("'A'+'B'") # 字符连接 'AB' >>> eval("1+2") # 数字相加 3 >>>
Python中eval()函数将字符串str当成有效的表达式来求值并返回计算结果。其函数声明如下:
eval(expression[, globals[, locals]])
其中,参数globals为字典形式,locals为任何映射对象,它们分别表示全局和局部命名空间。如果传入globals参数的字典中缺少__builtins__的时候,当前的全局命名空间将作为globals参数输入并且在表达式计算之前被解析。locals参数默认与globals相同,如果两者都省略的话,表达式将在eval()调用的环境中执行。
“eval is evil”(eval是邪恶的),这是一句广为人知的对eval的评价,它主要针对的是eval()的安全性。那么eval存在什么样的安全漏洞呢?来看一个简单的例子:
import sys from math import * def ExpCalcBot(string): try: print 'Your answer is',eval(user_func) # 计算输入的值 except NameError: print "The expression you enter is not valid" print 'Hi,I am ExpCalcBot. please input your experssion or enter e to end' inputstr ='' while 1: print 'Please enter a number or operation. Enter c to complete. :' inputstr = raw_input() if inputstr == str('e') : # 遇到输入为e 的时候退出 sys.exit() elif repr(inputstr) != repr(''): ExpCalcBot(inputstr) inputstr = ''
上面这段代码的主要功能是:根据用户的输入,计算Python表达式的值。它有什么问题呢?如果用户都是素质良好,没有不良目的的话,那么这段程序也许可以满足基本需求。比如,输入1+sin(20)会输出结果1.91294525073。但如果它是一个Web页面的后台调用(当然,你需要做一定的修改),由于网络环境下运行它的用户并非都是可信任的,问题就出现了。因为eval()可以将任何字符串当做表达式求值,这也就意味着有空子可钻。上面的例子中假设用户输入__import__("os").system("dir"),会有什么样的输出呢?你会惊讶地发现它会显示当前目录下的所有文件列表,输出如下:
C:\test>python tests.py Hi,I am ExpCalcBot. please input your experssion or enter e to end Please enter a number or operation. Enter c to complete. : __import__("os").system("dir") Your answer is 驱动器 C 中的卷是 SYSTEM 卷的序列号是 A0E0-1A46 C:\test 的目录 2013/07/31 12:28 <DIR> . 2013/07/31 12:28 <DIR> .. 2012/07/24 08:11 859,919 2012-07-24-010.jpg 2012/07/24 08:11 1,037,015 2012-07-24-011.jpg 2012/07/24 08:19 1,242,667 2012-07-24-012.jpg 2013/07/31 12:26 0 test.txt 2013/07/31 12:27 503 tests.py 5 个文件 3,140,104 字节 2 个目录 422,254,702,592 可用字节 0 Please enter a number or operation. Enter c to complete. :
于是顿时,有人的“坏心眼”来了,他输入了如下字符串,可悲的事情发生了,当前目录下的所有文件都被删除了,包括test.py,而这一切没有任何提示,悄无声息。
__import__("os").system("del * /Q") !!!不要轻易在你的计算机上尝试 Your answer is 0
试想,在网络环境下这是不是很危险?也许你会辩护,那是因为你没有在globals参数中禁止全局命名空间的访问。好,我们按照你说的来试验一下:将函数ExpCalcBot修改一下,其中math_fun_list限定为几个常用的数学函数。修改后的函数如下:
def ExpCalcBot(string): try: math_fun_list = ['acos', 'asin', 'atan', 'cos', 'e','log', 'log10','pi', 'pow', 'sin', 'sqrt', 'tan'] math_fun_dict = dict([ (k, globals().get(k)) for k in math_fun_list ]) # 形成可以访问的函数的字典 print 'Your answer is',eval(string,{"__builtins__": None},math_fun_dict) except NameError: print "The expression you enter is not valid"
再次运行程序(请读者自行试验)你会惊喜地发现上面的命令被看着无效表达式,你的辩护是对的,这确实是我们想要的。很好,安全问题不再是个问题!但仔细想想真是这样的吗?试试输入以下字符:
[c for c in ().__class__.__bases__[0].__subclasses__() if c.__name__ =='Quitter'][0](0)()
# ().__class__.__bases__[0].__subclasses__()用来显示object类的所有子类。类Quitter与"quit"功能绑定,因此上面的输入会直接导致程序退出。
注:你可以在Python的安装目录下的Lib\site.py中找到其类的定义。读者也可以自行在Python解释器中输入print().__class__.__bases__[0].__subclasses__()看看输出结果是什么。
因此对于有经验的侵入者来说,他可能会有一系列强大的手段,使得eval可以解释和调用这些方法,从而带来更大的破坏。此外,eval()函数也给程序的调试带来一定困难,要查看eval()里面表达式具体的执行过程很难。因此在实际应用过程中如果使用对象不是信任源,应该尽量避免使用eval,在需要使用eval的地方可用安全性更好的ast.literal_eval替代。literal_eval函数具体详情可以参考文档http://docs.python.org/2/library/ast.html#ast.literal_eval。上面的例子请读者使用literal_eval自行试验,你会体会得更加深刻。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论