ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

nmap使用教程:端口扫描、服务识别与NSE脚本实战详解

nmap使用教程:端口扫描、服务识别与NSE脚本实战详解

做网络排查和技术运维这些年,nmap是我用得最频繁的工具之一。你可能在无数地方见过这个名字,但真正遇到问题——服务端口连不上、内网设备找不到、安全审计要盘点资产——能一条命令把问题钉死的,多半就是它。这篇nmap使用教程我不打算复读官方文档,而是从一个实际使用者的角度,讲参数怎么选、脚本怎么用、输出怎么看,以及那些只有踩过坑才会懂的东西。无论你是运维要确认防火墙有没有放行端口,是开发要看服务到底有没有起来,还是安全方向的朋友要做基线检查,照着下面的思路操作,基本都能直接上手。我不会拽一堆专业名词吓唬人,但该讲清楚原理的地方一点也不会含糊——因为很多时候你用不好nmap,不是因为命令敲得不对,而是没搞懂它每一步在做什么。

1. 环境准备:装好nmap,先搞懂扫描的基本流程

1.1 安装与版本确认

nmap的安装没有太多门道,各发行版仓库里都有现成包。Linux上常见的是apt install nmap或yum install nmap,macOS用户用brew install nmap,Windows下直接下官方安装包,它会顺带把底层依赖的抓包驱动一起装好。

装完第一件事是确认版本:nmap --version。为什么要先看版本?因为脚本引擎和指纹库的更新频率很高,版本太老会导致两个问题:一是新协议的探测脚本跑不了,二是服务指纹识别结果可能出错。我遇到过拿老版本扫一个内网服务,版本号识别得不对,后来升级到新版就正常了。所以如果你要拿nmap做比较正式的资产梳理,开工前先确认版本不是多此一举。

1.2 扫描目标的写法与工作流程

nmap的目标写法非常灵活,理解和掌握这些写法能少敲很多命令:

  • 单个IP:nmap 192.168.1.100
  • 连续IP段:nmap 192.168.1.1-20,注意这个是第1到第20共20个地址
  • CIDR网段:nmap 192.168.1.0/24
  • 域名:nmap example.com
  • 文件列表:nmap -iL targets.txt,文件里每行一个目标,适合批量扫描

这里我要强调一下工作流程。nmap并非上来就对所有端口发探针,它分阶段:先做主机发现,确认目标是否在线;再做端口扫描,找出开放端口;接着可以做服务版本探测、操作系统识别,最后是脚本引擎。很多人觉得nmap慢或者结果不对,往往是在某个阶段配置有问题,比如目标主机禁ping,默认探活阶段就把主机判成down,后面的端口扫描自然就跳过了。这种情况需要加-Pn跳过探活,直接当目标在线来处理。

1.3 第一次运行:默认扫描结果怎么看

跑一条最简单的命令,看看输出长什么样:

nmap 192.168.1.100

输出大致是这样的:

Starting Nmap 7.94 ( https://nmap.org ) Nmap scan report for 192.168.1.100 Host is up (0.0021s latency). Not shown: 996 closed tcp ports (reset) PORT STATE SERVICE 22/tcp open ssh 80/tcp open http 443/tcp open https 3306/tcp open mysql MAC Address: 00:1B:44:11:3A:B7 (某设备) Nmap done: 1 IP address (1 host up) scanned in 2.54 seconds

先看Host is up,这里其实只是主机存活状态。然后“Not shown: 996 closed tcp ports”,这个信息很关键——nmap默认只扫1000个最常用的端口,其他端口根本没碰,不是不存在,只是没扫。STATE列是端口状态,SERVICE是nmap根据端口号推测的服务名。注意,默认扫描里SERVICE只是猜测,比如3306端口大概率是MySQL,但如果你在上面跑了别的服务,nmap是看不出来的,需要加-sV做真实版本探测。

1.4 端口的六种状态,先分清再优化

端口状态是理解所有扫描结果的基础。我把六种状态整理成一张表:

状态含义常见场景
open端口有程序监听并正常响应服务正常开放
closed端口可达但没有程序监听主机在线,但该端口未使用
filtered报文被防火墙规则丢弃,nmap无法判断防火墙默认丢弃策略
unfiltered端口可达,但nmap无法确定状态一般出现在ACK扫描
open|filtered无法区分是open还是filteredUDP扫描中很常见
closed|filtered无法区分是closed还是filteredIP协议扫描时出现

举个例子,你扫一个云主机看到大量filtered端口,说明安全组或防火墙规则很严格,默认丢弃没有放行的流量——这不一定是坏事。UDP扫描里出现open|filtered也很正常,因为UDP协议本身没有连接概念,端口开着未必回应,端口关着也未必回应,不像TCP那样通过握手行为就能判断。

搞清楚这六种状态之后,很多所谓的“异常结果”都能自己解释了。

2. 三大核心功能:探活、端口扫描、服务识别

2.1 主机发现:先确定目标在不在线

nmap的主机发现方式很多,常用的有:-sn仅主机发现,相当于ping扫描,不做端口扫描;-PS发送SYN包到指定端口;-PA发送ACK包;-PE基于ICMP Echo请求。默认情况下,nmap会同时用几种方式组合探测,包括ICMP和TCP探测。

-sn是我日常用得最多的探活命令。对内网一个网段做快速盘点,通常第一步就是:

nmap -sn 192.168.1.0/24

这个命令只确认哪些IP在线,几秒钟就能扫完一个C段。输出里会把在线主机的MAC地址也列出来,这个在内网资产识别时特别有用。不过要注意,如果目标禁ping、防火墙丢弃ICMP报文,默认探活可能判断失败,需要换用-PS指定的TCP探活,或者干脆-Pn跳过主机发现。跳过探活意味着对所有目标都做端口扫描,速度会慢一些,但在目标主机禁止Ping的场景下是唯一可行的方案。

2.2 端口扫描:SYN、Connect、UDP怎么选

端口扫描是nmap的主场,也是参数最密集的地方。先从最常见的三种说起。

-sS是TCP SYN扫描,也叫半开扫描。原理是nmap直接构造SYN包发给目标端口,收到SYN/ACK就判定为open,收到RST就判定为closed,没有响应就是filtered。因为不完成三次握手,目标应用层根本不会记录这次连接,速度也快。这是Linux环境下默认的扫描方式,也是我最推荐优先使用的。但它需要root权限,因为要绕过操作系统的TCP协议栈自己构造报文、自己解析响应。

-sT是TCP Connect扫描,走完整的三次握手。如果对方返回RST,端口closed;如果完成握手,端口open。这种方式不需要特殊权限,普通用户就能跑,但会在目标系统留下完整连接记录,速度也比SYN扫描慢。一般在没有root权限的Windows环境或者目标网络对半开扫描有检测机制时用。

-sU是UDP扫描,这是最容易让人崩溃的扫描方式。UDP没有握手过程,目标端口开着不一定回复,关着回复的ICMP报文还可能被限速。所以UDP扫描经常出现open|filtered,而且非常慢。但有些服务只跑UDP,比如DNS、SNMP、DHCP,你不扫UDP就永远不知道它们暴露在网络上。实际项目里,我通常不会对全端口做UDP扫描,而是明确知道目标可能跑哪个UDP服务时,只针对那几个端口扫。

2.3 服务版本与操作系统识别:深挖暴露面

刚才说过,默认扫描里SERVICE列只是猜端口号对应的服务。要拿到真实的服务版本,需要加-sV。它的原理是nmap先连接目标端口,然后根据端口类型发送一系列探测报文——比如对HTTP端口发送GET请求、对TLS端口发送ClientHello——再从目标响应中提取banner信息,和内置的指纹库比对。这个阶段会比较耗时,所以-sV默认有强度等级--version-intensity,范围0到9,默认7。级别越高,发的探针越多,识别越准确,也越慢。

系统识别用-O,原理是基于TCP/IP协议栈的指纹特征来判断操作系统类型。不同操作系统对TCP窗口大小、TTL值、分片策略的处理有细微差异,nmap通过这些差异跟指纹库比对。它同样需要root权限,而且在虚拟化环境或经过负载均衡设备时,识别结果可能不准。

实际使用中,很多人习惯直接用-A,它等于-sV -O -sC --traceroute的合集,一次把所有信息都拿了。这个组合对单个目标的排查非常高效,但不建议在大型网段直接铺开用,因为每个端口都要做版本探测、每台主机都要追踪路由,整体时间会成倍增加。正确的做法是先用普通扫描或SYN扫描做普查,挑出重点目标再用-A深挖。

3. 用脚本引擎扩展nmap能力

3.1 脚本引擎与脚本分类

nmap真正拉开和普通端口扫描器差距的地方,是它的脚本引擎NSE。NSE让nmap从“看看你开了哪些端口”进化成“针对这些端口做一系列自动检测”。脚本用Lua写成,按功能划分成很多类,常见的包括:

  • safe:不会对目标造成负面影响,可以放心跑
  • default:默认脚本集,兼顾信息收集和安全性
  • auth:与认证相关的探测
  • discovery:服务与资源发现
  • vuln:漏洞检测,只做检查不做利用
  • intrusive:具有侵入性,可能对目标产生噪音或影响
  • dos:拒绝服务相关测试,谨慎使用
  • malware:恶意软件相关检测

脚本分类很重要,因为选错脚本类别是会产生实际影响的。我见过有人拿intrusive类脚本去跑生产环境,把一个老旧服务的CPU直接打满。所以在生产网络或对外业务上扫描时,我建议优先选择safe和default类,dos和部分exploit类脚本只放在隔离的测试环境里用。

3.2 高频场景的脚本选型

几个高频用法先列出来。

-sC等同于--script=default,会在扫描时自动加载默认脚本集,对常见服务做信息采集,比如HTTP标题、SSH支持的算法、SMB共享列表等,很适合作为日常服务梳理的一部分。

漏洞信息收集用nmap --script=vuln -p 80,443 192.168.1.100,它会加载所有vuln类脚本,对指定端口做已知漏洞的被动检查。注意我用的是“被动检查”这个词,它只判断目标是否存在漏洞特征,不进行实际攻击尝试。但是这类脚本会发送大量探测报文,跟正常业务流量有明显差异,目标系统的安全设备是有可能产生告警的,这个要有预期。

针对具体服务,比如看HTTP响应头的--script=http-headers,枚举SMTP用户的--script=smtp-enum-users,查看SSH支持的认证方式用--script=ssh-auth-methods。这些都是从脚本库里挑指定脚本来跑,比全量跑脚本节省时间和带宽。

3.3 写第一个自定义脚本

NSE脚本其实没有想象中那么遥不可及。一个最小可用的脚本只需要几行Lua代码。下面这个脚本检测HTTP服务是否带调试信息相关的banner:

local shortport = require "shortport" local comm = require "comm" description = "检测HTTP服务是否开启调试信息" author = "yourname" license = "Same as Nmap--See https://nmap.org/book/man-legal.html" categories = {"safe", "discovery"} portrule = shortport.http action = function(host, port) local status, result = comm.get_banner(host, port) if status and result then if string.find(result, "debug") then return "Debug interface detected" end return ("Banner: %s"):format(result) end return "Failed to connect" end

脚本结构非常清晰:description写说明,categories声明脚本类别,portrule定义端口匹配规则,action是执行逻辑。把文件放到nmap的脚本目录后,就能用nmap --script=自定义脚本 -p 80,8080 目标的方式调用了。对于团队内部有定制化检测需求的场景,自己写脚本往往比套一堆外部工具更高效、更可控。

4. 典型实战场景的参数组合

4.1 场景一:内网资产快速盘点

内网资产盘点的核心诉求是“快”。先探活,找出哪些IP在线,然后只对这些在线主机做端口扫描。

第一步,探活:

nmap -sn 192.168.1.0/24 -oG alive.gnmap

第二步,从结果中提取在线主机清单,然后做快速端口扫描:

nmap -sS -T4 --top-ports 1000 --open -iL alive_hosts.txt -oN quick_scan.txt

这里我用了几个关键参数:-T4提升时序,--top-ports 1000控制扫描规模,--open只在结果中输出开放端口,配合-oN保存为可读格式。注意第一步不能跳过探活直接做全量扫描,因为对于在线主机比例不高的网段,先探活能省掉大量无效的端口探测流量。

4.2 场景二:重点主机深入服务梳理

盘点出重点目标后,需要深入看服务和系统信息。我的惯用命令:

nmap -p- -sV -sC -O --open 192.168.1.100

-p-是全端口扫描,1到65535全跑一遍。这一步极其耗时,一个端口多的主机可能扫十几分钟,所以我通常只对个位数的主机用。-sV -sC结合版本探测和默认脚本,-O做系统识别。一次跑完,目标开放的端口、每个端口背后的服务版本、系统指纹、常见漏洞信息基本就全了。

全端口扫描为什么慢?因为nmap要考虑丢包和重传机制,65535个端口逐个探测,加上版本探测和脚本执行的时间,确实需要耐心。所以这个组合不适合批量用,适合作为单个目标的深度检查手段。

4.3 场景三:速度、准确性与网络影响的平衡

nmap默认的扫描节奏比较保守,这是为了保证准确性。但在实际项目里,你可能需要在速度和准确性之间做取舍。

几个常用的控制参数:

  • -T0到-T5:时序模板,从最慢到最快。日常用-T4,延迟高的网络环境回落-T3。
  • --min-rate 1000:最低发包速率,用于迫使nmap提高扫描并发。对延迟低的目标网络效果好。
  • --max-retries 1:减少重试次数,能明显缩短超时等待,但可能增加漏报概率。
  • --host-timeout 10m:单主机的最大扫描时长,超时自动跳过,适合做大规模网段时防止被个别慢主机拖住。

这几个参数组合起来,可以把一个C段的扫描时间从几十分钟压缩到几分钟。但代价是可能漏掉一些响应慢的端口。我的经验是:大范围普查用激进参数,重点主机复查再用保守参数,两轮扫描结合。

4.4 场景四:防火墙策略验证

nmap在验证防火墙策略时也有不可替代的作用。比如你要确认防火墙是否真的放行了外部对特定端口的访问,可以用--source-port指定源端口来测试。经典手法是测试防火墙是否只放行了源端口53的报文——有些防火墙规则写得比较粗,只判断源端口而不判断目的端口,这时候来自53端口的探测就能穿透。这类测试只建议在你自己管理的目标环境里做,目的是验证规则有效性,而不是用来突破防线。

另外一个有用参数是--reason,它会在扫描结果里显示每个端口状态的判定依据,比如“syn-ack”“reset”“no-response”。在分析防火墙规则时,这个参数能帮你判断报文是被丢弃了还是目标主动拒绝的,比单纯看状态列表有用得多。

5. 扫描结果输出与数据整理

5.1 输出格式:-oN / -oX / -oG / -oA

nmap的输出不只是屏幕上那些文本。正式一点的扫描项目,结果的保存和整理是刚需。四种输出格式各有用处:

  • -oN:普通可读格式,人类友好,适合直接扔到归档文档里。
  • -oX:XML格式,结构化数据,方便程序解析和导入数据库或其他平台。
  • -oG:可grep格式,每行一条主机记录,方便用文本工具快速提取关键信息。
  • -oA basename:同时生成以上三种格式,以指定名称作为前缀。

实际项目里我几乎总是用-oA,一份扫描结果同时留下三种格式,后面想摸数据怎么都有得用。比如:

nmap -sS -sV -oA scan_result 192.168.1.0/24

执行后目录下会生成scan_result.nmap、scan_result.xml、scan_result.gnmap三个文件。等到要整理报告时,直接处理XML或者用grep处理gnmap,都很方便。

5.2 从结果中快速提取目标清单

整理扫描结果时,最常做的事就是把在线主机的IP和开放端口提取出来。-oG格式配合文本工具特别好使。

提取在线主机:

grep "Status: Up" alive.gnmap | awk '{print $2}' > hosts.txt

提取开放了指定端口的主机IP:

grep "22/open" quick_scan.gnmap | cut -d' ' -f2

如果需要更复杂的数据处理,直接用Python解析XML文件:

import xml.etree.ElementTree as ET tree = ET.parse("scan_result.xml") for host in tree.getroot().findall("host"): addr = host.find("address").attrib["addr"] ports = host.findall(".//port") for port in ports: portid = port.attrib["portid"] state = port.find("state").attrib["state"] svc = port.find("service") name = svc.attrib.get("name", "") if svc is not None else "" print(addr, portid, state, name)

这段代码会把所有主机、端口、状态、服务名输出成行列整齐的数据,后续可以导入Excel或数据库里做进一步关联分析。

6. 常见问题与排查技巧实录

6.1 扫描时间过长

这个问题出现的频率最高,而且原因通常很集中。首当其冲的是扫描范围太大,有人直接加-p-对着一个C段扫,那时间肯定是按小时算的。解决办法是分级:先用--top-ports 1000做普查,找到重点目标再用-p-深挖。其次是UDP扫描拖慢整体时间,如果业务不需要UDP探测,就别加-sU。还有一个隐藏因素是版本探测-sV,它对每个开放端口都要做多轮探针交互,大量开放端口时非常耗时。如果只是做端口状态判断,可以不急着加-sV,先拿到端口清单再对重点端口做版本识别。

6.2 主机明明在线,nmap却说down

这是探活策略的问题。目标主机可能禁用了ICMP响应,默认探活判断失败。此时加-Pn跳过主机发现阶段,直接进入端口扫描。要注意的是,-Pn会强制对所有目标IP都执行完整的端口扫描,如果目标网段里根本没有这么多在线主机,时间会成倍增加。另一个可能原因是虚拟机隔离或者云平台安全组不回ARP广播,这种情况要结合网络拓扑去看,不是加个参数就能彻底解决的。

6.3 端口状态是open,业务却连不上

这个坑非常经典。nmap判断端口open,是因为它收到了SYN/ACK响应,说明有程序在内核层面接受了连接。但业务连不上,常见原因有三个:

第一,服务监听的地址不对。比如MySQL只监听了127.0.0.1,nmap从外部扫到3306是open也是不可能的事——这种情况更多是服务监听了内网IP但安全组挡了外部来源。此时可以登录目标主机执行ss -lntp确认监听地址。

第二,防火墙只放行了特定来源IP。用--source-port配合--reason可以看到报文的响应情况,再结合防火墙规则排查。

第三,端口开放但协议不对。比如把HTTP服务跑在一个非标准端口,nmap默认看到端口开放,但你的客户端用了错误的协议去连接。这时候用nmap -sV让nmap探测一下该端口的真实服务,就能看出端倪。

6.4 权限、接口与平台相关的问题

nmap的部分功能依赖底层权限。在Linux下,-sS和-O需要root权限,普通用户运行时nmap会提示requires root privileges,要么换用-sT连接扫描,要么用sudo执行。在Windows下,如果没有正确安装底层驱动,扫描时会报接口相关的错误,检查驱动是否安装并重新启动服务就行。

网络接口方面,有时候会提示Failed to determine the interface,通常是机器上有多块网卡,或者接口名被改了。可以用nmap --iflist查看nmap识别到的接口列表,确认要走哪块网卡,必要时用-e 接口名指定。多网卡机器上指定错误的出口网卡,会导致扫描报文从错误的网段发出去,目标当然没响应。

综合来看,nmap这个工具本身不复杂,复杂的是怎么在不同场景下选对参数、读懂结果。我在实际项目里的一个体会是:扫描前先想清楚这次的目标到底是什么,探活、端口普查、深度服务识别、漏洞检查,每一类任务都有对应的参数组合,很少有人一次扫描就把所有事干完。把这些参数按场景组合记熟,再配合输出整理和问题排查的思路,基本就能应对日常工作中绝大部分网络排查和服务梳理任务了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进