“ddddns”战队 2025-01-16 10:00 北京
中国科学院信息工程研究所“ddddns”战队的解题报告。
2024年11月28日,DataCon2024大数据安全分析竞赛落下帷幕。来自中国科学院信息工程研究所的“ddddns”战队荣获网络基础设施安全赛道冠军,本期一起来看看“ddddns”战队的解题报告。
1.1 Q1:开放解析器发现(100分)
题目要求利用网络扫描和探测技术发现给定网段(172.19.0.0/16)内,作为开放DNS解析器的主机。开放DNS解析器是指能够接受来自外部IP地址的DNS查询请求并返回响应的DNS服务器。考虑向给定网段全部IP发送DNS查询,如果能够返回合法的DNS响应,则认为该服务器为开放解析器,并记录其IP地址。
使用Python的dnspython库向172.19.0.0/16网段内全部IP发送关于google.com的A记录的DNS查询,总计获取72个能够解析成功并返回合法响应的IP,记为开放解析器。
1.2 Q2:开放解析器版本识别(96.33分)
题目要求识别Q1中扫描得到的开放解析器的软件及版本,DNS解析器的版本识别可以通过两种主要方法:一是直接查询其提供的版本信息(如version.bind);二是根据其特定行为或特性来推断版本。通过结合这两种方法,可以有效提高版本识别的准确性和覆盖范围。
首先通过version.bind命令获取解析器的版本信息。使用Dig工具对目标解析器执行查询,指定version.bind和CHAOS类,检查解析器返回的版本信息。如果返回明确的版本号,则记录下来,得到65个返回明确版本信息的结果,另外9个系统提示关注版本特性。
因此,对于未直接提供版本信息的解析器,基于其行为特性进一步分析。首先通过向全部解析器发送空报文,观察不同软件返回的错误响应,分析不同软件返回结果的差异可确认余下9个解析器为Bind9。随后关注Bind 9不同软件特性差异,收集不同版本的官方文档,综合大模型和手工分析得到Bind9版本差异表。
--with-libnghttp2 | ||
表1 Bind9 软件版本差异表(部分)
最终选择以EDNS支持的UDP数据包大小限制值作为判断依据对Bind9软件版本进行区分:
BIND 9.16及之前的版本默认的UDP数据包大小限制为4096字节。
BIND 9.18默认的UDP数据包大小限制为1232字节。
图1.2.1 不同Bind版本之间的特性差异
根据该特征,可以将未知解析器与已知版本特性对应,完成全部DNS解析器版本识别。
1.3 Q3:拒绝服务攻击模拟(96.98分)
攻击目标为172.19.0.5(之后修改为172.18.0.5),观察附件中提供的域名解析记录(check_fqdn.pcap)发现目标解析器为权威服务器,负责解析的域名有:victim.com,jtfgzlm.com,jthmfgz.com,通过在攻击环境中查询pcap中出现的所有域名的二级域名的NS记录,得到的查询结果也可以验证该结论。
总体而言,发现如下五种攻击方式,同时为了提升攻击效率,针对不同的攻击方式选择了具有特定特征的解析器以最大化到攻击目标的流量,进而进行攻击实现。
首先关注目标服务器负责解析的域名,对check_fqdn.pcap文件进行分析,判断有以下四类攻击方式。
1.错误配置的NS循环解析链
分析check_fqdn.pcap文件中的域名解析记录发现,从域名a.qwe.com的查询开始,解析器不断返回NS记录,形成NS解析链,其中包括需要向目标解析器查询的ins.victim.com(i指a、b、c等)。
编写Python代码提取相关记录,得到如下NS解析链:
a.qwe.com.-> ans.victim.com. -> b.qwe.com. -> b.asd.com. -> bns.victim.com. -> c.qwe.com. -> c.asd.com. -> cns.victim.com. -> d.qwe.com. -> d.asd.com. -> dns.victim.com. -> e.qwe.com. -> e.asd.com. -> ens.victim.com. -> f.qwe.com. -> f.asd.com. -> fns.victim.com. -> g.qwe.com. -> g.asd.com. -> gns.victim.com. -> h.qwe.com. -> h.asd.com. -> hns.victim.com. -> i.qwe.com. -> i.asd.com. -> ins.victim.com. -> i1.qwe.com. -> i1.asd.com. -> i1ns.victim.com. -> i2.qwe.com. ->
...
i10.asd.com. -> i10.qwe.com. -> i9ns.victim.com. -> i9.asd.com. -> i9.qwe.com. -> i8ns.victim.com. -> i8.asd.com. -> i8.qwe.com. -> i7ns.victim.com. -> i7.asd.com. -> i7.qwe.com. -> i6ns.victim.com. -> i6.asd.com. -> i6.qwe.com. -> i5ns.victim.com. -> i5.asd.com. -> i5.qwe.com. -> i4ns.victim.com. -> i4.asd.com. -> i4.qwe.com. -> i3ns.victim.com. -> i3.asd.com. -> i3.qwe.com. -> i2ns.victim.com. -> i2.asd.com. -> i2.qwe.com. -> i1ns.victim.com. -> i1.asd.com. -> i1.qwe.com. -> ins.victim.com. -> i.asd.com. -> i.qwe.com. -> hns.victim.com. -> h.asd.com. -> h.qwe.com. -> gns.victim.com. -> g.asd.com. -> g.qwe.com. -> fns.victim.com. -> f.asd.com. -> f.qwe.com. -> ens.victim.com. -> e.asd.com. -> e.qwe.com. -> dns.victim.com. -> d.asd.com. -> d.qwe.com. -> cns.victim.com. -> c.asd.com. -> c.qwe.com. -> bns.victim.com. -> b.asd.com. -> b.qwe.com. -> ans.victim.com.
可以看到NS解析链最终回到ans.victim.com,形成循环。
因此,向开放解析器查询NS解析链中的任意域名A、AAAA、TXT、MX、CNAME即可触发NS循环解析,并产生大量发往目标服务器的查询,实现对目标服务器的拒绝服务攻击。
对于该类攻击,利用符合以下条件的解析器可以实现更好的攻击效果:①无缓存或缓存时间短,增加解析器的外部依赖,使其多次向权威服务器(目标服务器)发起查询;②超时重试次数高,多次重复尝试对NS记录中域名地查询,使得解析器可以更多次地向目标服务器发起查询。
2.NS记录→no such name
分析check_fqdn.pcap文件中的域名解析记录发现,存在一条长度为2444的NS记录,如下图所示。
于是对域名health.health.com的相关解析记录进行分析,结果如下表所示。
序号 | 报文类型 | 记录类型 | 记录值 | 源→目的 | 备注 |
1 | query | A | health.health.com | 3→112 | |
2 | query | NS | health.com | 112→2 | |
3 | response | NS | ns1.health.com | 2→112 | |
A | 172.19.0.11 | ||||
4 | query | AAAA | ns1.health.com | 112→254 | |
5 | response | NS | ns1.com | 254→112 | |
A | 172.19.0.2 | ||||
6 | query | A | health.health.com | 112→11 | |
7 | response | 11→112 | 未返回结果 | ||
8 | query | AAAA | ns1.health.com | 112→2 | |
9 | response | NS | ns1.health.com | 2→112 | |
A | 172.19.0.11 | ||||
10 | query | A | health.health.com | 112→11 | |
11 | response | NS | health1-100.victim.com | 11→112 | |
12 | query | A/AAAA | health1.victim.com | 112→5 | |
13 | response | no such name | 5→112 | 未查询到结果,继续查询其它NS记录 |
因此,向开放解析器查询域名health.health.com和healthi.health.com(i为1-100)的随机子域名的因此,向开放解析器查询NS解析链中的任意域名A、AAAA、TXT、MX、CNAME即可触发NS循环解析,并产生大量发往目标服务器的查询,实现对目标服务器的拒绝服务攻击。
对于该类攻击,利用符合以下条件的解析器可以实现更好的攻击效果:①无缓存或缓存时间短,增加解析器的外部依赖,使其多次向权威服务器(目标服务器)发起查询;②超时重试次数高,多次重复尝试对NS记录中域名地查询,使得解析器可以更多次地向目标服务器发起查询。
3.错误配置的CNAME循环解析链
分析check_fqdn.pcap文件中的域名解析记录发现,从对域名a.ieka.com的查询开始,解析器不断返回CNAME记录,形成CNAME解析链,其中包括需要向目标解析器查询的x.victim.com(x指a、b、c等)。
图1.3.5 CNAME解析链
编写Python代码提取相关记录,得到如下CNAME解析链:
a.ieka.com. -> a.yumlly.com. -> a.losers.com. -> a.victim.com. -> b.yumlly.com. -> b.losers.com. -> b.victim.com. -> c.yumlly.com. -> c.losers.com. -> c.victim.com. -> d.yumlly.com. -> d.losers.com. -> d.victim.com. -> d1.yumlly.com. -> d1.losers.com. -> d1.victim.com. -> d2.yumlly.com. -> d2.losers.com. -> d2.victim.com. -> d3.yumlly.com. -> d3.yumlly.com. -> d2.victim.com. -> d2.losers.com. -> d2.yumlly.com. -> d1.victim.com. -> d1.losers.com. -> d1.yumlly.com. -> d.victim.com. -> d.losers.com. -> d.yumlly.com. -> c.victim.com. -> c.losers.com. -> c.yumlly.com. -> b.victim.com. -> b.losers.com. -> b.yumlly.com. -> a.victim.com. -> a.victim.com. -> a.losers.com. -> a.yumlly.com.
可以看到,CNAME解析链最终回到a.yumlly.com,形成循环。
因此,向开放解析器查询CNAME解析链中的任意域名的随机子域名的A、AAAA、TXT、MX、CNAME、NS记录即可触发CNAME循环解析,并产生大量发往目标解析器的查询,实现对目标解析器的拒绝服务攻击。
对于该类攻击,利用符合以下条件的解析器可以实现更好的攻击效果:①无缓存或缓存时间短,增加解析器的外部依赖,使其多次向权威服务器(目标服务器)发起查询;② 允许较长CNAME链,部分解析器对递归深度有所限制,在达到一定次数时会停止查询,当解析器允许较长地CNAME链时,则可以使其更多次地向目标服务器发起查询。
4.jtfgzlm.com & jthmfgz.com(类似五叉树攻击)
分析check_fqdn.pcap文件中的域名解析记录发现,存在大量与jtfgzlm.com及jthmfgz.com的子域名相关的解析记录,如下图所示。
图1.3.6与jtfgzlm.com及jthmfgz.com的子域名相关的解析记录
于是对此类解析记录进行分析,结果如下表所示。
序号 | 报文类型 | 记录类型 | 记录值 | 备注 |
1 | query | NS | shiyan1.jtfgzlm.com | |
2 | response | NS | shiyan11-15.jthmfgz.com | |
3 | query | A/AAAA | shiyan11.jthmfgz.com | 12-15类似 |
4 | response | NS | ns1.jthmfgz.com | |
A | 172.19.0.8 | |||
5 | query | A/AAAA | shiyan11.jthmfgz.com | |
6 | response | NS | shiyan111-115.jtfgzlm.com | |
7 | query | A/AAAA | shiyan111.jtfgzlm.com | 112-115类似 |
8 | response | NS | shiyan1111-1115.jthmfgz.com | |
9 | query | A/AAAA | shiyan1111.jthmfgz.com | 1111-1115类似 |
10 | response | A | 172.19.0.5 | |
AAAA | 不存在 | |||
11 | query | A/AAAA | shiyan111.jtfgzlm.com | 发往172.19.0.5 |
12 | Response | Refused | 转向查询其他记录 |
因此,向开放解析器查询域名shiyani.jtfgzlm.com和shiyani.jthmfgz.com的随机子域名的A、AAAA、TXT、MX、CNAME、NS记录即可触发大量发往目标解析器的查询,实现对目标解析器的拒绝服务攻击。
对于该类攻击,利用符合以下条件的解析器可以实现更好的攻击效果:①无缓存或缓存时间短,增加解析器的外部依赖,使其多次向权威服务器(目标服务器)发起查询;②超时重试次数高,多次重复尝试对NS记录中域名的查询,使得解析器可以更多次地向目标服务器发起查询。
之后对check_fqdn.pcap文件中的数据包(packet)按照长度(length)进行排序,发现存在很多长度较大的TXT查询结果,于是得到第五种攻击方式。
5.基于TXT记录的反射放大攻击
由上面的发现可知存在大量域名,在查询其TXT记录时,会返回长度较大的包,其中最大为11496。因此可以伪造目标解析器的IP地址作为查询请求的源IP地址,查询上述域名的TXT记录,使大量的响应数据发送到目标解析器,实现反射放大攻击。
由于传统DNS的UDP响应报文大小限制为512字节。如果响应数据超过512字节,通常会导致使用TCP进行重传,而TCP流量不适合放大攻击(需要建立连接,增加攻击成本)。而EDNS(Extension Mechanisms for DNS)扩展了DNS的功能,将UDP数据包的响应大小限制提高到4096字节(默认)。
因此对于该类攻击,利用符合以下条件的解析器可以实现更好的攻击效果:支持ENDS,能够设置UDP数据包的响应大小。此外,该攻击对于查询域名也有所限制,要求其返回的TXT记录的大小不能超过4096字节。
2.1 Q1(100分)
2.1.1 解题思路和方法步骤
1. 服务判断
本题目作为蜜罐系列的第一道赛题,赛题介绍里明确说明了要实现模拟的服务为OPENSSH。
2. 蜜罐实现
第一步首先使用Python写一个基本的具备核心SSH服务功能的服务端,这里使用的是paramiko库。将上述实现的基础服务部署在4022端口提交答案后发现直接被检测出蜜罐,通过服务日志与抓取的实际数据包我们分析攻击者首先对目标端口进行了Nmap扫描,并且我们进一步模拟攻击者的行为—在本地环境部署脚本之后在本地使用Nmap扫描比较日志信息—确定了上述结论,且发现使用的是-sV参数。
为了迷惑Nmap扫描器,仔细观察发现使用python-paramiko库搭建的ssh服务,虽然能实现基本功能,但是处处显示出它自己独特的“痕迹”—例如banner信息。通过修改代码中的信息我们成功迷惑了Nmap,之后就发现攻击者开始尝试登录SSH服务。
需要注意的是我们发现攻击者(预定义的脚本)为了识别蜜罐,会在设计各种正常或者异常行为,来判断响应的合理性。例如登录过程中攻击者先是使用了空的账号和密码登录(蜜罐服务应该回复合理的报错信息)之后才使用预设的账号和密码登录。身份校验功能由paramiko库完成因此没有花太长时间。
之后,攻击者登录之后我们日志记录不到任何信息就结束了。应该是代码实现存在问题,代码只实现了针对exec模式的ssh响应,最开始以为是攻击者使用pty模式或者subsystem模式我们不支持,增加了相应的日志记录后发现并不操作模式的问题,攻击者仍然使用的是exec模式。这里应该是攻击者登陆之后起了多个连接我们必须保证每个都能正常处理于是修改socket逻辑代码即可(很小的一个trick,这里好像不是这个,蜜罐Q1做的比较早,实在记不得了)。之后攻击者的预设脚本就开始发送实际要执行的指令。另外,使用paramiko库的过程中我们遇到了一个极大的问题就是它写入stdin、stdout和stderr的逻辑好像有问题。经过反复修改代码,我们使用该代码库底层的类内部处理方法(触发channel._send_eof())才有效解决响应内容的功能。
攻击者按照预设的脚本执行了20多条执行,记录如下表。需要特别注意一些问题是,攻击者执行命令预设了相应的预设结果,我们需要先在自己掌握的linux环境中执行脚本获得实际的响应格式,在设计蜜罐代码来针对性执行。另外,上述步骤处理了攻击者10余条命令后,50分左右时,我们遇到了攻击者执行history命令。根据我们了解到的知识,如果攻击者使用exec模式执行命令的话,理论上不会涉及到一个会话来存储历史记录的,因此这里我们最开始以为应该响应一个空或者指令报错。但是这样处理一直提示检测到蜜罐。于是我们考虑攻击者预设的响应可能就是上述的脚本执行记录信息,因此按照格式设置了响应内容,发现还真的通过了。但是对于上述的处理逻辑我们仍然存疑。后面我们审计了攻击者要求执行的命令,发现没有危害操作系统安全的指令,于是为了处理方便并且使得蜜罐尽可能还原真实的响应,我们将攻击者的请求直接使用Python的subprocess在本地执行然后将结果进行返回。这也是蜜罐服务中常见的操作方式。
注:在第一题的时候和攻击者脚本交互的过程中我们也能记录到对方使用的工具:SSH-2.0-paramiko_3.5.0。这一点为我们后续的蜜罐解题提供了思路:攻击者大概率都是使用Python写的渗透脚本,于是我们能够使用相应的Python库,模仿攻击者可能的行为(有限的集合,Python某个库的命令是有限的)进而设计针对性的解决方案。
2.1.2 攻击者执行动作记录
序号 | 动作 | 响应 |
1 | echo $SSH_CLIENT | xx xx 22 |
2 | whoami | datacon |
3 | echo datacon | sudo -S echo "sudo success" | sudo success |
4 | notexistcommand | zsh:1: command not found: notexistcommand |
5 | cd ~ && pwd | /home/datacon |
6 | cd ~ && touch 123.txt abc.c xyz.cpp && ls | 123.txt\nabc.c\nxyz.cpp\nzzz.zz |
7 | cd ~ && rm 123.txt abc.c && ls | 下面都是一些按格式预设的响应 |
8 | cd ~ && mkdir testdir && mkdir workdir && cd testdir && pwd | |
9 | cd ~ && rmdir testdir && ls | |
10 | cd ~ && cp xyz.cpp workdir/ && ls workdir | |
11 | cd ~ && mv workdir/xyz.cpp ./abc.cpp && ls | |
12 | cd ~ && rm xyz.cpp && rmdir workdir && ls | |
13 | touch testfile && echo 'cat 4esae0sdo2t2eeTeAcosiaDFnlccNTt' > testfile && cat testfile && rm testfile | |
14 | echo ‘something’ > longfile && ls longfile | |
15 | head -n 3 longfile | |
16 | tail -n 3 longfile && rm longfile | |
17 | date -u +%s | |
18 | history | |
19 | ifconfig | |
20 | uname -a | |
21 | df -h | |
22 | ps -aux | |
23 | free -h | |
24 | ping -c 5 -W 2 203.0.113.1 |
2.2 Q2(0分)
2.2.1 解题思路和方法步骤
首先观察到目标尝试建立TCP连接,因此与目标完成TCP握手。
握手后尝试采取向攻击者发送Banner和不发送Banner两种行为,两种行为都出现问题,导致目标无进一步动作:
若不发送任何报文,等待目标主动请求,则目标会在间隔一小段时间后断开,判分日志指出功能未实现;
若发送banner报文,首先推测为ftp协议,但是主流的ftp协议banner均会让目标检测到蜜罐而停止动作,其次尝试了主流的各种协议,判分日志均为检测到蜜罐。
图2.2.1 Q2尝试解答过程中的流量
后续关于此题的解答主要围绕协议的推断、对应banner的尝试以及对目标仅有的几次响应的分析,效果并不理想,遂放弃。
2.2.2 攻击者执行动作记录
(无)
2.3 Q3(100分)
2.3.1 解题思路和方法步骤
1. 服务判断
首先,结合公开信息(通常MQTT服务会部署在1883端口)猜测为MQTT服务。
然后,编写Python脚本监听1883端口,并尝试建立TCP连接,观察脚本输出及pcap报文内容,确认为MQTT服务。
2. 蜜罐实现
整体思路:基于MQTT通信过程,使用Python逐步实现MQTT服务器的功能,在攻击者进行探测时抓包,等待攻击结束后检查抓包结果及程序的输出日志,并基于此进一步完善服务器功能。
实现过程如下:
(1)第一版(13.64分)
实现内容:建立TCP连接,处理连接请求、心跳请求,用户名认证。部分日志信息如下:
2024-11-17 16:25:40,417 - INFO - Parsed CONNECT packet from ('10.0.33.52', 43903): {'protocol_name': 'MQTT', 'protocol_level': 4, 'connect_flags': 194, 'keep_alive': 60, 'username': '', 'password': 'datacon'}
2024-11-17 16:25:40,417 - WARNING - Authentication failed for ('10.0.33.52', 43903): username=, password=datacon // username解析错误导致认证失败
2024-11-17 16:25:40,417 - INFO - Connection with ('10.0.33.52', 43903) closed
存在问题:将username字段解析为空,导致登录认证失败。
(2)第二版(0分)
实现内容:在第一版基础上优化username字段的解析。部分日志信息如下:
2024-11-17 17:35:51,426 - INFO - Parsed CONNECT packet from ('10.0.33.52', 43453): {'protocol_name': 'MQTT', 'protocol_level': 4, 'connect_flags': 194, 'keep_alive': 60, 'username': 'datacon', 'password': 'datacon'}
2024-11-17 17:35:51,426 - INFO - Authentication successful for ('10.0.33.52', 43453)
2024-11-17 17:35:52,427 - WARNING - Unknown packet type 3 from ('10.0.33.52', 43453)
2024-11-17 17:35:53,428 - WARNING - Unknown packet type 3 from ('10.0.33.52', 43453)
2024-11-17 17:35:54,430 - WARNING - Unknown packet type 3 from ('10.0.33.52', 43453)
2024-11-17 17:35:55,431 - WARNING - Unknown packet type 3 from ('10.0.33.52', 43453)
2024-11-17 17:36:05,440 - INFO - Connection closed by ('10.0.33.52', 42555)
结合抓包结果判断,该报文可能是SUBCRIBE报文。
图2.3.2 第二版抓包结果
存在问题:未对SUBCRIBE等报文进行解析。
(3)第三版(0分)
实现内容:在第二版基础上增加对subscribe和publish报文的解析和响应。部分日志信息如下:
2024-11-17 20:14:40,416 - INFO - Received SUBSCRIBE packet from ('10.0.33.52', 46603): 820a000100056f7a62706a00
2024-11-17 20:14:40,416 - INFO - Parsed SUBSCRIBE packet: {'message_id': 1, 'topics': ['ozbpj'], 'qos_values': [0]}
2024-11-17 20:14:41,416 - INFO - New connection from ('10.0.33.52', 55815)
2024-11-17 20:14:41,416 - INFO - Received CONNECT packet from ('10.0.33.52', 55815): 101e00044d51545404c2003c0000000764617461636f6e000764617461636f6e321400056f7a62706a000148454c4c4f20574f524c44
2024-11-17 20:14:41,416 - INFO - Authentication successful for ('10.0.33.52', 55815)
2024-11-17 20:14:42,418 - INFO - Received PUBLISH packet from ('10.0.33.52', 55815): 321300056f7a62706a00027572676f7369666e6362
2024-11-17 20:14:42,418 - INFO - Parsed PUBLISH packet: {'topic_name': 'ozbpj', 'qos': 1, 'retain': 0, 'packet_id': 2, 'payload': 'urgosifncb'}
2024-11-17 20:14:42,418 - INFO - Sent PUBACK for Packet ID: 2
存在问题:结合MQTT通信原理可知,收到某主题的消息,应该将其发布给订阅该主题的订阅者,该部分代码未实现。
(4)第四版(0分)
实现内容:在第三版基础上进一步实现消息的分发。部分日志信息如下:
2024-11-18 09:26:01,415 - INFO - Received PUBLISH packet from ('10.0.33.52', 33573): 321400056c716b627a000148454c4c4f20574f524c44
2024-11-18 09:26:01,415 - INFO - Parsed PUBLISH packet: {'topic_name': 'lqkbz', 'qos': 1, 'retain': 0, 'packet_id': 1, 'payload': 'HELLO WORLD'}
2024-11-18 09:26:01,415 - INFO - Sent PUBLISH message to ('10.0.33.52', 41111): 3000056c716b627a48454c4c4f20574f524c44
2024-11-18 09:26:01,415 - INFO - Sent PUBACK for Packet ID: 1
……
2024-11-18 09:26:05,419 - INFO - Sent PUBACK for Packet ID: 5
2024-11-18 09:26:06,421 - WARNING - Unknown packet type 14 from ('10.0.33.52', 33573)
2024-11-18 09:26:06,421 - INFO - Connection closed by ('10.0.33.52', 33573)
存在问题:① 结合pcap解析结果发现向订阅者发送的PUBLISH报文格式不能正常解析,推测是构造报文产生的错误;② 结合pcap解析结果判断不能解析的报文为DISCONNECT报文,代码中未对该类报文进行解析及处理。
(5)第五版(100分)
实现内容:基于第四版代码,修改发送给订阅者的PUBLISH报文的格式,增加对DISCONNECT报文的解析及处理。
3. 参考资料
https://blog.csdn.net/jiejiemcu/article/details/106737995
2.3.2 攻击者执行动作记录
序号 | 动作 | 响应 |
1 | 向服务器发送连接请求(CONNECT报文,用户名和密码均为空) | 验证用户名和密码,返回确认连接请求(CONNACK报文,AUTH FAILED) |
2 | 向服务器发送连接请求(CONNECT报文,用户名和密码均为datacon) | 验证用户名和密码,返回确认连接请求(CONNACK报文,SUCCESS) |
3 | 发送订阅主题报文SUBSCRIBE(主题字符串随机生成) | 记录订阅列表(哪些用户订阅了哪些主题);返回订阅主题消息SUBACK |
4 | 向服务器发送连接请求(CONNECT报文,用户名和密码均为datacon)(与2端口不同) | 验证用户名和密码,返回确认连接请求(CONNACK报文,SUCCESS) |
5 | 发送5条发布消息报文PUBLISH | 返回发布确认报文PUBACK; 向订阅该主题的用户(2中连接的用户)分发该消息 |
6 | 发送DISCONNEC报文与服务器断开连接 | 与客户端断开连接 |
2.4 Q4(100分)
2.4.1 解题思路和方法步骤
1. 服务判断
题目给出服务所在端口为27017,结合公开信息推断该服务为MongoDB数据库。MongoDB数据库采用MongoDB Wire协议。首先编写Python脚本监听27017端口。可以看到攻击者符合MongoDB Wire协议形式的数据包Query消息,该请求用于检查MongoDB是为主节点或能否提供服务,从而确定需要模拟的服务为一个MongoDB服务。
2. 解题步骤
整体思路:基于MongoDB Wire Protocol通信过程,在本机部署一个真实的MongoDB数据库,利用python的pymongo库编写客户端和数据库进行交互,分析交互情况。在攻击者进行探测时抓包,等待攻击结束后检查抓包结果及程序的输出日志,并基于此进一步完善服务器功能。MongoDB Wire Protocol具有16字节的头部(分别为消息长度、请求ID、应答ID、操作码,均为4字节)和4字节的标识符。常用的操作码有2004(Query)、2001(Reply)、2013(Message)。数据包大体形式如下:
攻击者发来的第一个数据包是一个用于执行ismaster命令的Query操作,操作码为2004。通过pymongo向本机27017端口的真实mongoDB服务发送ismaster请求获取真实场景下对于ismaster的响应,构造对应的响应。同时在交互过程中攻击者会不断发送执行hello和ismaster命令的消息,操作码为2013,同样模拟真实场景下对应的响应。
之后攻击者会发送一个操作码为2013的buildinfo请求用于查询服务器版本和构建信息,模拟真实情况返回响应,这里存在一些大小写问题,比如buildinfo/buildInfo,需要在代码中补充对各种请求的处理和响应逻辑。
在攻击者扫描过程中会不断发送hello/ismaster命令以判断服务的存活状态,这里也存在一些大小写问题,比如ismaster/isMaster,需要在代码中补充对各种请求的处理和响应逻辑。
此后攻击者执行serverStatus用于查看服务器状态,在本机向自建mongodb服务发送同样的指令,发现在不输入用户名和密码(即未认证)的情况下无法给予响应而是返回errmsg,据此构造响应。
之后攻击者会发送一个ping命令探测服务器存活性,伪造合法响应。
完成上述操作后,攻击者试图通过SASL认证与服务建立会话。SASL流程如下:
客户端发送saslStart。客户端持有一个客户端随机数clientNonce,生成客户端消息clientFirstMsg=”n=${username},r=${clientNonce}”,随后向服务器发送包含如下BSON文档的命令。
{
"saslStart": 1,
"mechanism": "SCRAM-SHA-1",
"payload": binary("n,," + clientFirstMsg),
"autoAuthorize": 1
}
服务器响应saslStart。服务器收到请求后使用如下BSON文档进行响应,其中r为客户端随机数和服务器随机数的拼接、s为盐值、i为哈希迭代次数。
{
"conversationId": 1,
"payload": binary("r=fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE,s=rQ9ZY3MntBeuP3E1TDVC4w==,i=10000"),
"done": false,
"ok": 1
}
客户端发送saslContinue。之后客户端发送一条编码消息以证明自己持有正确的密码,客户端会将密码字符串哈希,之后对哈希后的字符串加盐,随后生成认证消息authMessage,获取clientKey和storedKey,利用clientKey对authMessage进行签名得到clientProof,将其封装为clientFinal= “${clientFinalNoPf},p=${clientProof.toBase64}”,流程如下:
clientFinalNoPf := "c=biws,r=${serverNonce}"
authMessage:="${clientFirstMsg},${serverFirstMsg},${clientFinalNoPf}"
clientKey:= Buf().print("Client Key").hmac("SHA-1", saltedPassword)
storedKey:= clientKey.toDigest("SHA-1")
clientSignature := Buf().print(authMessage).hmac("SHA-1", storedKey)
clientProof:= xor(clientKey, clientSignature)
clientFinal:= "${clientFinalNoPf},p=${clientProof.toBase64}"
随后客户端发送包含如下BSON文档的消息:
{
"saslContinue": 1,
"conversationId": conversationId,
"payload": binary(clientFinal)
}
服务器响应saslContinue。之后服务器响应如下BSON文档,这里payload中的v是服务器基于持有的随机数以及攻击者发来的认证信息计算得到的Proof:
{
"conversationId": 1,
"payload": binary("v=UMWeI25JD1yNYZRMpZ4VHvhZ9e0="),
"done": false,
"ok": 1
}
为了完成SASL鉴权,基于python实现了认证过程(见validation_client_msg.py)。完成SASL鉴权后,攻击者再次发送ping命令探测存活性。
随后攻击者执行usersInfo命令查看系统当前用户信息。之后执行createUsers创建新用户,用户名为test,密码为test@123!,对test_db具有读写权限。创建完成后再次以新用户的身份进行SASL会话鉴权。
认证完毕后,再次执行listDatabase操作,查看全部数据库。
随后攻击者执行insert操作,向test_db数据库插入一个test_collection文档,内容为{_id: 673f3312a4244c2da0153576, FNUTR: euzfcawhnpkdoxrmgsyi}。
Insert操作解析部分出现问题,该命令操作码虽然仍为2013,但和此前的MSG类型形式不一致,该数据包包含两个section,二者之间通过一个\x00分隔,第一部分是一个BSON文档表明要执行的插入操作、第二部分是一个BSON文档列表表明要插入的文档,需要对两部分进行解析并返回成功插入的响应。由于操作码为2013的数据包主体部分存在多种不同形式(一个section、多个section),在代码中设置了一个FLAG_multi_msg标志位用来区分是否存在多个section并进一步提取各个section内容方便后续数据库增删改查操作。
之后攻击者执行find操作,在数据库test_db中对test_collection执行一次查询,查询条件是FUNTR的值为euzfcawhnpkdoxrmgsyi,要求查询结果最多返回一条记录、启用单批次模式,同时使用特定的逻辑会话ID进行关联。
随后构造响应表明查询操作完成,状态字段ok为1、返回的第一批数据为空、命名空间为test_db.test_collection、查询游标ID置为0以表明没有后续批次需要获取
之后攻击者执行update命令。在数据库 test_db 的集合 test_collection 中查找文档,条件是键 FNUTR 的值为 euzfcawhnpkdoxrmgsyi。如果找到匹配的文档,更新其键 FNUTR 的值为 i%q%pbnj#fm@o^g$e&d^。更新操作是部分更新,且只更新一个文档(multi: False)。如果没有匹配的文档,不会执行插入操作(upsert: False)。更新操作是有序的(ordered: True),失败后不继续其他更新操作。返回响应, MongoDB 在数据库 test_db 的集合 test_collection 中找到了 1 条符合条件的文档(n: 1)。该文档成功进行了更新操作(nModified: 1)。操作状态为成功(ok: 1),没有发生错误或异常。
此后会依次进行删除操作和查询操作,处理方式同上。
随后攻击者发起删除用户的操作,目标用户名是 test,目标数据库为 test_db,即在该数据库范围内删除用户。
再次执行userinfo查看用户,返回空,表明此时用户已经被删除。
参考资料:https://blog.csdn.net/bo_self_effacing/article/details/134884570
2.4.2 攻击者执行动作记录
序号 | 动作 | 响应 |
1 | 客户端检查MongoDB是否为主节点或能否提供服务(Ismaster) | 返回服务端信息 |
2 | 客户端检查MongoDB是否为主节点或能否提供服务(Hello) | 返回服务端信息 |
3 | 获取服务器版本和构建信息(Buildinfo) | 返回服务器版本和构建信息 |
4 | 查询服务器的当前状态信息,包括资源使用情况、连接数等(Serverstatus) | 返回认证失败,要求先认证再查看服务器状态 |
5 | 开始一个基于 SASL(简单认证和安全层)的认证会话(SaslStart) | 返回认证响应 |
6 | 继续之前的 SASL 会话,完成认证流程(SaslContinue) | 验证并返回认证响应 |
7 | 测试服务器的响应性(Ping) | 返回本机状态 |
8 | 列出所有数据库(Listdatabase) | 返回操作失败,要求先认证再查看所有数据库 |
9 | 查询用户信息(Userinfo) | 列出所有用户 |
10 | 创建一个新用户(Createuser) | 返回用户创建成功 |
11 | 再次开始 SASL 认证,会话针对新用户(SaslStart) | 返回认证响应 |
12 | 继续新用户的认证过程(SaslContinue) | 验证并返回认证响应 |
13 | 重新列出所有数据库,确认新用户的权限(Listdatabase) | 返回所有数据库列表 |
14 | 向集合中插入文档(Insert) | 返回插入结果 |
15 | 查询集合中的文档(Find) | 返回查询结果 |
16 | 更新集合中的文档(Update) | 返回更新结果 |
17 | 再次查询集合中的文档(Find) | 返回查询结果 |
18 | 删除集合中的文档(Delete) | 返回删除结果 |
19 | 再一次查询集合中的文档(Find) | 返回查询结果 |
20 | 删除一个用户(dropUser) | 返回删除结果 |
21 | 检查当前用户信息(Userinfo) | 返回当前用户信息 |
2.5 Q5(100分)
2.5.1 解题思路和方法步骤
1. 服务判断
首先阅读题干,得知攻击者将攻击9200端口,然后直接去查资料/ChatGPT获知该端口一般运行着elasticsearch服务,有了大概方向。
2. 蜜罐实现
初步尝试。进一步了解elasticsearch服务的特点以及协议交互的行为。这里elasticsearch是一个HTTP服务,开放restful接口供用户获取数据。让ChatGPT写一个简单的HTTP服务(用HTTP库实现),要求能够回显客户端请求的URL路径。提交答案,发现直接是0分,且没有记录到任何日志。
抓包分析。使用tcpdump抓取9200端口数据包(注意这里不要使用nohup,使用tcpdump需要sudo,但是nohup之后需要结束进程的时候,kill没法sudo,导致杀不掉进程。直接前台运行,抓包差不多了直接ctrl+c),以为是HTTP的服务都是明文的,应该可以分析。开启ChatGPT写的HTTP服务,开启tcpdump抓包(-i any监听任意端口即可,指定文件输出),提交答案。之后确实能抓到数据包,将保存好的pcap文件,通过scp上传到一个自己的远程vps,然后xshell连接vps再把pcap文件拉到本地。打开pcap发现,TCP三次握手之后,客户端尝试TLS握手,考虑这边的elasticsearch服务(我们的蜜罐服务)应该需要支持HTTPS。
按需求代码重构。让ChatGPT使用更高层的库实现HTTPS服务,选择使用flask框架。ChatGPT提供能够支持HTTPS的服务端,同时功能仍然是响应所有页面,并且记录请求的URL路径。再次运行服务,提交答案,结果还是0分,但是收到了请求日志,攻击者请求了根目录(且根据请求头可以了解到攻击者使用的是Python的elasticsearch库来开发的攻击脚本)。这时候就需要考虑一个真实的elasticsearch服务,如果用户访问根目录,代表着什么样的行为以及真实的elasticsearch应该如何响应。
真实的服务。此时需要实际运行elasticsearch服务分析交互行为。直接根据ChatGPT的教程在本机上运行一个elasticsearch服务。然后还需要模拟攻击者的行为(请求该服务的根目录),使用和攻击者一样的基于Python的elasticsearch库的方法来请求服务。此时场景为有一个真实的服务端,攻击者请求这个真实的服务端得到的响应,就是攻击者确实想要得到的结果,攻击者拿这个结果和一个可能是蜜罐服务的结果做比较就能判断是否为蜜罐,这就需要尽可能模拟真实的服务行为。之后发现直接请求失败,报错信息提示认证失败,考虑到题干提示要输入账号和密码;配置了账号密码再登录,发现访问服务的根目录会得到当前数据库的基本信息,然后就将真实服务的响应复制下来,放到基于flask伪装的elasticsearch蜜罐的响应数据中(这里还需要确保一些头部字段也进行伪装,和真实服务的响应结果尽量保持一致;例如响应头中不应包含 server: python等字段)。
重新修改蜜罐代码。按照上述思路,蜜罐响应根路径并且返回数据库的基本信息(模仿真实的服务)。提交,发现还是0分,再根据日志信息看。发现攻击者在第一次请求的时候没有带认证字段,这个时候如果是真实的服务应该会响应401错误码的。于是再次修改蜜罐代码,当攻击者请求根路径不包含认证信息时,响应401;包含认证信息,且验证是datacon|datacon时响应数据库基本信息。这样之后就发现已经获得了一些分数,说明前面的操作是正确的,已经通过了攻击者的一些检查点。
此后按部就班的进一步看日志-修改代码-提交即可。
2.5.2 攻击者执行动作记录
序号 | 动作 | 响应 |
1 | 建立TLS连接 | |
2 | GET / 不带认证 | 401 |
3 | GET / 带认证 | 200 |
4 | GET /_cat/indices | 200 |
5 | GET /_nodes | 200 |
6 | PUT /ehbfd(可变) | 200 |
7 | PUT /ehbfd 再次 | 400 |
8 | GET /_cat/indices | 200 |
9 | POST /ehbfd/_doc | 201 |
10 | POST /ehbfd/_refresh | 201 |
11 | POST /ehbfd/_search | 200 |
12 | DELETE /ehbfd/_doc/xx | 200 |
13 | DELETE /ehbfd | 200 |
-点击查看更多赛道解题报告-
感谢合作伙伴的助力 让我们走得更高更远