最近一直在开发公司内部使用的<巡检系统>,在涉及端口扫描这块,为了保证速度和准确性,方案是使用Masscan+Nmap:Masscan速度快,先使用Masscan进行一遍全端口探测。再使用Nmap对已经探测出来的端口进行复扫和Banner识别,Nmap的准确性较高,但速度较慢,所以通过这种方式保证端口扫描的准确性。
这是我对于端口扫描过程中寻找速度与准确度之间平衡点的浅显思路。前几天刚好看到一篇国外的文章,介绍的东西远比我理解的更深入,故翻译出来一起学习。
如下内容为翻译内容,原文链接如下:
inding the Balance Between Speed & Accuracy During an Internet-wide Port Scanning
侦察是每个bug bounty(漏洞赏金)和渗透测试过程中最重要的阶段,一次好的侦察能决定成败。侦察可以分为两类:主动和被动。在主动侦察期间主要使用的方法之一就是端口扫描。渗透测试人员和bug hunters(漏洞赏金猎人)使用端口扫描确定目标主机和网络上的哪些端口是开放的,以及识别在这些端口上运行的服务。
但是,端口扫描总是需要在速度和精度之间进行权衡。在渗透测试期间,测试人员的时间都是有限的;而在bug bounty过程中,大家都是争先恐后的发现并提交漏洞,拼的是速度。这些原因迫使我们在端口扫描时优先考虑的是速度,而不是精度。而在于时间赛跑的过程中,我们可能会错过一些开放的端口,而恰巧这些端口可能就存在漏洞,并且能成功利用。
本次研究旨在利用开源和大家熟知的工具在端口扫描期间找到速度和准确度之间的平衡。
端口扫描是侦察期间最常用的技术之一。渗透测试人员和bug bonty用于识别主机上可用的开放端口,以及识别这些开放端口上运行的服务。
端口扫描器可以根据他们的操作方式分类为:面向连接(同步模式)扫描器和无连接的(异步模式)扫描器。
这种类型的扫描器想目标端口发送请求并等待响应,直到超时时间到期。这种类型扫面器的缺点是性能比较慢,因为扫描器在当前连接关闭之前不会去扫描下一个目标端口或ip。
面向连接的扫描器好处是它们更准确,因为它们可以识别丢弃的数据包。
面向连接扫描器最流行就是我们熟知的Nmap。
无连接扫描器不依赖于当前被探测端口的完成来启动下一个端口,因为它们有单独的发送和接受线程。这允许它们进行高速扫描。但是,这些扫描器的结果可能不太准确,因为它们无法检测丢失的数据包。
Masscan和Zmap是目前最流行的两种无连接扫描器。
本次研究只包括Nmap和Masscan。虽然Zmap是一个快速的扫描器,并且扫描结果还不错。但是根据经验,即使同时运行多个扫描任务,Zmap的扫描速度仍然很慢。
虽然Nmap和Masscan都提供了良好的性能、特性和扫描结果。但它们 仍然有自己的弱点。下表展示了这两种工具的优缺点。
Nmap | Masscan | |
---|---|---|
优点 | -两者对比起来,它更精确(使用同步模式) -有很多功能 -同时接受域名和IP地址(IPv4和IPv6) |
-速度非常快(使用异步模式) -语法与Nmap非常相似 |
缺点 | -扫描数十万目标的时候速度非常慢 | -在高速率(rates)扫描大端口范围时结果不太准确[1] -不接受域名作为目标输入 -不能根据自身环境自动调整传输速率 |
基于上面列出的工具的有点和缺点,在试图找到速度和准确度之间的平衡时,确定了一下解决方案和问题。
以下是基于工具的优点而形成的:
虽然上面列出的想法很好,但是我们仍然需要解决每个工具的缺点。具体来说,我们需要解决的有:
选择一下子网作为本次研究的网络目标:
目标 | 子网 |
---|---|
A | A.A.0.0/16 |
B | B.B.0.0/16 |
C | C.C.0.0/16 |
D | D.D.0.0/16 |
对于本次研究,两种工具都有自己的一些测试用例。这些测试用例是每种工具中可用的不同选项(参数)的变化。这些测试用例旨在解决工具的缺点,并利用它们的优点在速度和准确性之前找到平衡点。
在有限的时间内不可能涵盖所有选项的每个变化/组合,因此仅涵盖上述内容。
对于使用并发任务的测试用例,使用了工具GNU Parallel。如果你对这个工具还是个新手,请查看详细的教程。
本节详细介绍了使用Masscan执行的不同测试用例和其测试结果。
这个测试用例没啥特别之处,这只是Masscan的正常扫描,只是速率不同而已。
以下命令用于启动此扫描用例的扫描任务:
masscan -p 1-65535 --rate RATE--wait 0 --open TARGET_SUBNET -oG TARGET_SUBNET.gnmap
rate(扫描速率)参数的设置:
在实验过程中,我得VPS可以运行的最大速率仅为250kpps左右。这是因为扫描的机器不支持PF_RING。
图表(由于最大速率是250kpps,故图表中的为250k、100k和50k的对比):
观察:
慢速率会导致发现更多的开放端口,但是代价就是扫描花费的时间更长。
为了能够运行并发任务,我觉得将/16的目标子网拆分为更小的子网。你可以将其分为更小的子网,例如/24。本次研究我拆分为/20。
要将目标网络拆分为更小的子网,使用的python代码如下:
#!/usr/bin/python3 import ipaddress, sys target = sys.argv[1] prefix = int(sys.argv[2]) for subnet in ipaddress.ip_network(target).subnets(new_prefix=prefix): print(subnet)
以下是该代码的运行截图:
每项任务所用的速率都是基于扫描机器能够处理的速率最大化思想。在我的例子中,我的扫描机器最大只能处理250kpps,所以如果我要运行5个并行任务,每个任务可使用50kpps的速率。
由于机器的最大速率不是“绝对”的(在本次测试中不完全都是250kpps的速率),你可以设置每个任务的速率,使总速率等于最大速率的80%-90%。
对于本项测试,执行了以下命令。通过split.py来划分成较小的子网,然后使用parallel命令来运行并行任务。
python3 split.py TARGET_SUBNET 20 | parallel -j JOBS "masscan -p 1-65535 --rate RATE--wait 0 --open {} -oG {//}.gnmap"
以下是执行上述命令时的截图。在这种情况下,20个Masscan任务,每个任务的速率为10kpps,同时运行。
任务数和速率如下:***
说明:
- 大家可以注意到,我上面说的任务数和速率中第一个(5个任务/每个任务的速率是100kpps),我计算错了。因为它的总速率是500kpps,而我的机器只能处理250kpps。尽管如此,这个的测试结果仍然是有价值的,将可以在下面的图表里看到。
- 其他的组合,例如10个任务,每个任务的速率20kpps,这样是可行的。但是由于时间和预算有限,我不能把所有可能的组合都涵盖了。
图表如下:
观察:
第三个测试用例是为了解决在扫描大端口范围的时候,上文提到的Masscan的问题,特别是整个1-65535这样的范围。我的解决方案是将1-65535的范围拆分为更小的范围。
就像之前的测试用例一样,所使用的任务数&扫描速率组合的总速率是基于机器最大容量的80-90%这样的想法。
以下的命令用于本次的测试用例,PORT_RANGES是包含端口范围列表,然后使用parallel命令来运行并行任务。
cat PORT_RANGES | parallel -j JOBS "masscan -p {} --rate RATE --wait 0 --open TARGET_SUBNET -oG {}.gnmap"
1-65535端口范围分为四种拆分方式,如下所示,每种拆分方式包含任务和速率的组合/变化。
1-13107 13108-26214 26215-39321 39322-52428 52429-65535
任务数和速率如下:
图表如下:
任务数和速率如下:
图表如下:
1-8190 8191-16382 16383-24574 24575-32766 32767-40958 40959-49151 49152-57343 57344-65535
任务数和速率如下:
图表如下:
1-16383 16384-32767 32768-49151 49152-65535
任务数和速率如下:
本次测试我之所以只使用了一种任务数&速率的组合,是因为我意识到我已经超过了每个月的带宽限制。这样我不得不多付100+美元。
图表如下:
观察:
下面列出的观察结果涵盖了上面提到的所有4个拆分方式的方案。
原始数据
下表显示了使用上述不同Masscan测试用例进行实验的原始数据:
根据使用Masscan进行的所有测试用例的结果,得出以下结论:
在此阶段,只执行版本扫描。Nmap的NSE,OS探测和其他扫描功能都没有涉及。Nmap的线程被限制为T4,等同于如下命令:
--max-rtt-timeout=1250ms --min-rtt-timeout=100ms --initial-rtt-timeout=500ms --max-retries=6 --max-scan-delay=10ms
以下Nmap选项也用于模拟masscan使用的选项。这些选项应用于所有Nmap测试用例。
使用的Nmap选项如下:
-sS
)-sV
)-T4
)--randomize-hosts
)-Pn
)-n
)这个测试用例只是使用Nmap的正常扫描,所以没啥特别之处。使用的命令如下:
sudo nmap -sSV -p- -v --open -Pn -n --randomize-hosts -T4 TARGET_SUBNET -oA OUTPUT
观察:
在这种情况下,我尝试通过运行并发的Nmap扫描任务来解决Nmap的低性能问题。通过将目标子网划分为较小的子网块来完成,就像上面Masscan测试的那样。同样,下面的代码(split.py)用于拆分目标子网:
#!/usr/bin/python3 import ipaddress, sys target = sys.argv[1] prefix = int(sys.argv[2]) for subnet in ipaddress.ip_network(target).subnets(new_prefix=prefix): print(subnet)
运行命令如下:
python3 split.py TARGET_SUBNET 20 | parallel -j JOBS "sudo nmap -sSV -p- -v --open -Pn -n --randomize-hosts -T4 {} -oA {//}"
对于这个测试用例,我决定使用两个并发任务实例,如下所示:
*使用5个并发任务:**/16的目标子网拆分为/20的子网*
观察:
*使用64个并发任务:**/16的目标子网拆分为/24的子网*
观察:
这个测试用例背后的想法是,首先获得一个主机列表和一个由Masscan扫描出的开放端口的组合列表。这个开放端口的组合列表被用作基线(如下图图表中的绿色条所示),以确定下面的Nmap测试用例能否能检测出更多或更少的k开放端口。
例如,Masscan检测到300个开放端口,而常规Namp扫描检测到320个开放端口。但是,当使用5个并发Nmap任务扫描时,仅检测到295个开放端口。这意味着常规的Nmap扫描是更好的选择。
要从Masscan的扫描结果中获得主机列表,使用如下命令:
grep "Host:" MASSCAN_OUTPUT.gnmap | cut -d " " -f2 | sort -V | uniq > HOSTS
下图显示了上述命令的运行情况:
下面的命令用于获取Masscan检测到的所有开放端口的组合列表:
grep "Ports:" MASSCAN_OUTPUT.gnmap | cut -d " " -f4 | cut -d "/" -f1 | sort -n | uniq | paste -sd, > OPEN_PORTS
下图显示了上述命令的运行情况:
下面的命令用户Nmap的常规扫描:
sudo nmap -sSV -p OPEN_PORTS -v --open -Pn -n --randomize-hosts -T4 -iL HOSTS -oA OUTPUT
以下命令用于运行并发的Nmap扫描任务。使用上面命令生成的主机列表和开放端口的组合列表。
cat HOSTS | parallel -j JOBS "sudo nmap -sSV -p OPEN_PORTS -v --open -Pn -n --randomize-hosts -T4 {} -oA {}"
使用的并发任务数:
图表如下:
观察:
运行常规的Nmap扫描时,CPU的利用率仅为10%左右;
常规的Nmap扫描发现了更多的开放端口,而并发的Nmap扫描发现的开放端口较少一些。
与基线(上面图表中的绿色条)相比,在某些目标网络(子网A)上识别出更多的开放端口,而在其他的网络目标(子网B和子网C)上检测到的开放端口较少,在某些网络目标(子网D)上没有太大差异。
先看下面的表格。例如,让我们假设Masscan在每台主机上检测到以下的开放端口(表格第2列)。在运行Nmap扫描时,Masscan检测到的所有开放端口将用作Nmap的目标端口(表格第3列)。
在我们的示例中,Nmap在完成扫描后检测到的新开放的端口(第4列中的粗体文字)。这种情况是怎么发生的?Masscan是一个异步的扫描器,主机192.168.1.2和192.168.1.3上可能丢失了22端口。由于我们合并了每个主机上检测到的开放端口,并将它们作为Nmap的目标端口,因此这个丢失的22端口将再次进行探测。需要注意的是,无法保证Nmap能够将其检测为开放状态,因为还有其他可能影响扫描结果的因素。
主机 | Masscan检测到的端口 | Nmap扫描的目标端口 | Nmap运行后检测到的开放端口 |
---|---|---|---|
192.168.1.1 | 22,80,443 | 22,80,443,8080,8888 | 22,80,443 |
192.168.1.2 | 8080,8888 | 22,80,443,8080,8888 | 22,8080,888 |
192.168.1.3 | 80,443 | 22,80,443,8080,8888 | 22,80,443 |
这个与之前的测试用例有点类似。在这个用例中,我没有将Masscan检测到的所有开放端口与每个主机组合在一起。无论Masscan在特定主机上检测到哪些开放端口,Nmap都将使用相同的端口作为目标端口。下表说明了我们这个测试用例中的操作:
主机 | Masscan检测到的端口 | Nmap扫描的目标端口 |
---|---|---|
192.168.1.1 | 22,80,443 | 22,80,443 |
192.168.1.2 | 8080,8888 | 8080,8888 |
192.168.1.3 | 80,443 | 80,443 |
以下命令用于获取主机列表:
cat MASSCAN_OUTPUT.gnmap | grep Host | awk '{print $2,$5}' | sed 's@/.*@@' | sort -t' ' -n -k2 | awk -F' ' -v OFS=' ' '{x=$1;$1="";a[x]=a[x]","$0}END{for(x in a) print x,a[x]}' | sed 's/, /,/g' | sed 's/ ,/ /' | sort -V -k1 | cut -d " " -f1 > HOSTS
下图显示了上述命令的运行情况:
要从每个主机获取打开的端口列表,执行以下命令:
cat MASSCAN_OUTPUT.gnmap | grep Host | awk '{print $2,$5}' | sed 's@/.*@@' | sort -t' ' -n -k2 | awk -F' ' -v OFS=' ' '{x=$1;$1="";a[x]=a[x]","$0}END{for(x in a) print x,a[x]}' | sed 's/, /,/g' | sed 's/ ,/ /' | sort -V -k1 | cut -d " " -f2 > OPEN_PORTS
下图显示了上述命令的运行情况:
可以看到,上图输出的内容于测试用例 #3中的不同,而且使用的命令也不一样。我们查询出每个主机的开放端口列表,而不是所有开放端口的组合。
然后使用parallel命令的::::
选项将上面两个命令查询出的列表,并发执行Nmap扫描。
如果您不熟悉GNU Parallel,请查看本教程。
parallel -j JOBS --link "sudo nmap -sSV -p {2} -v --open -Pn -n -T4 {1} -oA {1}" :::: HOSTS :::: OPEN_PORTS
这是个例子,当执行上述parallel命令后,并扫描时会发生什么(多条命令同时执行)。
sudo nmap -sSV -p 443 -v --open -Pn -n -T4 192.168.1.2 -oA 192.168.1.2 sudo nmap -sSV -p 80,443,1935,9443 -v --open -Pn -n -T4 192.168.1.5 -oA 192.168.1.5 sudo nmap -sSV -p 80 -v --open -Pn -n -T4 192.168.1.6 -oA 192.168.1.6 sudo nmap -sSV -p 80,443 -v --open -Pn -n -T4 192.168.1.7 -oA 192.168.1.7 sudo nmap -sSV -p 08,443 -v --open -Pn -n -T4 192.168.1.9 -oA 192.168.1.9
下图展示了测试用例执行时,发生的一个片段。如下图所示,使用parallel运行10个并发的Nmap扫描。
使用的并发任务数:
图表如下:
观察:
原始数据
下表显示了使用上述不同的Nmap测试用例进行实验的原始数据:
根据使用Nmap进行的实验结果,得出以下结论:
根据对Masscan和Nmap进行的多个测试用例的测试结果,建议采用以下方法在端口扫描期间实现速度和精度之间的平衡:
对于这两种扫描端口的工具,应采用以下的预防措施进行规避,因为它们会导致检测到的开放端口更少:
虽然这项研究提供了一种如何在互联网端口扫描期间平衡速度和准确性的方法,但读者不应将此结论视为100%可靠。由于时间和预算有限,研究期间没有涵盖其他的影响因素。最值得注意的是,在整个研究期间仅使用一个IP地址进行扫描并不是一个好的设置。因为在我多次扫描相同的目标网络后,机器的IP地址可能会以某种方式被拉黑,这可能导致检测到的开放端口数量不太一致。
请重新查看0x06 范围和限制部分,因为从中可以很好的理解影响本研究结果的一些因素。