端口扫描中速度与准确度的平衡

0x01 介绍

​ 侦察是每个bug bounty(漏洞赏金)和渗透测试过程中最重要的阶段,一次好的侦察能决定成败。侦察可以分为两类:主动和被动。在主动侦察期间主要使用的方法之一就是端口扫描。渗透测试人员和bug hunters(漏洞赏金猎人)使用端口扫描确定目标主机和网络上的哪些端口是开放的,以及识别在这些端口上运行的服务。

​ 但是,端口扫描总是需要在速度和精度之间进行权衡。在渗透测试期间,测试人员的时间都是有限的;而在bug bounty过程中,大家都是争先恐后的发现并提交漏洞,拼的是速度。这些原因迫使我们在端口扫描时优先考虑的是速度,而不是精度。而在于时间赛跑的过程中,我们可能会错过一些开放的端口,而恰巧这些端口可能就存在漏洞,并且能成功利用。

​ 本次研究旨在利用开源和大家熟知的工具在端口扫描期间找到速度和准确度之间的平衡。

0x02 端口扫描概述

​ 端口扫描是侦察期间最常用的技术之一。渗透测试人员和bug bonty用于识别主机上可用的开放端口,以及识别这些开放端口上运行的服务。

端口扫描器可以根据他们的操作方式分类为:面向连接(同步模式)扫描器和无连接的(异步模式)扫描器。

面向连接(同步模式)

​ 这种类型的扫描器想目标端口发送请求并等待响应,直到超时时间到期。这种类型扫面器的缺点是性能比较慢,因为扫描器在当前连接关闭之前不会去扫描下一个目标端口或ip。

面向连接的扫描器好处是它们更准确,因为它们可以识别丢弃的数据包。
面向连接扫描器最流行就是我们熟知的Nmap

无连接(异步模式)

无连接扫描器不依赖于当前被探测端口的完成来启动下一个端口,因为它们有单独的发送和接受线程。这允许它们进行高速扫描。但是,这些扫描器的结果可能不太准确,因为它们无法检测丢失的数据包。

MasscanZmap是目前最流行的两种无连接扫描器。

0x03 Nmap VS Masscan

本次研究只包括Nmap和Masscan。虽然Zmap是一个快速的扫描器,并且扫描结果还不错。但是根据经验,即使同时运行多个扫描任务,Zmap的扫描速度仍然很慢。

虽然Nmap和Masscan都提供了良好的性能、特性和扫描结果。但它们 仍然有自己的弱点。下表展示了这两种工具的优缺点。

Nmap Masscan
优点 -两者对比起来,它更精确(使用同步模式) -有很多功能 -同时接受域名和IP地址(IPv4和IPv6) -速度非常快(使用异步模式) -语法与Nmap非常相似
缺点 -扫描数十万目标的时候速度非常慢 -在高速率(rates)扫描大端口范围时结果不太准确[1] -不接受域名作为目标输入 -不能根据自身环境自动调整传输速率

0x04 研究思路

基于上面列出的工具的有点和缺点,在试图找到速度和准确度之间的平衡时,确定了一下解决方案和问题。

解决方案

以下是基于工具的优点而形成的:

  1. 将Nmap的准确性及其其他的功能与Masscan的速度相结合。
  2. 使用Masscan执行初始端口的扫描,以识别开放的端口和开端口的主机。
  3. 使用Masscan的结果(已识别的开放端口和主机)作为Nmap的输入,以进行详细的端口扫描。

问题

虽然上面列出的想法很好,但是我们仍然需要解决每个工具的缺点。具体来说,我们需要解决的有:

  1. 当扫描数数万个目标的时候,Nmap的速度很慢。
  2. Masscan在高速(rates)扫描大端口范围时的不准确性(参见Github的Issues 365)。

0x05 研究配置

目标网络

选择一下子网作为本次研究的网络目标:

目标 子网
A A.A.0.0/16
B B.B.0.0/16
C C.C.0.0/16
D D.D.0.0/16

测试用例

对于本次研究,两种工具都有自己的一些测试用例。这些测试用例是每种工具中可用的不同选项(参数)的变化。这些测试用例旨在解决工具的缺点,并利用它们的优点在速度和准确性之前找到平衡点。

Masscan:

  1. 以不同的速率(rates)定期扫描所有的TCP端口。
  2. 将/16的目标子网差分为/20,并运行X个并发masscan任务,每个任务的速率为Y。
  3. 将1-65535的端口范围划分为几个范围,并运行X个并发的Masscan任务,每个任务的速率为Y。

Nmap:

  1. 定期扫描所有的TCP端口。
  2. 使用X并发任务扫描所有的TCP端口。
  3. 扫描Masscan识别的开放端口和主机的组合列表。
  4. 扫描Masscan识别特定主机上的特定开放端口。

在有限的时间内不可能涵盖所有选项的每个变化/组合,因此仅涵盖上述内容。

对于使用并发任务的测试用例,使用了工具GNU Parallel。如果你对这个工具还是个新手,请查看详细的教程

0x06 范围和限制

  • 该研究使用以下版本的工具进行:Nmap v7.70和Masscan v1.0.5-51-g6c15edc;
  • 该研究仅涉及IPv4地址;
  • 不包括扫描UDP端口;
  • 仅使用了最流行和开源的工具(不包括Zmap,因为它一次只能扫描一个端口;即使运行多个任务,也会导致扫描速度非常慢);
  • 仅探测了4个目标网络,都是/16;
  • 端口扫描仅来自一台机器,且这台机器的ip地址是固定ip;
  • 由于扫描机器不支持PF_RING,因此Masscan的速率仅限于250kpps(每秒数据包)
  • 并不是所有的测试用例都是由有限的资源而进行的(这样做非常耗时)。

0x07 Masscan的测试用例和测试结果

本节详细介绍了使用Masscan执行的不同测试用例和其测试结果。

测试用例 1:使用不同的速率定期扫描所有的TCP端口

这个测试用例没啥特别之处,这只是Masscan的正常扫描,只是速率不同而已。

以下命令用于启动此扫描用例的扫描任务:

1
masscan -p 1-65535 --rate RATE--wait 0 --open TARGET_SUBNET -oG TARGET_SUBNET.gnmap

rate(扫描速率)参数的设置:

  • 1000000 (1M)
  • 100000 (100K)
  • 50000 (50K)

在实验过程中,我得VPS可以运行的最大速率仅为250kpps左右。这是因为扫描的机器不支持PF_RING。

图表(由于最大速率是250kpps,故图表中的为250k、100k和50k的对比):

观察:

  • 较慢的速率会导致更多的开放端口,但代价是会扫描时间更长。

测试用例 2:将/16的目标子网拆分为/20,并运行X个Masscan并发任务,每个任务的速率为Y

为了能够运行并发任务,我觉得将/16的目标子网拆分为更小的子网。你可以将其分为更小的子网,例如/24。本次研究我拆分为/20。

要将目标网络拆分为更小的子网,使用的python代码如下:

1
2
3
4
5
6
7
8
#!/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命令来运行并行任务。

1
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 (–rate 100000 )
  • 5个任务/每个任务的速率是50kpps (–rate 50000)
  • 20个任务/每个任务的速率是10kpps (–rate 10000)

说明:

  • 大家可以注意到,我上面说的任务数和速率中第一个(5个任务/每个任务的速率是100kpps),我计算错了。因为它的总速率是500kpps,而我的机器只能处理250kpps。尽管如此,这个的测试结果仍然是有价值的,将可以在下面的图表里看到。
  • 其他的组合,例如10个任务,每个任务的速率20kpps,这样是可行的。但是由于时间和预算有限,我不能把所有可能的组合都涵盖了。

图表如下:

观察:

  • 当前的方案会比常规扫描(测试用例 #1)快2-3倍,但是导致开放的端口更少了。
  • 使用扫描机器的最大速率将导致扫描出的开放端口数更少(五个任务/每个任务100k的扫描速率)。
  • 少任务数&高扫描速率(例如5个任务/每个任务的速率50k)比多任务数&低扫描速率(例如20个任务/每个任务的速率10k)的效果好。

测试用例 3:将1-65535端口范围拆分为多个更小的范围,运行X个Masscan并发任务,每个任务的扫描速率为Y

第三个测试用例是为了解决在扫描大端口范围的时候,上文提到的Masscan的问题,特别是整个1-65535这样的范围。我的解决方案是将1-65535的范围拆分为更小的范围。

就像之前的测试用例一样,所使用的任务数&扫描速率组合的总速率是基于机器最大容量的80-90%这样的想法。

以下的命令用于本次的测试用例,PORT_RANGES是包含端口范围列表,然后使用parallel命令来运行并行任务。

1
cat PORT_RANGES | parallel -j JOBS "masscan -p {} --rate RATE --wait 0 --open TARGET_SUBNET -oG {}.gnmap"

1-65535端口范围分为四种拆分方式,如下所示,每种拆分方式包含任务和速率的组合/变化。

拆分方式 1:拆分为5个端口范围

1
2
3
4
5
1-13107
13108-26214
26215-39321
39322-52428
52429-65535

任务数和速率如下:

  • 5个扫描任务/每个任务50k的扫描速率 (–rate 50000)
  • 2个扫描任务/每个任务100k的扫描速率 (–rate 100000)

图表如下:

拆分方式 2:拆分为2个端口范围

1
2
1-32767
32768-65535

任务数和速率如下:

  • 2个扫描任务/每个任务100k的扫描速率 (–rate 100000)
  • 2个扫描任务/每个任务125k的扫描速率 (–rate 125000)

图表如下:

拆分方式 3: 拆分为8个端口范围

1
2
3
4
5
6
7
8
1-8190
8191-16382
16383-24574
24575-32766
32767-40958
40959-49151
49152-57343
57344-65535

任务数和速率如下:

  • 4个扫描任务/每个任务50k的扫描速率 (–rate 50000)
  • 2个扫描任务/每个任务100k的扫描速率 (–rate 100000)

图表如下:

拆分方式 4: 拆分为4个端口范围

1
2
3
4
1-16383
16384-32767
32768-49151
49152-65535

任务数和速率如下:

  • 2个扫描任务/每个任务100k的扫描速率 (–rate 100000)

本次测试我之所以只使用了一种任务数&速率的组合,是因为我意识到我已经超过了每个月的带宽限制。这样我不得不多付100+美元。

图表如下:

观察:

下面列出的观察结果涵盖了上面提到的所有4个拆分方式的方案。

  • 拆分端口范围会扫描出更多的开放端口(这样解决了Masscan在扫描大范围端口时的问题);
  • 使用更少的并行任务(本次测试中是2个并行任务)会扫描出更多的开放端口;
  • 在所有的拆分方案的测试中,拆分为5个端口范围(拆分方式# 1)的扫描结果最佳。

原始数据

下表显示了使用上述不同Masscan测试用例进行实验的原始数据:

Masscan结论:

根据使用Masscan进行的所有测试用例的结果,得出以下结论:

  • 以100%的CPU利用率运行扫描任务,会导致端口开放性降低;
  • 使用机器能运行的最大速率容量进行扫描会导致更少的端口开放;
  • 当使用并发任务时,较少的任务数会扫描出更多的开放端口;
  • 拆分端口范围的方式比拆分目标子网的方式要好;
  • 对于端口范围拆分的方式,(拆分方式 #1 和拆分方式 #4)的扫描结果是最佳的。

0x08 Nmap的测试用例和测试结果

在此阶段,只执行版本扫描。Nmap的NSE,OS探测和其他扫描功能都没有涉及。Nmap的线程被限制为T4,等同于如下命令:

1
--max-rtt-timeout=1250ms --min-rtt-timeout=100ms --initial-rtt-timeout=500ms --max-retries=6 --max-scan-delay=10ms

以下Nmap选项也用于模拟masscan使用的选项。这些选项应用于所有Nmap测试用例。

使用的Nmap选项如下:

  • SYN扫描方式(-sS
  • 端口服务版本扫描(-sV
  • 线程(-T4
  • 随机选择扫描对象(--randomize-hosts
  • no ping(-Pn
  • no DNS解析(-n

测试用例 1:定期扫描所有的TCP端口

这个测试用例只是使用Nmap的正常扫描,所以没啥特别之处。使用的命令如下:

1
sudo nmap -sSV -p- -v --open -Pn -n --randomize-hosts -T4 TARGET_SUBNET -oA OUTPUT

观察:

  • 扫描了四天半以后,扫描任务仍然没有完成。这就是前文提到的缺点之一:扫描大型网络目标的时候,Nmap的速度非常慢;
  • 由于性能太低,我决定取消这个扫描任务。

测试用例 2:使用X个并发任务扫描所有的TCP端口

在这种情况下,我尝试通过运行并发的Nmap扫描任务来解决Nmap的低性能问题。通过将目标子网划分为较小的子网块来完成,就像上面Masscan测试的那样。同样,下面的代码(split.py)用于拆分目标子网:

1
2
3
4
5
6
7
8
#!/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)

运行命令如下:

1
python3 split.py TARGET_SUBNET 20 | parallel -j JOBS "sudo nmap -sSV -p- -v --open -Pn -n --randomize-hosts -T4 {} -oA {//}"

对于这个测试用例,我决定使用两个并发任务实例,如下所示:

使用5个并发任务: /16的目标子网拆分为/20的子网*

观察:

  • 也很慢。扫了2.8天,仍然没扫完,所以我取消了。

使用64个并发任务: /16的目标子网拆分为/24的子网*

观察:

  • 五天过去了,扫描仍然没有完成,所以我也取消了。

测试用例 3: 扫描Masscan识别出的开放端口和主机的组合列表

这个测试用例背后的想法是,首先获得一个主机列表和一个由Masscan扫描出的开放端口的组合列表。这个开放端口的组合列表被用作基线(如下图图表中的绿色条所示),以确定下面的Nmap测试用例能否能检测出更多或更少的k开放端口。

例如,Masscan检测到300个开放端口,而常规Namp扫描检测到320个开放端口。但是,当使用5个并发Nmap任务扫描时,仅检测到295个开放端口。这意味着常规的Nmap扫描是更好的选择。

要从Masscan的扫描结果中获得主机列表,使用如下命令:

1
grep "Host:" MASSCAN_OUTPUT.gnmap | cut -d " " -f2 | sort -V | uniq > HOSTS

下图显示了上述命令的运行情况:

下面的命令用于获取Masscan检测到的所有开放端口的组合列表:

1
grep "Ports:" MASSCAN_OUTPUT.gnmap | cut -d " " -f4 | cut -d "/" -f1 | sort -n | uniq | paste -sd, > OPEN_PORTS

下图显示了上述命令的运行情况:

下面的命令用户Nmap的常规扫描:

1
sudo nmap -sSV -p OPEN_PORTS -v --open -Pn -n --randomize-hosts -T4 -iL HOSTS -oA OUTPUT

以下命令用于运行并发的Nmap扫描任务。使用上面命令生成的主机列表和开放端口的组合列表。

1
cat HOSTS | parallel -j JOBS "sudo nmap -sSV -p OPEN_PORTS -v --open -Pn -n --randomize-hosts -T4 {} -oA {}"

使用的并发任务数:

  • 0 (这是常规的nmap扫描)
  • 10
  • 50
  • 100

图表如下:

观察:

运行常规的Nmap扫描时,CPU的利用率仅为10%左右;

常规的Nmap扫描发现了更多的开放端口,而并发的Nmap扫描发现的开放端口较少一些。

与基线(上面图表中的绿色条)相比,在某些目标网络(子网A)上识别出更多的开放端口,而在其他的网络目标(子网B和子网C)上检测到的开放端口较少,在某些网络目标(子网D)上没有太大差异。

Nmap检测到的其他开放端口

先看下面的表格。例如,让我们假设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

测试用例 4 扫描由Masscan识别的特定主机上的特定开放端口

这个与之前的测试用例有点类似。在这个用例中,我没有将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

以下命令用于获取主机列表:

1
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

下图显示了上述命令的运行情况:

要从每个主机获取打开的端口列表,执行以下命令:

1
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,请查看本教程。

1
parallel -j JOBS --link "sudo nmap -sSV -p {2} -v --open -Pn -n -T4 {1} -oA {1}" :::: HOSTS :::: OPEN_PORTS

这是个例子,当执行上述parallel命令后,并扫描时会发生什么(多条命令同时执行)。

1
2
3
4
5
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扫描。

使用的并发任务数:

  • 10
  • 50
  • 100

图表如下:

观察:

  • 更多的并发任务和以100%的CPU利用率进行扫描时,检测出更少的开放端口。
  • 10个和50个Nmap并发任务,扫描结果差别不大,因此建议可以运行50个并发任务,以减少扫描时间。
  • 此测试用例比测试用例 #3的扫描速度略快,但是检测出的开放端口较少。

原始数据

下表显示了使用上述不同的Nmap测试用例进行实验的原始数据:

Nmap结论:

根据使用Nmap进行的实验结果,得出以下结论:

  • 测试用例 #3(扫描Masscan识别出的开放端口和主机的组合列表)可获得最佳的结果。这也是推荐的方法,因为可以发现额外的端口开放;
  • 以100%的CPU利用率进行扫描,会导致检测出更少的开放端口;
  • 使用并发任务时,更少的任务数会导致检测出更多的开放端口;

0x09 研究结论

推荐的扫描方法

根据对Masscan和Nmap进行的多个测试用例的测试结果,建议采用以下方法在端口扫描期间实现速度和精度之间的平衡:

  1. 首先运行2或3个并发的Masscan任务,所有的65535个端口分为4-5个更小的范围;
  2. 获取主机列表以及Masscan扫描出的开放端口的组合列表;
  3. 使用这些列表作为Nmap的扫描目标并执行常规Nmap扫描。

注意事项

对于这两种扫描端口的工具,应采用以下的预防措施进行规避,因为它们会导致检测到的开放端口更少:

  • 扫描时避免CPU过载。
  • 不要使用扫描机器的最大速率容量。
  • 避免运行太多并行任务。

0x10 最后的想法

虽然这项研究提供了一种如何在互联网端口扫描期间平衡速度和准确性的方法,但读者不应将此结论视为100%可靠。由于时间和预算有限,研究期间没有涵盖其他的影响因素。最值得注意的是,在整个研究期间仅使用一个IP地址进行扫描并不是一个好的设置。因为在我多次扫描相同的目标网络后,机器的IP地址可能会以某种方式被拉黑,这可能导致检测到的开放端口数量不太一致。

请重新查看0x06 范围和限制部分,因为从中可以很好的理解影响本研究结果的一些因素。

0x11 Reference

文章翻译自: https://captmeelo.com/pentest/2019/07/29/port-scanning.html

作者: Capt. Meelo