扫描对外公开的服务器信息,是 nmap 的主要用途之一。为了识别服务器上运行的是哪种服务,nmap 把主流服务的特征信息存储在 nmap-services-probes 和 nmap-os-db 这两个文件中。其中前者包含应用程序的特征信息,后者放置操作系统的特征信息。不同操作系统上,这两个文件的位置不一样,可以通过 locate
或其他相似的全盘搜索工具找出它们具体的位置。
接下来让我们解读下这两个文件,看看 nmap 是如何利用这些信息来识别服务的。
服务识别
当我们指定 -sV
选项运行 nmap 时,它会根据 nmap-services-probes 文件里面存储的是服务类型特征数据去判断具体扫描到的是哪种服务。
nmap-services-probes 看上去比较乱。事实上,如果这部分数据可以组织成 xml 或 yaml 这种树型结构的格式,看上去会明显清晰得多。
贴个片段看下:
Probe TCP GetRequest q|GET / HTTP/1.0\r\n\r\n|
rarity 1
ports 1,70,79,80-85,88,113,139,143,280,497,505,514,515,540,554,591,620,631,783,888,898,900,901,1026,1080,1042,1214,1220,1234,1314,1344,1503,1610,1611,1830,1900,2001,2002,2030,2064,2160,2306,2396,2525,2715,2869,3000,3002,3052,3128,3280,3372,3531,3689,3872,4000,4444,4567,4660,4711,5000,5427,5060,5222,5269,5280,5432,5800-5803,5900,5985,6103,6346,6544,6600,6699,6969,7002,7007,7070,7100,7402,7776,8000-8010,8080-8085,8088,8118,8181,8880-8888,9000,9001,9030,9050,9080,9090,9999,10000,10001,10005,11371,13013,13666,13722,14534,15000,17988,18264,31337,40193,50000,55555
sslports 443,993,995,1311,1443,3443,4443,5061,7443,8443,9443,10443,14443,44443,60443
...
match http m|^HTTP/1\.[01] \d\d\d.*?\r\nServer: nginx\r\n|s p/nginx/ cpe:/a:igor_sysoev:nginx/
match http m|^HTTP/1\.[01] \d\d\d.*\r\nServer: nginx/([\d.]+)\r\n|s p/nginx/ v/$1/ cpe:/a:igor_sysoev:nginx:$1/
nmap-services-probes 没有直接列出各种服务的特征,而是描述了服务识别扫描过程中的操作步骤。其中 Probe
开头表示新起一个探测项。后面跟着的 TCP GetRequest
表示该探测项的种类和名字。q|GET / HTTP/1.0\r\n\r\n|
表示探测时采取的动作,这里就是发送一个最小的 GET 请求。
下一行的 rarity 表示该探测的罕见性。nmap 默认只会执行 rarity 7 以下的探测,不过可以通过 --version-intensity N
选项更改。
再往下,是需要执行该探测的端口列表。在发起识别服务的扫描之前,nmap 已经扫描出当前可用的端口列表了。对于列表中的端口,如果它在某个探测的端口列表里,nmap 就会对该端口执行对应的探测。不用担心每个端口都执行许多探测会明显减慢扫描速度 —— nmap 会并行扫描多个端口。
sslports 类似于 ports,不同之处在于生效的时间。当 nmap 完成 tls 握手后,会根据 sslports 登记的配置去运行对应的探测。
接下来是一连串的 match 语句。顾名思义,它们都是响应的匹配规则。其中 m 开头的是正则表达式,p 开头的是产品名字,v 开头的是版本号,cpe 开头的是 cpe 编号, i 开头表示附加的信息。这里列出的匹配规则表示,如果 GET 请求对应的响应匹配到 ^HTTP/1\.[01] \d\d\d.*?\r\nServer: nginx\r\n
,说明对应的服务是 Nginx。单个产品可能会有多个与之相关的规则,比如 nmap 会有以下方法去匹配 Nginx:
- 响应报头里包含 Server: nginx
- 错误页面中包含
<center>nginx</center>
- 构造一个畸形的 HTTP 请求,返回
<head><title>400 Bad Request</title></head>\r\n<h1>400 Bad Request</h1>
- 向 HTTPS 端口发送 HTTP 请求,返回
HTTP/1.1 400 Bad Request\r\n.*<title>400 The plain HTTP request was sent to HTTPS port</title>
match 语句最多只会匹配到一个服务。如果想匹配多个服务,比如 Nginx 代理的 PHP 应用,可以结合 softmmatch 使用。添加下面一行:
softmatch http m|^HTTP/1\.[01] .*\r\nX-Powered-By: PHP/(\d[\w._-]+)|s i/PHP $1/
最后输出结果里会多一个括号:8080/tcp open http nginx (PHP 5.6.25)
。
除了 HTTP 请求,nmap 也会用其他方式探测目标服务,比如这个为 Redis 量身定制的请求:Probe TCP redis-server q|\*1\r\n\$4\r\ninfo\r\n|
。甚至连 minecraft 都有:Probe TCP minecraft-ping q|\xFE\x01|
。
nmap-service-probes 开头的 Probe TCP NULL q||
值得介绍。这个 Probe 是为了检测出某些会主动给连上来的客户端发送数据的服务设置的。跟其他 Probe 不同,它不发送请求,只是干等 6 秒。当你给 nmap 抓包时,端口探测和服务识别间会相差 6 秒,就是这个探测导致的。
附赠一个列出所有可探测的服务的 Python 脚本:
import re
# 以实际位置替换下面的路径
nmap_service_probes_file = "/usr/local/share/nmap/nmap-service-probes"
pat = re.compile(r"^match .+ p/([^/]+)/ .+$")
products = set()
with open(nmap_service_probes_file) as f:
for line in f:
m = pat.search(line)
if m is not None and not m.group(1).startswith('$'):
# 会存在 product 名中间含有 $xx 变量的情况,这里就忽略吧
products.add(m.group(1))
for p in sorted(list(products)):
print(p)
操作系统探测
跟 -sV
选项类似,有一个名为 -O
的选项可以开启操作系统探测的扫描功能。依本人的经验,nmap 的 操作系统探测的准确度仅到操作系统类型这一级,对于具体的版本,其扫描结果可能会不准。
扫描过程中会用到 nmap-os-db 这个文件里面的特征信息识别遇到的 OS。如果说 nmap-service-probes 相对还好阅读,那 nmap-os-db 无疑便是天书一般了。还好 nmap 的文档里面对此有充分的解释,参见:
操作系统探测利用的是 IP/TCP 层面上的特征。在 nmap-os-db 里面,每个特征以 Fingerprint 开头,接着是该特征对应的操作系统信息,然后是具体的特征值。
举个例子,像下面这样的特征数据:
Fingerprint Linux 3.10
Class Linux | Linux | 3.X | general purpose
CPE cpe:/o:linux:linux_kernel:3.10 auto
SEQ(SP=FB-105%GCD=1-6%ISR=109-113%TI=Z%CI=I%II=I%TS=A)
OPS(O1=M548ST11NW7%O2=M548ST11NW7%O3=M548NNT11NW7%O4=M548ST11NW7%O5=M548ST11NW7%O6=M548ST11)
WIN(W1=3890%W2=3890%W3=3890%W4=3890%W5=3890%W6=3890)
ECN(R=Y%DF=Y%T=3B-45%TG=40%W=3908%O=M548NNSNW7%CC=Y%Q=)
T1(R=Y%DF=Y%T=3B-45%TG=40%S=O%A=S+%F=AS%RD=0%Q=)
T2(R=N)
T3(R=N)
T4(R=Y%DF=Y%T=3B-45%TG=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)
T5(R=Y%DF=Y%T=3B-45%TG=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)
T6(R=Y%DF=Y%T=3B-45%TG=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)
T7(R=Y%DF=Y%T=3B-45%TG=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)
U1(DF=N%T=3B-45%TG=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)
IE(DFI=N%T=3B-45%TG=40%CD=S)
看上去还是人话的前三行描述了该特征对应的 OS。需要解释的是鬼画符一般的后面几行。
每一行都是一项测试的结果,其中 XXX()
中的 XXX 是测试的名字。括号内以 %
分隔的各项是测试中的各种指标。每项指标的值都是 K-V 形式,K 是指标的名字,而 V 具体怎么解释,则各个指标各个不同。具体代表什么烦请查阅 https://nmap.org/book/osdetec... 。
所有的测试分成五组,详见 https://nmap.org/book/osdetec... 的说明。
第一组测试包含 SEQ、OPS、WIN、T1。这项测试会发送六个 TCP 包,然后检查响应的各种细节。
第二组测试是 IE,会发送两个不同的 ICMP echo 请求,检测其响应特征。
第三组测试是 U1,发送一个 UDP 包给一个关闭的端口,然后看下 ICMP 的 port unreachable 回复。
第四组测试是 ECN,全称是 Explicit Congestion Notification,即“显式拥塞通知”。该测试会发送带 ECN 位的 TCP 请求,比较其响应。
最后一组是发送六个不同的 TCP 包,这六个 TCP 包的响应结果将对应T2到T7的各项指标,其中 T2-T4 会发给打开的 TCP 端口,T5-T7 会发给关闭的 TCP 端口。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。