PyYaml反序列化源码浅探
0x00
今天看到关于PyYaml反序列化的一篇文章:https://xz.aliyun.com/t/7923,自己复现时候debug,发现一些和文章不一样的地方,记录一下。
预备知识先了解一下__init__、__new__和__import__函数。
0x01
__init__和__new__函数
参考:
https://www.cnblogs.com/wuxianfeng023/p/17188362.html
https://blog.csdn.net/sj2050/article/details/81172022
要点:
__new__是个静态方法,__init__是实例方法
__new__需要返回一个实例,并自动执行该实例的__init__。若不返回,则不执行。
关于这两个函数的执行条件,自己做了测试。
python版本:

测试1:
class poc:
def __new__(cls):
print('__new__')
self_in_init = super().__new__(cls)
return self_in_init
def __init__(self):
print('__init__')
poc()#先执行__new__,再执行__init__
测试2:
class poc:
def __new__(cls):
print('__new__')
self_in_init = super().__new__(cls)
def __init__(self):
print('__init__')
poc() #只执行__new__
测试3:
class poc:
def __init__(self):
print('__init__')
poc() #只执行__init__
测试4:
class poc:
def __new__(cls):
print('__new__')
self_in_init = super().__new__(cls)
return self_in_init
def __init__(self):
print('__init__')
poc.__new__(poc)#只执行__new__

测试5:
class c(str):
def __new__(cls, string):
aa = a()
return aa
def __init__(self, string):
print(id(self))
class a:
def __init__(self):
print('a: __init___ ')
c('1') #a: __init___
总结:
以括号方式创建实例,单有__new__或__init__都会执行,若两个都有,先执行__new__,然后执行__new__返回的实例的__init__。
手动用__new__创建实例,只执行他,不执行__init__。
__import__
import 和 __import__作用差不多,都是用来导包,遇到py代码则执行。
a.py文件
print('aaaaaaa')
b.py文件:
#import a 这样也可以
__import__('a')
两个文件要在一个目录下。执行b.py,会输出aaaaaaa。
0x02
复现原文章,自己的PyYaml版本和原文章一致。
tmp.py:

tmp2.py:

先执行tmp,再执行tmp2,两次执行都会弹计算器。
第二次弹计算器,会想到是创建了poc实例,执行了__init__。我们再测试一遍:
tmp.py:

tmp2.py不修改。
tmp3.py:

先执行tmp.py,再执行tmp2.py,发现第一次弹计算机,而第二次没有。
接着我自己debug看源码。先说结果:创建对象调的是__new__,而不是__init__。第一次测试的第二次可以弹计算器,不是执行的poc实例的__init__,而是因为__import__('tmp'),把生成yml文件用的代码也导入进去了,所以才弹计算器。
0x03
开始debug。

关键部分:

先跟进find_python_name看看:


可以看到,tmp3.poc根据 '.' 被分割成module_name 和object_name,并执行__import__(module_name)。
跳出find_python_name函数,继续往下,可以看到执行的是tmp3.poc的__new__函数。

0x04
当时debug纯粹是想练习调代码的能力,因为自己平时不怎么调。调了挺多遍,看得稍微有点明白了才罢休。能发现和网上师傅没注意到的点,心里还是有点激动的,嘻嘻嘻。东西不多,纯记录一下,师傅们看个乐就好。

浙公网安备 33010602011771号