超越Nmap:Zmap与Zgrab2构建企业级外网资产地图实战
1. 项目概述:为什么我们需要超越Nmap?
在安全评估和资产梳理的日常工作中,端口扫描是第一步,也是最基础的一步。长久以来,Nmap几乎是这个领域的代名词,它功能全面、脚本强大,是渗透测试人员和安全工程师的“瑞士军刀”。然而,当场景切换到对企业庞大的外网IP段进行快速、全面的资产发现时,Nmap的局限性就暴露出来了。它的设计哲学是“深度”而非“广度”,在扫描成千上万个IP时,其单线程、顺序探测的模式会变得异常缓慢,一次完整的全端口扫描可能需要数天甚至数周,这在追求效率的现代安全运营中几乎是不可接受的。
这就是Zmap和Zgrab组合的价值所在。这个项目的核心目标,不是要完全取代Nmap,而是在大规模资产发现这个特定场景下,实现“降维打击”。Zmap是一个专为互联网级扫描设计的工具,它能在极短时间内(例如45分钟)扫描整个IPv4地址空间的所有80端口。其核心原理是使用无状态的异步扫描技术,通过精心构造的SYN包和高效的发包算法,将扫描速度提升数个数量级。而Zgrab则是一个应用层协议扫描器,它接收Zmap发现的开放端口列表,然后去建立完整的TCP连接,并模拟客户端发送特定协议(如HTTP、HTTPS、SSH、FTP等)的握手请求,以获取更丰富的横幅(Banner)信息和应用层数据。
将两者结合,我们就能构建一个高效的工作流:Zmap作为“侦察兵”,以闪电速度摸清哪些IP的哪些端口是开放的;Zgrab作为“特工”,对开放的端口进行深度交互,采集详细的指纹信息。最终,我们将这些数据整合,绘制出一张清晰、动态的“企业外网资产地图”。这张地图不仅能告诉我们有哪些资产暴露在外,还能揭示它们运行的服务、版本,甚至是TLS证书的详细信息,为后续的风险评估、漏洞扫描和攻击面管理提供精准的数据支撑。无论是安全团队进行周期性暴露面自查,还是红队进行外围信息收集,这套组合拳都能带来效率的质的飞跃。
2. 核心工具选型与架构设计
2.1 Zmap:互联网级扫描的引擎
Zmap的设计哲学与Nmap截然不同。Nmap是一个功能丰富的工具箱,而Zmap更像是一台专为高速扫描定制的发动机。它的核心优势在于其无状态扫描架构。
无状态扫描原理:传统的扫描器(如Nmap默认的TCP connect扫描)需要为每个探测目标维护一个TCP连接状态。当扫描大量目标时,这需要消耗大量的内存和文件描述符。Zmap则采用了“无状态”方式。它预先构造好要发送的SYN探测包,然后以极高的速率发送出去。发送后,它并不在本地维护每个连接的状态,而是通过计算每个目标IP和端口对应的初始序列号(ISN),来识别返回的SYN-ACK包是否属于自己发出的探测。这种方法使得Zmap的内存占用几乎恒定,与扫描目标数量无关,从而实现了极高的并发性能。
关键参数与选型考量:
-p:指定端口。对于企业外网资产,我们通常关注Web服务(80, 443, 8080, 8443)、常见管理服务(22/SSH, 23/Telnet, 3389/RDP)以及数据库端口(3306, 5432, 6379等)。建议根据企业业务特点定制端口列表。-o:输出文件。Zmap支持多种格式,如csv,json,grepable。为了便于后续用Zgrab处理,json格式是最佳选择。-b:黑名单文件。这是企业内扫描必须注意的!你必须将公司内部的核心生产网段、合作伙伴IP、云服务商的控制平面IP等加入黑名单,避免扫描到不该扫描的目标,引发误报或警报。--rate:发包速率。默认是1000包/秒,但根据你的网络带宽和机器性能,可以调得更高(如10000或更多)。但要注意,过高的速率可能被上游网络设备限速或触发安全防护机制。-n/-N:扫描数量。用于测试时限制扫描的IP数量,非常实用。
注意:大规模扫描必须获得授权!在扫描任何不属于你自己的网络空间前,务必取得书面授权。即使是扫描自己公司的外网IP,也最好提前通知网络和安全团队,避免触发IDS/IPS告警,造成不必要的恐慌。
2.2 Zgrab 2.0:应用层指纹采集专家
Zmap告诉我们“门”是开的,而Zgrab的任务是去“敲门”并看看“屋里有什么”。Zgrab 2.0是一个模块化的应用层扫描器,它支持十几种协议。
工作模式:Zgrab通常以“跟随”模式运行。它从标准输入或文件中读取Zmap的输出(即ip:port列表),然后对每个目标发起完整的TCP连接。连接建立后,它会根据配置的协议模块,发送相应的握手数据包。例如,对于443端口,它会启动TLS握手;对于80端口,它会发送一个HTTP GET请求。
核心模块与价值:
- http:获取HTTP响应头、Server字段、HTML标题、跳转链等。这对于识别Web框架(如Nginx, Apache, IIS)、中间件版本和Web应用类型至关重要。
- tls:这是本项目的重点之一。TLS模块不仅能获取证书链(包含颁发者、有效期、域名信息),还能通过协商支持的加密套件、TLS版本、扩展(如ALPN, SNI)来生成更细粒度的TLS指纹。这些指纹可用于资产关联和威胁情报。
- ssh, ftp, smtp, pop3, imap等:获取对应服务的横幅信息,直接暴露服务软件和版本号。
输出格式:Zgrab默认输出结构化的JSON,每个扫描结果都包含连接状态、协议握手详情、错误信息(如果有)以及最重要的——data字段,里面存放了协议交互的完整数据。这种格式非常适合用jq等工具进行后续的自动化分析和处理。
2.3 整体架构与数据流设计
一个高效、可重复执行的扫描流程需要清晰的数据流设计。以下是推荐的架构:
[目标IP段列表] + [端口列表] | v [Zmap扫描] | (输出开放端口列表: ip:port) v [Zgrab深度扫描] | (输出结构化JSON指纹数据) v [JQ/Python脚本进行数据清洗与聚合] | v [生成资产地图报告] (CSV/HTML/数据库)这个流程的关键在于将“发现”和“识别”解耦。Zmap只负责快速发现,产出是一个简单的列表。Zgrab负责深度识别,产出是丰富的指纹数据。最后的数据处理环节,则是将两者关联,并提取出对人类和机器都友好的信息,比如“IP 1.2.3.4 的 443 端口运行着 Nginx 1.18.0,证书由 Let‘s Encrypt签发,有效期至2024年底,TLS指纹ID为 xxxx”。
3. 环境准备与实战部署
3.1 系统与权限准备
首先,你需要一台Linux服务器作为扫描引擎。推荐使用Ubuntu 20.04 LTS或CentOS 7/8,因为它们有较好的软件包支持。机器需要具备公网IP和足够的网络带宽(百兆以上为宜),CPU和内存要求不高,但网络I/O是瓶颈。
权限提升:Zmap和Zgrab都需要发送原始网络数据包,因此必须以root权限运行,或者为相关的二进制文件设置CAP_NET_RAW能力。最简单的方式就是使用sudo。
# 安装必要的编译工具和库 sudo apt-get update sudo apt-get install -y build-essential cmake libgmp3-dev gengetopt libpcap-dev flex byacc libjson-c-dev pkg-config3.2 源码编译安装Zmap与Zgrab
虽然有些系统可以通过包管理器安装,但为了获得最新特性和更好的控制,建议从源码编译。
安装Zmap:
git clone https://github.com/zmap/zmap.git cd zmap mkdir build && cd build cmake .. make -j4 # 根据你的CPU核心数调整,加快编译速度 sudo make install安装完成后,运行zmap --version验证。
安装Zgrab 2.0:Zgrab 2.0是用Go语言编写的,因此需要先安装Go环境(版本1.16+)。
# 安装Go (以1.20为例) wget https://go.dev/dl/go1.20.linux-amd64.tar.gz sudo tar -C /usr/local -xzf go1.20.linux-amd64.tar.gz echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc source ~/.bashrc # 安装Zgrab go install github.com/zmap/zgrab2/cmd/zgrab2@latest # 编译好的二进制会在 $GOPATH/bin 下,通常为 ~/go/bin/zgrab2 # 将其移动到系统路径或添加到PATH sudo cp ~/go/bin/zgrab2 /usr/local/bin/运行zgrab2 --help验证安装。
3.3 配置扫描目标与黑名单
在开始扫描前,精心准备输入文件是成功的一半。
1. 定义目标IP段:创建一个文件targets.txt,每行一个CIDR格式的网段。
203.0.113.0/24 198.51.100.0/24 192.0.2.0/24你可以从公司的IP地址管理(IPAM)系统导出,或从云服务商的控制台获取企业拥有的公网IP段。
2. 创建黑名单文件blacklist.txt:这个文件至关重要,用于排除不应扫描的地址。至少应包括:
224.0.0.0/3(组播地址段)0.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16(私有地址段)100.64.0.0/10(运营商NAT地址)- 公司内部的办公网出口IP、VPN网关IP、云平台的元数据服务IP(如169.254.169.254)等。
- 任何已知的敏感系统或合作伙伴的IP。
3. 定义端口列表:创建一个文件ports.txt,包含你关心的端口。一个基础的Web和服务扫描列表可能如下:
80 443 8080 8443 8888 22 21 23 25 110 143 3306 3389 5432 6379 27017你可以根据业务情况调整,端口越多,扫描总时间越长。
4. 执行大规模扫描与指纹采集
4.1 第一阶段:使用Zmap进行高速端口发现
现在,让我们启动第一次扫描。假设我们只扫描80和443端口作为演示。
sudo zmap \ -p 80,443 \ # 指定端口 -w targets.txt \ # 目标文件 -b blacklist.txt \ # 黑名单文件 -n 10000 \ # 本次只扫描前1万个IP(测试用) -o zmap_results.csv \ # 输出CSV格式 -r 5000 \ # 将发包速率限制在5000包/秒 --verbosity=1 # 输出一些进度信息这个命令会运行得很快。-n参数用于在正式扫描前进行小规模测试,确保你的命令、黑名单和网络环境都正常工作,不会误扫。输出文件zmap_results.csv的格式很简单:saddr,daddr,sport,dport,seq,ack,classification,reason。我们最关心的是saddr(源IP,即目标IP)和dport(目标端口)。
实操心得:在正式全量扫描前,务必先用-n参数进行小规模测试。用-N参数也可以,它表示扫描指定数量的随机目标。测试时,可以搭配tcpdump监听出口流量,观察扫描包是否正常发出,以及是否有来自非目标IP的意外回复(这可能意味着黑名单没配好)。
4.2 第二阶段:使用Zgrab进行深度指纹采集
得到开放端口列表后,我们需要将其转换为Zgrab可用的格式。Zgrab需要的是ip:port或domain:port的列表。
# 从Zmap的CSV结果中提取 ip:port awk -F',' '{print $1":"$4}' zmap_results.csv | grep -v 'saddr' > open_ports.txt # 去除标题行,并过滤掉可能存在的空行现在,使用Zgrab对这批开放的端口进行HTTP和TLS指纹采集。
# 扫描HTTP (80端口)服务,获取横幅和响应头 zgrab2 http \ -p 80 \ -i open_ports.txt \ -o http_results.json \ --timeout 5 \ # 每个目标超时时间 --retries 1 # 失败重试次数 # 扫描HTTPS (443端口)服务,重点获取TLS证书和指纹 zgrab2 tls \ -p 443 \ -i open_ports.txt \ -o tls_results.json \ --timeout 5 \ --senders 100 \ # 并发连接数,可根据机器性能调整 --heartbeat-enabled # 输出进度信息这里我们分两次运行,一次针对HTTP,一次针对TLS。你也可以尝试使用Zgrab的多模块扫描,但分开运行更易于管理和调试。--senders参数控制并发数,对于外网扫描,100-500是一个合理的范围,太高可能导致本地端口耗尽或被目标视为攻击。
4.3 TLS指纹采集的进阶技巧
默认的zgrab2 tls扫描已经能获取证书基本信息。但如果我们想生成更独特、可用于资产关联的TLS指纹,就需要采集更多的握手细节。
1. 使用自定义扫描配置:Zgrab允许通过JSON配置文件进行更精细的控制。创建一个文件tls_config.json:
{ "protocol": "tls", "port": 443, "timeout": 5, "tls": { "heartbeat_enabled": true, "extended_master_secret": true, "session_ticket": true, "supported_versions": true, "supported_groups": true, "signature_algorithms": true, "sni": "example.com" // 可以指定一个SNI,观察服务器对不同域名的响应 } }然后运行:zgrab2 multiple -c tls_config.json -i open_ports.txt -o tls_detailed.json
2. 生成JA3/S指纹:JA3是一种流行的TLS客户端指纹方法。虽然Zgrab本身不直接输出JA3,但我们可以利用它收集的原始握手信息(如TLS版本、支持的加密套件列表、扩展列表等)来后期计算。这些信息都包含在tls_results.json的data.tls字段中。你需要编写一个脚本,按照JA3的算法(MD5( TLS版本, 加密套件, 扩展列表, 椭圆曲线, 曲线格式 ))来生成哈希值。这个哈希值就是TLS指纹,同一批服务器或中间件设备往往会共享相同的指纹。
3. 证书信息挖掘:证书本身是信息宝库。除了肉眼查看颁发者和有效期,还可以提取:
- 主题备用名称(SAN):证书可能包含大量内网域名或泛域名,这有助于发现更多的关联资产。
- 证书序列号/指纹:同一张证书可能被部署在多台服务器上,这是进行资产分组的有力依据。
- 证书透明度(CT)日志:利用证书的SHA256指纹,可以在公开的CT日志中搜索,可能发现历史使用过该证书的其他未知域名。
注意事项:深度TLS扫描会发送更多的扩展字段,可能被一些先进的WAF或入侵检测系统识别为恶意扫描行为。在生产环境中进行大规模扫描时,建议与网络团队协调,并考虑降低扫描频率和并发度。
5. 数据处理与资产地图构建
扫描完成后,你会得到几个庞大的JSON文件。原始数据是杂乱的,我们需要将其转化为清晰的资产地图。
5.1 使用JQ进行数据提取与关联
jq是处理JSON的神器。我们可以用它来提取关键信息。
1. 提取基本的HTTP资产信息:
# 从http_results.json中提取IP,端口,Server头,标题 jq -r '.[] | select(.data.http.result.response.status == 200) | [.ip, .port, (.data.http.result.response.headers?["Server"]? // "N/A"), (.data.http.result.response.body? | ascii_downcase | match("<title>(.*)</title>")? // "N/A" | .captures[0].string)] | @csv' http_results.json > http_assets.csv这个命令会过滤出HTTP状态码为200的响应,并提取IP、端口、Server头和HTML标题,输出为CSV格式。
2. 提取详细的TLS/证书信息:
# 从tls_results.json中提取IP,端口,证书颁发者,有效期,SAN域名 jq -r '.[] | select(.data.tls.result.handshake_log.server_certificates.certificate.parsed != null) | [.ip, .port, .data.tls.result.handshake_log.server_certificates.certificate.parsed.issuer.common_name, .data.tls.result.handshake_log.server_certificates.certificate.parsed.validity.end, (.data.tls.result.handshake_log.server_certificates.certificate.parsed.extensions?.subject_alt_name?.dns_names? // [] | join(";"))] | @csv' tls_results.json > tls_assets.csv这个命令提取了TLS握手成功的记录,并获取了证书颁发者、过期时间以及所有的SAN域名。
5.2 数据聚合与地图生成
现在我们有http_assets.csv和tls_assets.csv两个文件。我们可以用Python(Pandas库)或简单的Shell脚本将它们与最初的open_ports.txt合并,生成一个总览表。
一个简单的Python脚本示例:
import pandas as pd import socket # 读取基础开放端口列表 ports_df = pd.read_csv('open_ports.txt', header=None, names=['target']) ports_df[['ip', 'port']] = ports_df['target'].str.split(':', expand=True) # 读取HTTP和TLS数据 http_df = pd.read_csv('http_assets.csv', header=None, names=['ip', 'port', 'server_header', 'page_title']) tls_df = pd.read_csv('tls_assets.csv', header=None, names=['ip', 'port', 'cert_issuer', 'cert_expiry', 'san_domains']) # 合并数据 merged_df = ports_df.merge(http_df, on=['ip', 'port'], how='left')\ .merge(tls_df, on=['ip', 'port'], how='left') # 尝试解析主机名(反向DNS),注意这可能会很慢 def reverse_dns_lookup(ip): try: return socket.gethostbyaddr(ip)[0] except: return 'N/A' # 谨慎启用,大规模IP的反查非常耗时 # merged_df['hostname'] = merged_df['ip'].apply(reverse_dns_lookup) # 保存最终资产地图 merged_df.to_csv('external_asset_map.csv', index=False) print(f"资产地图已生成,共发现 {len(merged_df)} 个开放端口。")生成的external_asset_map.csv就是你的核心资产地图。你可以用Excel打开它,进行排序、筛选和分析。更高级的做法是将其导入到Elasticsearch、Splunk或本地数据库中,以便进行可视化仪表盘和持续监控。
5.3 可视化与持续监控
静态的地图会过时。理想的情况是建立一个自动化的扫描流水线。
1. 自动化流水线设计:使用Cron定时任务(例如每周日凌晨2点)触发一个Shell脚本,该脚本按顺序执行:
- Zmap扫描预设的端口。
- Zgrab对结果进行深度指纹采集。
- 使用JQ和Python脚本处理数据,生成新的资产报告。
- 将新报告与上周的报告进行对比,用
diff或专门的库生成“资产变更报告”(新增、关闭、服务变更)。 - 将变更报告通过邮件或即时通讯工具(如钉钉、企业微信机器人)发送给安全团队。
2. 简易可视化:对于中小型团队,用Python的matplotlib或seaborn库可以快速生成一些图表,比如:
- 开放端口分布图:哪个端口最常开放?
- Web服务器类型分布:Nginx vs Apache vs IIS的比例?
- 证书过期时间分布:有多少资产将在未来30/60/90天内证书过期?
- 资产地理位置分布(如果结合GeoIP数据库)。
这些图表能帮助你快速把握整体暴露面的情况和风险趋势。
6. 常见问题、性能调优与避坑指南
在实际操作中,你一定会遇到各种问题。以下是我踩过的一些坑和总结的经验。
6.1 扫描性能上不去?
- 症状:Zmap扫描速度远低于预期,
--rate参数调高也没用。 - 排查:
- 检查系统限制:使用
ulimit -n查看文件描述符限制。Zmap需要大量socket。使用ulimit -n 1000000临时提高,或在/etc/security/limits.conf中永久设置。 - 检查网络队列:使用
ethtool -g eth0查看网卡环形缓冲区大小。如果Current值很小,可以用ethtool -G eth0 rx 4096 tx 4096调大(需根据网卡驱动支持情况)。 - 检查CPU软中断:使用
top查看%si(软中断)是否过高。如果单核si接近100%,说明网络包处理瓶颈在CPU。可以考虑使用RPS(Receive Packet Steering)将软中断负载均衡到多个CPU核心。 - 目标端限制:扫描某些云服务商或大型企业的IP段时,对方可能有速率限制或主动丢包。尝试降低
--rate,或分批扫描。
- 检查系统限制:使用
6.2 Zgrab连接超时或失败率高?
- 症状:Zgrab输出中
"success": false的记录很多,错误是timeout或connection-refused(在Zmap显示开放的情况下)。 - 原因与解决:
- 网络延迟与超时:跨运营商或国际链路延迟可能很高。将
--timeout从默认的2秒提高到5-10秒。 - 防火墙中间拦截:有些安全设备会放行SYN包(Zmap能检测到开放),但拦截后续完整的TCP握手或应用层请求(导致Zgrab失败)。这是正常现象,本身也是一个安全发现(可能存在中间件防火墙)。
- 本地端口耗尽:高并发下,Zgrab可能耗光本地临时端口。减少
--senders并发数,或调整系统net.ipv4.ip_local_port_range参数,增加可用端口范围(如sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535")。 - 目标服务不稳定:互联网上的服务本身可能时好时坏。对于重要资产,可以配置重试
--retries 2。
- 网络延迟与超时:跨运营商或国际链路延迟可能很高。将
6.3 如何避免被“封IP”或触发警报?
大规模扫描必然会产生流量,可能触发目标或云服务商的防护规则。
- 速率限制:这是最重要的手段。不要用极限速率(如数百万包/秒)去扫描一个小的目标网段。根据目标网络规模合理设置
--rate。对于/24(256个IP)的小网段,每秒几百包足矣。 - 分散扫描源:如果条件允许,使用多个云服务器从不同地域扫描,分散流量和“嫌疑”。
- 遵守Robots协议与法律:仅扫描你拥有或获得明确授权的资产。在HTTP扫描中,可以考虑在User-Agent中标识自己(例如
Company-Security-Scanner/1.0),有些网站会根据此来识别善意爬虫。 - 错峰扫描:避免在业务高峰时段扫描。
6.4 数据量太大,JSON文件难以处理?
单次扫描几十万IP,Zgrab产生的JSON文件可能达到GB级别。直接用文本编辑器或简单的jq命令处理会非常慢甚至内存不足。
- 流式处理:使用
jq的--stream选项来处理巨大的JSON文件,它是流解析器,内存占用小。 - 分而治之:在扫描时就用
split命令将open_ports.txt分成多个小文件,然后并行运行多个Zgrab进程,每个处理一部分,最后再合并结果。 - 使用专业工具:对于持续性的项目,建议直接将Zgrab的输出导入到像Elasticsearch这样的搜索引擎中,利用其强大的索引和聚合能力进行分析。
6.5 TLS指纹在实际中如何应用?
采集了TLS指纹(比如自计算的JA3哈希)后,它可以用于:
- 资产关联:发现使用相同TLS配置(相同中间件、负载均衡器或安全设备)的未知资产。例如,你发现一个未知IP的TLS指纹与公司官网一致,那它很可能是官网的某个后端服务器或CDN节点。
- 威胁狩猎:某些恶意软件或C2服务器会使用特定的TLS库和配置,产生独特的指纹。可以将扫描到的指纹与公开的威胁情报库(如JA3指纹库)进行比对。
- 变更监控:定期扫描并对比TLS指纹。如果一台服务器的TLS指纹突然变了,可能意味着软件升级、配置变更或被入侵后替换了证书。
最后,记住这套工具链的目的是提高效率,而不是提供一个全自动、零维护的解决方案。你需要根据自己企业的网络环境、业务特点和安全需求,不断调整端口列表、扫描策略、黑名单和处理脚本。一开始可能会遇到各种问题,但一旦流程跑通,它将成为你手中一张持续更新的、宝贵的企业外网资产“活地图”。
