python--基础知识点--eval、exec、compile

1.eval函数

(1) 函数的作用

计算指定表达式的值。也就是说它要执行的python代码只能是单个表达式(注意eval不支持任何形式的赋值、循环、条件等语句),而不能是复杂的代码逻辑。

(2) 函数原型

eval(source, globals=None, locals=None)

(3) 参数说明

source:必选参数,可以是字符串,也可以是一个任意的code(代码)对象实例(可以通过complie函数创建)。如果它是一个字符串,它会被当作一个(使用globals和locals参数作为全局和本地命名空间的)python表达式进行分析和解释。
globals :用于指定运行时的全局命名空间(存放全局变量),类型是字典,缺省时使用的是当前模块的内置命名空间。
locals: 指定运行时的局部命名空间(存放局部变量),类型是字典,缺省时使用 globals 的值。

两者都缺省时,那么它们将取eval函数被调用环境下的全局命名空间和局部命名空间。值得注意的是,这两者不代表真正的命名空间,只在运算时起作用,运算后则销毁。

(4)返回值

(i) source是一个code对象,且创建该code对象时,complie函数的mode参数是‘exec’,那么eval()函数的返回值是None。

(ii) source是一个输出语句,如print(),则eval返回结果为None。

(iii) rsource表达式的结果就是eval函数的返回值。

(5) 示例
x = 10


def func():
    y = 20   # 局部变量y
    a = eval("x+y")
    print("a:", a)  # x没有就调用全局变量
    b = eval("x+y", {"x": 1, "y": 2})  # 定义局部变量,优先调用
    print("b:", b)
    # e = eval('x + y', locals={'x': 1, 'y': 2})  # 这种情况不存在,会报错TypeError: eval() takes no keyword arguments
    c = eval("x+y", {"x": 1, "y": 2}, {"y": 3, "z": 4})
    print("c:", c)
    d = eval("print(x,y)")
    print("d:", d)   # 对于变量d,因为print()函数不是一个计算表达式,因此没有返回值


func()


"""
运行结果:
a: 30
b: 3
c: 4
10 20
d: None

Process finished with exit code 0
"""

2.exec函数

(1) 函数的作用

动态执行python代码。也就是说exec可以执行复杂的python代码,而不像eval函数那样只能计算一个表达式的值。

(2) 函数原型

exec(source, globals=None, locals=None)

(3) 参数说明

source:必选参数,表示需要被指定的python代码。它必须是字符串或code对象。如果source是一个字符串,该字符串会先被解析为一组python语句,然后执行。如果source是一个code对象,那么它只是被简单的执行。

globals:意义与 eval() 的globals意义和作用相同。

locals:的意义与 eval() 的locals意义和作用相同。

(4)返回值

exec函数的返回值永远为None。

(5) 示例
# 例1:我们把eval中的例子拿过来执行
x = 10


def func():
    y = 20   # 局部变量y
    a = exec("x+y")
    print("a:", a)  # x没有就调用全局变量
    b = exec("x+y", {"x": 1, "y": 2})  # 定义局部变量,优先调用
    print("b:", b)
    # e = exec('x + y', locals={'x': 1, 'y': 2})  # 这种情况不存在,会报错TypeError: exec() takes no keyword arguments
    c = exec("x+y", {"x": 1, "y": 2}, {"y": 3, "z": 4})
    print("c:", c)
    d = exec("print(x,y)")
    print("d:", d)   # 对于变量d,因为print()函数不是一个计算表达式,因此没有返回值


func()


"""
运行结果:
a: None
b: None
c: None
10 20
d: None

Process finished with exit code 0
"""
# 例2:
x = 10
expr = """
z = 30
sum = x + y + z   #一大包代码
print(sum)
"""


def func():
    y = 20
    exec(expr)   # 10+20+30
    exec(expr, {'x': 1, 'y': 2})  # 1+2+30
    # exec(expr, locals={'x': 1, 'y': 2})  # 这种情况不存在,会报错TypeError: exec() takes no keyword arguments
    exec(expr, {'x': 1, 'y': 2}, {'y': 3, 'z': 4})  # 1+3+30,x是定义全局变量1,y是局部变量


func()


"""
运行结果:
60
33
34

Process finished with exit code 0
"""
(6) eval()函数和exec()函数的区别

eval()函数只能计算单个表达式的值,而exec函数可以动态运行代码段。

eval()函数可以有返回值,而exec函数返回值永远为None。

3.complie函数

(1) 函数的作用
(2) 函数原型

compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)

(3) 参数说明

source:字符串或AST对象,表示需要进行编译的python代码

filename:指定需要编译的代码文件,如果不是文件读取代码则传递一些可辨认的值。

mode:用于标识必须当做那类代表来编译;如果source是由一个代码语句序列组成,则指定mode=‘exec’,如果source由单个表达式组成,则指定mode=‘eval’;如果source是由一个单独的交互式语句组成,则指定modo=‘single’。必须要制定,不然肯定会报错。

(4) 实例
s = """              #一大段代码
for x in range(10):
    print(x, end='')  
print()
"""
code_exec = compile(s, '', 'exec')   # 必须要指定mode,指定错了和不指定就会报错。
code_eval = compile('10 + 20', '', 'eval')   # 单个表达式
code_single = compile('name = input("Input Your Name: ")', '', 'single')   # 交互式

a = exec(code_exec)  # 使用的exec,因此没有返回值
b = eval(code_eval)

c = exec(code_single)  # 交互
d = eval(code_single)  # 交互 覆盖掉上条语句exec(code_single)中的name值

print('a: ', a)
print('b: ', b)
print('c: ', c)
print('name: ', name)
print('d: ', d)
print('name; ', name)


"""
运行结果:
0123456789
Input Your Name: zhangsan
Input Your Name: lisi
a:  None
b:  30
c:  None
name:  lisi
d:  None
name;  lisi

Process finished with exit code 0
"""

4、为什么要慎用 eval ?

很多动态的编程语言中都会有 eval函数,作用大同小异,但是,无一例外,人们会告诉你说,避免使用它。

为什么要慎用 eval呢?主要出于安全考虑,对于不可信的数据源,eval 函数很可能会招来代码注入的问题。

>>> eval("__import__('os').system('whoami')")
desktop-fa4b888\pythoncat
>>> eval("__import__('subprocess').getoutput('ls ~')")
#结果略,内容是当前路径的文件信息

在以上例子中,我的隐私数据就被暴露了。而更可怕的是,如果将命令改为rm -rf ~ ,那当前目录的所有文件都会被删除干净。

针对以上例子,有一个限制的办法,即指定 globals 为 {‘builtins’: None} 或者 {‘builtins’: {}} 。

>>> s = {'__builtins__': None}
>>> eval("__import__('os').system('whoami')", s)
#报错:TypeError: 'NoneType' object is not subscriptable

__builtins__ 包含了内置命名空间中的名称,在控制台中输入 dir(__builtins__) ,就能发现很多内置函数、异常和其它属性的名称。在默认情况下,eval 函数的 globals 参数会隐式地携带__builtins__ ,即使是令 globals 参数为 {} 也如此,所以如果想要禁用它,就得显式地指定它的值。

上例将它映射成 None,就意味着限定了 eval 可用的内置命名空间为 None,从而限制了表达式调用内置模块或属性的能力。

但是,这个办法还不是万无一失的,因为仍有手段可以发起攻击。

某位漏洞挖掘高手在他的博客中分享了一个思路,令人大开眼界。其核心的代码是下面这句,你可以试试执行,看看输出的是什么内容。

>>> ().__class__.__bases__[0].__subclasses__()

关于这句代码的解释,以及更进一步的利用手段,详见博客。(地址:https://www.tuicool.com/articles/jeaqe2n)

另外还有一篇博客,不仅提到了上例的手段,还提供了一种新的思路:

# 警告:千万不要执行如下代码,后果自负。
>>> eval('(lambda fc=(lambda n: [c 1="c" 2="in" 3="().__class__.__bases__[0" language="for"][/c].__subclasses__() if c.__name__ == n][0]):fc("function")(fc("code")(0,0,0,0,"KABOOM",(),(),(),"","",0,""),{})())()', {"__builtins__":None})

这行代码会导致 Python 直接 crash 掉。具体分析在:https://segmentfault.com/a/1190000011532358

除了黑客的手段,简单的内容也能发起攻击。像下例这样的写法, 将在短时间内耗尽服务器的计算资源。

>>> eval("2 ** 888888888", {"__builtins__":None}, {})

如上所述,我们直观地展示了 eval() 函数的危害性,然而,即使是 Python 高手们小心谨慎地使用,也不能保证不出错。

在官方的 dumbdbm 模块中,曾经(2014年)发现一个安全漏洞,攻击者通过伪造数据库文件,可以在调用 eval() 时发起攻击。(详情:https://bugs.python.org/issue22885)

无独有偶,在上个月(2019.02),有核心开发者针对 Python 3.8 也提出了一个安全问题,提议不在 logging.config 中使用 eval() 函数,目前该问题还是 open 状态。(详情:https://bugs.python.org/issue36022)

如此种种,足以说明为什么要慎用 eval() 了。同理可证,exec() 函数也得谨慎使用。

5、安全的替代用法

既然有种种安全隐患,为什么要创造出这两个内置方法呢?为什么要使用它们呢?

理由很简单,因为 Python 是一门灵活的动态语言。与静态语言不同,动态语言支持动态地产生代码,对于已经部署好的工程,也可以只做很小的局部修改,就实现 bug 修复。

那有什么办法可以相对安全地使用它们呢?

ast模块的 literal() 是 eval() 的安全替代,与 eval() 不做检查就执行的方式不同,ast.literal() 会先检查表达式内容是否有效合法。它所允许的字面内容如下:

strings, bytes, numbers, tuples, lists, dicts, sets, booleans, 和 None

一旦内容非法,则会报错:

import ast
ast.literal_eval("__import__('os').system('whoami')")

报错:ValueError: malformed node or string
不过,它也有缺点:AST 编译器的栈深(stack depth)有限,解析的字符串内容太多或太复杂时,可能导致程序崩溃。

至于 exec() ,似乎还没有类似的替代方法,毕竟它本身可支持的内容是更加复杂多样的。

最后是一个建议:搞清楚它们的区别与运行细节(例如前面的局部命名空间内容),谨慎使用,限制可用的命名空间,对数据源作充分校验。

[参考博客]
https://www.cnblogs.com/yangmingxianshen/p/7810496.html
https://www.cnblogs.com/pythonista/p/10590682.html

你可能感兴趣的:(python,#,基础知识点,python)