排查错误


最近测试环境一直没问题的 API 突然报错了,错误信息如下:

MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error.

从该信息可以看出是 Redis 服务端持久化保存快照时出错,因此 API 项目作为客户端就收到了这个消息,搜索后得到如下解决方法:

$ redis-cli
redis 127.0.0.1:6379> CONFIG SET stop-writes-on-bgsave-error no

执行了上面的命令之后确实不报错了,但问题并没有从根本上解决,原因如下:

  • 这个配置项如果为 yes,表示 Redis 在执行 bgsave 报错时停止写入,也就是上面看到的错误信息,改为 no 其实并没有解决 bgsave 报错的问题,而只是把这个错误忽略掉了。
  • 不考虑第一条,即使该命令是正确的解决方法,CONFIG SET 也只是修改当前的配置,在 Redis 重启后仍然会从 redis.conf 读取原有的配置,修改配置之后需要配合 CONFIG REWRITE 写入到 redis.conf 中。

接着我到服务端查看了 Redis 的日志,找到了更明确的错误信息:

Failed opening the RDB file root (in server root dir /var/spool/cron) for saving: Permission denied

这个信息是对客户端错误的补充,可以看出 Redis 在保存 rdb 文件到 /var/spool/cron 下时因为没有权限所以出错了,到这里我又疑惑了, rdb 文件的路径是由 redis.conf 中的两个配置决定的:

  • dir 表示 rdb 文件的路径
  • dbfilename 表示 rdb 文件的名称

通过 CONFIG GET 命令可以获得这两个配置的值:

$ redis-cli
redis 127.0.0.1:6379> CONFIG GET dir 
1) "dir"
2) "/var/spool/cron"
redis 127.0.0.1:6379> CONFIG GET dbfilename 
1) "dbfilename"
2) "root"

结果竟然跟 redis.conf 配置的值是不一致的,然后我以 redis /var/spool/cron为关键字搜索,才发现了著名的 Getshell 问题。

Redis Getshell


该问题是由 Redis 的配置不当引起的,满足以下三个条件:

  • 服务端以 root 启动
  • 服务端无密码认证或者使用的是弱口令进行认证
  • redis.conf 的 bind ip 设为了允许外部连接

在这种情况下,Redis 服务完全暴露到公网上,攻击者通常会以扫描端口的方式检测到 Redis 并连接,由于 Redis 是以 root 启动的,可以通过上文的 CONFIG SET dir 和 dbfilename 的方式写入文件到 Redis 所在的服务器,举几个例子:

  • 写入一段 PHP 脚本,即 Webshell
  • 写入 SSH 公钥,通过私钥可以直接登录服务器
  • 写入 /var/spool/cron 中,通过 crontab 定时执行

解决方法


结合项目中遇到的错误,可以分析出我遇到的正是上面的第三种情况,因为我们的 Redis 会有多个客户端连接,所以 bind ip 设为了 0.0.0.0,并且我对测试环境的安全性要求不高,并没有配置认证,最终由于 Redis 不是以 root 启动的,写入文件的时候没有权限,因此才没有彻底失守,找到问题的根源就很好解决了,针对以上三个条件一一破解即可,这里就不再详述。