Eval代码解析
تم الإنشاء في: ١٨ يناير ٢٠٢٥
تم الإنشاء في: ١٨ يناير ٢٠٢٥
builtins.getattr(builtins.getattr(builtins.dict,'get'(builtins.golbals(),'builtins'),'eval')(command)是什么意思,详细解释一下
这段代码看起来有点绕,其本质是想通过一系列“间接调用”,最终执行了 eval(command)。
下面让我们一步步拆解这行代码(我在此将其稍微排版一下,便于说明):
pythonbuiltins.getattr( builtins.getattr( builtins.dict, 'get'(builtins.globals(), 'builtins'), 'eval' )(command) )
注意:这段代码里写法有些不规范,尤其在
'get'(builtins.globals(), 'builtins')这个位置,
实际上我们常见的是getattr(builtins.dict, 'get')(some_dict, 'some_key')的形式。
也就是先用getattr(builtins.dict, 'get')拿到dict.get方法,然后再调用它。
不过为了说明原理,我们先把它当作是“dict.get被取出并调用”的意思理解即可。
builtins.dict这是 Python 内置模块 builtins 中的 dict 类,代表 Python 的字典类型。
'get'(builtins.globals(), 'builtins')如果我们把它理解为:
pythonget_method = getattr(builtins.dict, 'get') # 相当于拿到了 dict.get 这个函数 result = get_method(builtins.globals(), 'builtins')
builtins.globals():理论上这里是想拿到当前全局作用域(一个字典),即 globals()。builtins.globals() 这种写法(globals() 直接就是内置函数)。dict.get(一个字典, 'builtins'):从这个全局字典里取出键 'builtins' 对应的值,一般情况下就是 builtins 模块本身。换句话说,这一句的目的是从全局命名空间里找到 builtins 模块对象。
builtins.getattr( …, 'eval')拿到 builtins 模块后,再通过 getattr(builtins, 'eval'),就相当于得到了 eval 这个函数的引用。
…(command)最后,对上一步拿到的 eval 函数执行 (command),也就是 eval(command)。
从上面的分步可以看出,这段“长得很奇怪”的调用链条,本质上就是为了绕过直接写 eval(command),而是通过一大串“取属性”+“取内置字典”+“获取 eval 函数”……最后仍然是执行了 eval(command)。
在有些环境下,人们可能会用这种方式绕过一些安全检查(因为直接出现 eval 关键字可能会被检测),或者在某些场景下进行混淆,让阅读者不易一眼看出最终的含义。
总结:
builtins.getattr(builtins.getattr(builtins.dict, 'get'(builtins.globals(),'builtins'), 'eval')(command)
就是在比较绕的方式下调用了eval(command),最终效果是执行command(当作 Python 表达式/代码)对应的字符串。
builtins.getattr(
builtins.dict,
'get'(builtins.globals(), 'builtins'),
'eval'
)
为什么这里getattr内有三个参数?是怎么一回事?
builtins.dict, 'get'(builtins.globals(), 'builtins'这个直接就能执行得到builtins的操作吗?
首先要说明的是,在标准的 Python 语法中,getattr 的常见用法是:
pythongetattr(obj, name[, default])
obj 和 namedefault,如果提供该参数并且 obj 没有名为 name 的属性,则返回 default 而不是抛出异常。因此,通常 我们会看到两种形式:
getattr(obj, "some_attr")getattr(obj, "some_attr", default_value)题目中的代码(带有一些不标准的写法/排版)如下:
pythonbuiltins.getattr( builtins.dict, 'get'(builtins.globals(), 'builtins'), 'eval' )
直觉上,这好像把“getattr”的三个形参写成了:
obj = builtins.dictname = 'get'(builtins.globals(), 'builtins')default = 'eval'但这种写法本身在正常的 Python 里是会出错的——'get'(...) 并不是合法的 Python 调用,因为 'get' 只是个字符串,而字符串不可以直接像函数那样调用(除非经过某些非常规的魔术改造,通常并不存在)。
更合理的读法应该是先用 getattr(builtins.dict, 'get') 拿到 dict.get 方法,再调用这个方法,然后再把得到的结果作为第一个参数传给下一层 getattr(...)。也就是说,应当是下面这样的结构(我给它加上括号,帮助理解):
pythonbuiltins.getattr( # ┌────────────────────────┐ # │1. 调用 dict.get(...) │ # │ 并把结果传进来 │ # └────────────────────────┘ builtins.getattr(builtins.dict, 'get')( builtins.globals(), 'builtins' ), # ┌────────────────────────┐ # │2. 要获取的属性名字 'eval' # └────────────────────────┘ 'eval' )
这样就很清晰了:
getattr(builtins.dict, 'get') 就是 dict.get 方法。dict.get(builtins.globals(), 'builtins') → 这一步返回的是当前全局变量(globals())里名为 'builtins' 的那个值,大多数情况下就是内置模块 builtins。builtins.getattr( 这一步返回的对象, 'eval'),也就是 getattr(builtins, 'eval') → 取到内置函数 eval。最后,这段大张旗鼓的代码,其实得到的就是 builtins.eval 这个函数引用。
getattr 传了三个参数?'get'(builtins.globals(), 'builtins') 看成整体是字符串 'get'(第二实参)和 '(builtins.globals(), 'builtins')'(好像当成第三实参),那就会产生歧义或语法错误。'get' 并不是直接传给 getattr,而是先做了 getattr(builtins.dict, 'get'),这会返回一个可调用的函数(即 dict.get),然后再 (builtins.globals(), 'builtins') 调用它,把返回值作为**getattr 的第一个参数**;'eval' 作为**getattr 的第二个参数**。换言之,真正的函数调用顺序是:
builtins.getattr(builtins.dict, 'get')(...)(builtins.globals(), 'builtins') → 获得 builtins 模块builtins 模块做 getattr(..., 'eval')它只是被写成了一种非常混淆的格式,让你一眼看上去好像 getattr 有三个参数分别是 (builtins.dict, 'get'(builtins.globals(), 'builtins'), 'eval'),从而容易混淆。
getattr 有三个参数,但实际上 'get'(builtins.globals(), 'builtins') 那一坨不是 getattr 的第二实参,而是先取到了 dict.get 并调用返回 builtins,然后将该 builtins 作为 getattr 的第一个参数,'eval' 才是第二实参。eval 函数。