前几天,临近五一劳动节假期,一想到五天长假,我就有点得意忘形了。于是就没注意写一个简单的 Bash 脚本,写完也没检查,就直接拖到物理服务器上跑了。
图片来自 Pexels
结果开始运行就出错了,这么简单的脚本怎么可能跑了10秒还跑不完呢?于是我立刻按下Ctrl+C,停止了正在运行的脚本。
然后,我习惯性地输入了 ls。发生了什么?找不到 ls 命令?
我感觉后背一凉,慌乱地打开脚本,发现问题了,我写了一个愚蠢的bug,间接执行了rm -fr /*,这不就是把库删了吗?
这是公司的授权服务器,如果我把它弄乱了,公司的历史授权记录等重要信息不是会丢失吗?
我很慌张,就把这件事告诉了我的朋友,朋友建议我尽快向领导汇报,不要把删除数据库的事瞒着。
于是我向领导汇报了删除数据库的想法,但我以为我会受到严厉的批评。
Leader笑道:“没事,你看看那些重要文件还在不在,不过顺便说一句,我突然想起来,我有半年没备份过我的编译服务器了,我先备份一下我的编译服务器,免得哪天被你删了。”
正在看节目的朋友们,你们以为我会删掉数据库然后跑掉吗?哈哈哈,我没跑掉,而是恢复了。那么接下来我就讲讲我是怎么删掉数据库然后恢复的。
犯罪现场的初步调查
我们先来看看我写的垃圾代码是如何导致库被删除的:
由于出现了rm -fr /*现象,所以new_lic_dir变量肯定是空的。
所以当执行 rm -fr $new_lic_dir/* 语句时,就变成了 rm -fr /* 语句来删除库。太好了,凶器找到了。
那么为什么new_lic_dir是空的呢?细心的朋友肯定注意到了,这是因为在给new_lic_dir变量赋值的时候使用了反引号。
是的,是因为反引号。反引号在 Linux Shell 命令行中有一个特殊含义:反引号之间的内容会被 Shell 优先执行,将其输出放入主命令中后,主命令才会被执行。
换句话说,new_lic_dir 的值是执行命令 ${lic_path}/new_license 的结果。问题是这不是一个命令,所以它必须向 new_lic_dir 变量返回一个空值。
我写的温柔的代码原来是恶意的删库代码。
现在找到原因了,反引号要改成双引号。(内心OS:你真是菜鸟,这么简单的赋值命令都写错了!)
哈哈哈,我真的很烂,前面也说了,快五一了,迷迷糊糊的写了这段代码,于是习惯性地开启了程序员第一武功:Crtl+C、Crtl+V。
我把第一条赋值lic_path=`pwd`的语句复制粘贴过来,然后只改了变量名,没注意到反引号要改成双引号,导致删除库的悲剧。
保护犯罪现场
由于数据库已被删除,请不要重新启动服务器或关闭 SSH 连接会话。相反,请保留犯罪现场,然后检查剩下的东西。
PS:这不是吹牛吗?ls 没了怎么查看?
幸好,这次我运气比较好,因为在执行脚本的时候,我第一时间发现了错误,并立即终止了还在运行的脚本,所以并不是所有的Linux文件都被删除了。
只要我动作快,rm -fr /* 不会害死我。虽然 ls 被删除了,但我幸运地发现 cd 命令仍然有效。
只要你用好 cd,它也可以用来达到和 ls 一样的效果。很简单,只要按 cd + Tab 键,就会自动出现指定目录下的所有文件。
通过cd+Tab键,我们可以查看到各个目录下的文件,这样就可以一步步确认哪些系统文件被删除了。
经过一番确认和对比,发现主要被删除的四个目录分别是:
我们来回顾一下上面四个目录主要存放什么:
/boot被删除了,幸亏小林没有重启服务器,要是重启了服务器,那就灾难了,系统肯定开不了机。
cd命令是在/sin目录下,而/sin依然完好,所以cd可以正常使用。
幸好重要的数据库信息和文件没有被删除,所以小林的第一个目标就是恢复/bin、/boot、/dev、/lib这四个目录。
还原文件
由于/bin目录和/lib下的一些动态文件被删除,导致常用的文件传输方式如ftp、scp、mount等都无法使用。
找了好久rm文件编辑软件,发现可以用wget,wget命令在/usr/bin目录下,幸好/usr/bin还完好。
于是,我用了一个刁钻的办法,先用另外一个普通的服务器,把/bin目录放到Web服务器的Web目录中,然后通过wget下载。
有希望,就有成功的曙光。
但是新的问题出现了,我下载的命令文件没有执行权限。
chmod命令在/bin目录下,该目录也已被删除,无法用来授予文件权限。
不过,我还是在网上搜索了一下,找到了一个很棒的 perl 命令,可以用来授予文件权限:
perl -e "chmod 777, 'ls'"
好神奇的命令啊,好了,现在权限分配问题已经解决了,成功在望。
wget不能直接下载/bin目录,它只能下载一个文件。
但我无法逐一下载并恢复它们,这将需要数年时间才能完成......
于是我想到了一个方法:
/bin 就这样恢复了,其余目录也通过同样的操作恢复了。
我的笑容渐渐又回来了,哈哈哈哈哈哈哈哈哈哈哈哈!
当遇到rm -fr /*数据库删除事件时,一定要保持冷静,保持心态稳定!
我们之所以能够从这次数据库删除事件中恢复,关键有两点:
以上两点如果做不好的话,服务器恢复的难度就会增加很多,更严重的是五一假期就过不了了。
防止意外执行 rm -fr /*
rm -fr /* 既然是暴力武器,那就必须防范,下面我就给大家讲几种防范的方法。
解决方案1:rm -rf 删除时确定目录
#!/bin/bash work_path=`pwd` #如果目录不为空,才执行删除操作 if [ ${work_path} != "" ];then rm -fr ${work_path}/* fi
执行目录删除操作之前,先判断需要删除的目录是否为空,如果不为空,则执行删除操作。
解决方案2:Shell脚本指定set -u
执行脚本时,如果遇到不存在的变量,Bash 默认会忽略它。
#!/bin/bash echo $a echo hello
上述代码中$a为不存在的变量,执行结果如下。
nbsp;bash test.sh hello
可以发现,echo $a 输出了一个空行,Bash 忽略了不存在的$a,然后继续执行echo hello。
当变量不存在时,脚本最好报告错误,而不是默默执行。
set -u 用于改变这个行为,如果把它添加到脚本中,当遇到不存在的变量时就会报错并停止执行。
#!/bin/bash set -u rm -fr $a/* echo hello
结果如下:
nbsp;bash test.sh test.sh: line 4: a: unbound variable
可以看到,由于a是未定义的变量,所以脚本报错,不再执行后面的语句。
解决方案 3:用 safe-rm 替换 rm
safe-rm 是一款开源软件工具,名字听起来很安全,所以用来替代安全性较差的 rm。
可以在 /etc/safe-rm.conf 中配置路径黑名单,定义哪些路径不能被 safe-rm 删除。
我们可以将safe-rm改名为rm,假设/etc/无法删除,那么删除/etc的时候会报错:
nbsp;rm -rf /etc/ safe-rm: skipping /etc/
解决方案4:建立回收箱机制
Windows 有回收站,所以即使你误删了一些东西,也可以在回收站里恢复。所以,我们也可以在 Linux 中实现回收站机制。
实现思路:
① 创建回收站目录
mkdir /home/.trash
② 编写remove.sh脚本,内容如下:
③ 修改~/.bashrc,将其中的rm命令替换为我们自建的remove.sh:
alias rm="sh /home/remove.sh"
④ 设置crontab定时清空垃圾,如每天0点清空垃圾:
0 0 * * * rm -rf /home/.trash/*
⑤ 最后执行以下命令使之生效:
source ~/.bashrc
解决方案 5:将根文件挂载为只读
在 /etc/fstab 文件中,将 / 文件系统挂载为只读。
remount,ro 表示以只读模式挂载。
文件以只读方式挂载后,删除操作无法成功:
反射
对于涉及rm -fr命令的代码,一定要小心谨慎,反复检查,防止误执行rm -fr /*。在测试机上验证无误后,再在真机上运行,千万不要大意。
即使发生 rm -fr /*rm文件编辑软件,也立即停止并执行三件事:
只要我们立即终止 rm -fr /*,它就不会杀死我们,我们可以在当前环境中使用剩余的命令,冷静分析,还有机会恢复。
现在的我是一个删库不逃的人了,再见,下次见。