Laravel 学院今天凌晨四点到上午十点不能访问问题定位及修复细节通报

今天早上起来(起来有点晚,九点多了),像往常一样打开学院网站,竟然报错了:

然后瞅一眼 QQ 群、微信,各路人马都来反馈问题,于是赶紧打开邮件,查看 Sentry 报警,竟然有这么多报警邮件:

这些报错邮件都无一例外都指向了同一个问题:

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 被配置为保存 RDB 快照,但是现在无法写入磁盘,那么这无非是两个问题,一个是没有权限,一个是空间不足,于是立即登录到 Redis 所在服务器,分别查看了权限和空间都没有问题,当务之急是先修复问题,然后再定位原因,所以立即在服务启动 redis-cli,将 stop-writes-on-bgsave-error 设置为 on(该选项在 Redis 配置文件中默认配置为 yes,表示 Redis 报错时停止写入并报错,我们将其设置为 on 表示忽略报错,继续执行):

#redis-cli 
127.0.0.1:6379> config set stop-writes-on-bgsave-error no

运行完这条命令再次访问学院,已经恢复访问了,但是这是个治标不治本的临时解决方案,只是忽略错误,但是错误仍然在发生,所以我回到服务器上继续定位问题。

我打开 Redis Server 的日志文件 /var/log/redis/redis-server.log,想从这里寻找蛛丝马迹,果不其然,发现了大量报错日志:

14632:C 09 Nov 08:58:14.020 # Failed opening .rdb for saving: Read-only file system
30211:M 09 Nov 08:58:14.120 # Background saving error

这里显示的错误信息将问题的源头指向了 .rdb 是一个只读文件,可是明明系统默认的 dump.rdb 文件时有写入权限的啊(在 Redis 配置文件中,可以看到 dbfilename 选项和 dir 选项,分别用于配置 RDB 文件名和所在目录)。于是我在 Google 上搜索了这个报错信息,果然找到了我要的答案:Redis Getshell漏洞。我在 redis-cli 交互 Shell 中查看 CONFIG GET dirCONFIG GET dbfilename 命令,得到了这篇文章中一样的结果,返回的目录和文件名分别是 /var/spool/cronroot,与 Redis 默认配置不符。

故障的原因很简单,就是因为 redis-server 是公开运行的,而且我是两台机器访问这台 Redis 服务器,所以把 Redis 配置文件中 bind 选项配置为了 0.0.0.0,以便让多个机器可以访问,但是没有配置任何权限和 iptables 过滤规则,这样导致的结果是外网可以自由访问我的 Redis 服务器,这种故障会导致 Redis 被 block,无法处理和响应请求(具体原因为何绕过默认配置文件写入/var/spool/cron可以看这个issue),幸而 Redis 服务器默认通过 redis 用户运行,而不是 root,否则后果更严重!(使用阿里云 ECS 的童鞋要注意这个问题了!!)

既然问题的根找到了,解决起来也就有了方向,那就是在 iptables 配置过滤规则,只让自己信任的 IP 访问 Redis 服务器的 6379 端口,举个例子,我们配置允许 192.168.0.1192.168.0.2 这两个 IP 访问 6379 端口(具体 IP 以你自己服务器的公网 IP 为准,允许自身访问 IP 可以配置为 127.0.0.1):

# 禁止任何 IP 访问 6379 端口
iptables -I INPUT -p TCP --dport 6379 -j REJECT
# 允许指定 IP 访问 6379 端口
iptables -I INPUT -s 192.168.0.1 -p tcp --dport 6379 -j ACCEPT
iptables -I INPUT -s 192.168.0.2 -p tcp --dport 6379 -j ACCEPT
# 保存并生效
iptables-save

然后我们需要通过 kill -9 杀死 redis-server 进程,并通过 redis-server /etc/redis/redis.conf 重新启动 Redis 服务器,再次访问学院,可以发现能够正常访问,Redis日志文件也不再有报错信息了,在本地测试连接 Redis 服务器,也拒绝访问了,至此,问题完全修复,大功告成。

再次提醒,使用阿里云 ECS 的同学,或者其他云服务的同学,需要自查下自己的 Redis 服务器,如果也是配置允许多台机器访问的话,有没有做这种过滤,否则某一天也会有这种被 block 导致依赖 Redis 的服务挂掉的风险。

注:如果你是将 Redis 服务器安装在应用所在服务器,并且只有这么一台机器,不用这么大费周章的配置,只需在 Redis 配置文件中将 bind 选项设置为 127.0.0.1 只允许本机访问即可。

上一篇: 基于 Laravel 5.6 重构的新版 Laravel 学院正式发布了!

下一篇: Laravel 学院新增移动内容分发渠道:微信公众号和小程序