简介一份基于Python3开发的多功能网络安全扫描工具源码面向安全测试人员、甲方自测团队及网络安全学习者适用于敏感文件探测、WAF/CDN识别、端口扫描、服务识别、操作系统识别、弱口令检测、漏洞扫描、绕过CDN及旁站查询等授权安全评估场景。资源共41个文件压缩包6.98MB以31个Python脚本为核心辅以JSON配置、文本说明、依赖清单、Git忽略规则及许可证并包含恶意代码数据库目录结构清晰。已有333人学习下载。通过源码可掌握扫描工具的模块化设计思路学习常见未授权访问、SQL注入、Struts2远程代码执行等漏洞检测脚本写法以及WAF识别、端口服务指纹识别等实用技术适合用于授权安全评估与日常学习。1. 为什么综合两个字才是这个扫描工具的灵魂很多人理解综合网络安全扫描工具的第一反应是用 Python 把一堆现成脚本串起来哪个端口通就记哪一笔。真拿去回答这个端口上跑的是什么服务、有没有风险、你怎么证明扫描结果靠谱时立刻就露馅了。综合二字的核心是把端口探测、服务识别、指纹比对、风险研判串成一条可重复执行的流水线这才是设计源码时真正要解决的事。下面按源码的拆解顺序把一套能跑的扫描器从头讲到尾从 TCP 端口探测到 Banner 抓取再到指纹匹配和报告输出每段代码都讲清参数含义和踩过的坑。适合安全工具开发工程师、拿它当毕设题目的同学以及想做内网资产盘点和自查的运维。前提只有一个所有扫描都在授权范围内进行。2. 拆掉黑匣子一个扫描器必须立住的三个支柱一开始很容易把扫描器理解成一个 socket 循环for port in range(1, 65535)连一下通了就记录。真这么写了会发现三个问题速度慢得没法用、拿到一堆端口不知道是什么服务、报告写出来没有说服力。所以要把扫描器拆成三个独立模块端口探测、服务识别、指纹匹配。每个模块可以单独测试最后再串成流水线。可能你会问直接调 nmap 不就行了用 python-nmap 封装 nmap 确实是最快路径但标题里强调的是设计源码意味着你要把原理落地成自己的代码。自己实现的好处是可控、可改、无外部依赖坏处是性能和识别率短期追不上 nmap。我的建议是学习和毕设自己写生产环境用 nmap 结果交叉验证。这个定位想清楚代码写到什么程度就心里有数了。2.1 端口探测用 TCP connect 还是半开扫描端口探测的底层原理是向目标端口发起 TCP 三次握手如果对方回了 SYN-ACK说明端口监听中如果回了 RST说明端口关闭或被过滤。Python 里最直接的方式是用 socket.connect_ex()它不会像 connect() 那样抛异常而是把错误码返回来0 就代表连接成功。import socket def tcp_connect(host: str, port: int, timeout: float 1.0) - bool: 探测指定端口是否开放。返回 True 表示对方接受了 TCP 连接。 sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) try: # connect_ex 成功返回 0失败返回 errno不会抛异常 return sock.connect_ex((host, port)) 0 finally: sock.close()这段代码的逻辑很直接每个端口建一个 socket设置超时然后去连目标。timeout 是灵魂参数默认 1.0 秒的意思是如果 1 秒内没握上手就认为这个端口不可达。对局域网目标设 0.5 就够跨网段或对端有丢包时设 2 到 3 会更稳但整体扫描时间会线性变长。粗算一下65535 个端口、每个端口串行 1 秒要跑 18 个小时所以并发是必须的这个放到下一章。为什么不推荐用 SYN 半开扫描半开扫描只发 SYN 不收 ACK速度快且不容易在应用层留下连接记录但它要构造原始报文在 Windows 上需要额外驱动在 Linux 上要么用 scapy 要么自己写 raw socket还经常被中间设备直接丢掉。对自研源码项目来说TCP connect 是跨平台最稳、最容易解释的方法足够覆盖课程设计和绝大多数内网巡检场景。2.2 服务识别Banner 抓取不是发一个请求就完事端口通了只说明这个端口有东西在听至于是 HTTP、SSH 还是数据库要靠 Banner。所谓 Banner就是服务端在连接建立后主动吐出来的一段文本或者响应你一个探测请求后返回的头部。不同协议脾气不一样FTP、SSH 通常一上来就自报家门HTTP 需要你发一个 HEAD 请求才肯开口。def grab_banner(host: str, port: int, timeout: float 3.0) - str: 建立 TCP 连接先尝试读取服务端主动发送的 Banner 如果端口像 HTTP再补发一个 HEAD 请求。返回清理后的文本。 try: with socket.create_connection((host, port), timeouttimeout) as conn: conn.settimeout(timeout) data b # 先读一次FTP、SSH 等服务会主动推送欢迎语 try: data conn.recv(256) except socket.timeout: pass # 对明文 HTTP 端口补一个 HEAD 请求拿 Server 头 if port in (80, 8080, 8000): conn.sendall(bHEAD / HTTP/1.0\r\n\r\n) try: data conn.recv(1024) except socket.timeout: pass return data.decode(utf-8, errorsignore).strip() except OSError: return 这里有个很反直觉的细节抓 Banner 和扫端口要分开做。端口扫描只关心通不通为了快会设很短的超时Banner 抓取需要等服务端慢慢把欢迎语吐完超时太短会抓到半个包甚至空字符串。我一般把抓取的超时设成 2 到 3 秒并且先读后写——如果先急着发探测词反而可能把协议握手打断拿到一堆二进制乱码。recv(256) 是试探性读取HTTP 的响应头通常远大于 256所以才有了后面第二次 recv(1024)。关于 443 这类 TLS 端口直接发明文 HTTP 请求会被服务端断开当前版本先做被动读取后续用 ssl 模块包装 socket 后再处理 HTTPS Banner。写代码时不要贪多先把明文协议识别准确再考虑加密协议。2.3 指纹匹配把 Banner 变成风险结论拿到 Banner 字符串之后最笨也最可靠的做法是拿正则去匹配已知产品特征比如 Server: nginx/1.18.0 匹配 NginxSSH-2.0-OpenSSH_8.2p1 匹配 OpenSSH。指纹库本质上是一张表产品名、正则、风险等级、处理建议。匹配到版本后再和你维护的已知有问题版本范围表做比对给出一条风险提示。import re FINGERPRINTS [ { name: nginx, pattern: re.compile(rServer:\s*nginx/([\w.]), re.IGNORECASE), risk: 低, advice: 关注官方通告及时升级到不受影响版本, }, { name: OpenSSH, pattern: re.compile(rSSH-2\.0-OpenSSH_([\w.])), risk: 中, advice: SSH 应禁止密码登录建议密钥登录并限制来源 IP, }, { name: Apache, pattern: re.compile(rServer:\s*Apache/([\w.]), re.IGNORECASE), risk: 中, advice: 确认已隐藏版本号避免暴露精确版本, }, ] def match_fingerprint(banner: str) - dict: for fp in FINGERPRINTS: m fp[pattern].search(banner) if m: return { product: fp[name], version: m.group(1), risk: fp[risk], advice: fp[advice], } return {product: unknown, version: , risk: 未知, advice: 建议人工确认}这段代码的匹配逻辑是从上到下逐个试第一个命中的指纹胜出。所以指纹表的顺序很重要Nginx 和 Apache 的 Server 头不可能同时出现但如果两个正则都写得特别宽就可能误判。我吃过一次亏把 Apache 的正则写成Server:\s*Apache忘了版本号结果很多带 Apache-Coyote 字样的中间件也被归到了 Apache。版本号用([\w.])捕获别用(.*)后者的贪婪匹配会把整个响应头吞进去。指纹库做到这个程度够不够对毕设和内部巡检够了但要明白它的边界——它只能识别正则库里有特征的产品识别不了经过高度定制的服务。真正商业扫描器会结合端口、协议行为、页面内容做加权判断那是另一个量级的工程。你在设计源码时先保证常见服务认得出、认不出的不硬猜就已经赢过很多拿到端口就交差的脚本了。这三个模块的依赖关系是单向的端口探测不依赖后面Banner 抓取依赖端口开放指纹匹配依赖 Banner。所以调试时也从前往后调先用 tcp_connect 确认端口再用 grab_banner 看能不能读出内容最后才调 match_fingerprint 的正则。别反过来把指纹匹配调了半天结果发现是 Banner 抓取返回了空字符串。3. 串成综合流程并发调度、数据结构与报告输出第 2 章三个函数各管一段但真正能用的综合扫描工具要有调度逻辑先把端口扫描并发跑完收集开放端口再并发抓 Banner最后统一做指纹匹配和风险标注。这个过程要处理好三个问题并发不能把目标和自身都打垮、任务状态要能被记录和展示、结果要能导出成别人看得懂的报告。3.1 先用数据类把扫描结果固定下来写脚本最忌讳的是结果到处塞字典字段名拼错一个下游全崩。我习惯先用 dataclass 定义扫描结果的骨架这样写报告、做断言、调接口都方便。在源码里这个数据类放在 scanner/scheduler.py 中是整个流水线的通用语言。from dataclasses import dataclass, asdict dataclass class ScanResult: host: str # 目标主机 port: int # 开放端口 state: str # open / filtered / closed banner: str # 抓取到的原始服务 Banner product: str # 指纹匹配出的产品名 version: str # 产品版本 risk: str # 低 / 中 / 高 / 未知 advice: str # 处理建议定义这个数据结构有几个好处一是 asdict() 可以直接把对象转成字典交给 json.dumps 序列化二是后面写 Markdown 报告时只用按字段取值不会出现这个字段叫 product 还是叫 name的记忆模糊。state 字段目前只用了 open但我建议保留 closed 和 filtered 的语义后续要支持更细的端口状态判断时不用改结构。3.2 并发调度ThreadPoolExecutor 配 as_completedPython 做网络并发主要有三种姿势多线程、asyncio、多进程。扫描任务的特点是 IO 密集、单次任务简单、并发量几百上千用 ThreadPoolExecutor 最直白——每个端口一个 future完成后立刻收结果。from concurrent.futures import ThreadPoolExecutor, as_completed def scan_ports(host: str, ports: list[int], workers: int 100, timeout: float 1.0) - list[int]: 并发扫描一批端口返回按端口号升序排列的开放列表。 open_ports [] with ThreadPoolExecutor(max_workersworkers) as executor: # 把每个端口提交成一个独立任务 future_map {executor.submit(tcp_connect, host, p, timeout): p for p in ports} for future in as_completed(future_map): if future.result(): open_ports.append(future_map[future]) open_ports.sort() return open_ports关键点在 future_map它把 future 对象和端口号绑定future 完成后通过 as_completed 依次拿结果谁先完成先处理谁不用等最慢的那个。max_workers 我建议从 100 起步局域网内效果很好如果是跨网段扫多台机器可以压到 50避免中间设备认为你在做泛洪而直接断流。timeout 和 workers 是跷跷板timeout 越小单个端口越快放弃但误报会多workers 越大单位时间发出的请求越多对目标压力也越大。千万记住这是对你有授权的目标做巡检不是压力测试。3.3 Banner 抓取也要并发而且要和扫描分开有些人会把抓 Banner 和端口扫描混在一个任务里端口一开放就在同一个连接里读数据。听起来省了一次握手实际会让端口扫描慢三倍——因为抓 Banner 的超时是 3 秒而端口探测的超时是 1 秒。更好的做法是先快速扫完所有端口再对开放端口单独开一批线程去抓 Banner。def grab_banners(host: str, ports: list[int], workers: int 50, banner_timeout: float 3.0) - dict[int, str]: 对一批开放端口并发抓取 Banner返回 {port: banner} 字典。 banners {} with ThreadPoolExecutor(max_workersworkers) as executor: future_map {executor.submit(grab_banner, host, p, banner_timeout): p for p in ports} for future in as_completed(future_map): port future_map[future] banners[port] future.result() return banners这里 workers 反而比扫描阶段小原因是 Banner 抓取需要等服务端响应同时打开的连接太多容易把目标服务进程的连接数打满。我就是吃过这个亏扫一台并发能力很弱的服务时100 个线程同时去抓 Banner结果目标连接池被打爆后面的探测全部超时报告里一片 unknown。后来把抓 Banner 的并发降到 50问题就消失了。Banner 抓取失败时不要立刻报错先把空字符串存下来指纹匹配阶段会把它标记成 unknown等日志里再排查。3.4 流水线主控把探测、识别、匹配按顺序串起来组装整条流水线的函数要承担三件事记录扫描开始时间、依次调用扫描和 Banner 抓取、返回结构化结果。报告写入交给独立的 report 模块这样主控逻辑不会被文件 IO 弄脏。from datetime import datetime def run_scan(host: str, ports: list[int], workers: int 100, timeout: float 1.0, banner_timeout: float 3.0): 执行一次完整扫描返回结果列表和开始时间。 start_time datetime.now().isoformat(timespecseconds) open_ports scan_ports(host, ports, workers, timeout) banners grab_banners(host, open_ports, max(10, workers // 2), banner_timeout) results [] for port in open_ports: matched match_fingerprint(banners.get(port, )) results.append(ScanResult( hosthost, portport, stateopen, bannerbanners.get(port, ), productmatched[product], versionmatched[version], riskmatched[risk], advicematched[advice], )) return results, start_time这段代码把全流程串起来了先并发扫端口再并发抓 Banner最后逐条匹配指纹。注意 grab_banners 的 workers 我用max(10, workers // 2)做了折算避免扫描阶段 200 个线程、抓 Banner 阶段还是 200 个线程把目标压出问题。start_time 用 ISO 格式并且精确到秒它会成为这次扫描的唯一标识写报告和排查问题都靠它对齐时间线。4. 设计源码落地从零跑通一个最小可用的扫描器前面几章拆了模块这一章把它们组装成一个能直接运行的工程。一个综合扫描工具的源码包通常会有清晰的目录结构入口文件承担参数解析和合规校验scanner 包放核心逻辑报告输出独立成模块。你拿到手或者自己写的时候都先按这个布局组织。4.1 项目目录结构源码不是一堆脚本平铺network_scan/ ├── main.py # 命令行入口 ├── scanner/ │ ├── __init__.py │ ├── probe.py # 端口探测和 Banner 抓取 │ ├── fingerprint.py # 指纹库和匹配逻辑 │ ├── scheduler.py # 数据类、并发调度、流水线主控 │ └── report.py # JSON / Markdown 报告输出 └── requirements.txt # 依赖说明这个结构是给设计源码用的核心逻辑放在 scanner 包main.py 只负责解析参数和调调度函数。好处有三点一是以后加新功能只需要往包里面加模块不用动入口二是测试时可以 import scanner.probe 单独验证函数三是报告给别人看的时候包结构本身就是文档。requirements.txt 我留空了唯一要求是 Python 3.10全部用标准库不用 pip 装任何第三方包——这对很多刚配好 Python 环境、还在跟 PATH 和虚拟环境搏斗的同学特别友好。动手前先确认环境Python 3.10 以上版本装好后在命令行里敲 python --version 能输出版本号就行。如果你在用 VS Code 写代码给这个项目单独建一个 .venv 虚拟环境再打开终端避免和系统 Python 打架。整个过程不依赖任何第三方 pip 包所以那些在 pip 安装上翻车的同学这版源码对你们特别友好。4.2 命令行入口参数怎么设计才顺手入口参数要回答几个问题扫谁、扫哪些端口、多快、结果放哪。我用 argparse 实现并且加了一个授权确认参数防止误扫公网地址。import argparse import ipaddress import sys from scanner.scheduler import run_scan from scanner.report import write_json, write_markdown def parse_ports(port_str: str) - list[int]: 把 1-1000,3389,8080 这样的字符串解析成端口列表。 ports [] for part in port_str.split(,): part part.strip() if - in part: a, b map(int, part.split(-)) if 1 a b 65535: ports.extend(range(a, b 1)) elif part.isdigit(): ports.append(int(part)) return sorted(set(ports)) def is_private_or_loopback(host: str) - bool: 判断目标是否属于本机或私网地址用于合规确认。 try: ip ipaddress.ip_address(host) except ValueError: return False # 域名不做自动判断走授权确认 return ip.is_private or ip.is_loopback def main(): parser argparse.ArgumentParser(description综合网络安全扫描工具) parser.add_argument(--host, requiredTrue, help目标 IP 或域名) parser.add_argument(--ports, default1-1000, help端口范围如 1-1000,3389) parser.add_argument(--workers, typeint, default100, help并发线程数) parser.add_argument(--timeout, typefloat, default1.0, help端口探测超时秒) parser.add_argument(--report, defaultscan_report.md, help报告输出路径) parser.add_argument(--confirm-authorized, actionstore_true, help确认目标已获得授权扫描公网/域名时必须显式声明) args parser.parse_args() if not is_private_or_loopback(args.host) and not args.confirm_authorized: print(目标不是私网或本机地址若已获得授权请加 --confirm-authorized) sys.exit(1) ports parse_ports(args.ports) print(f待扫描端口数: {len(ports)}并发: {args.workers}超时: {args.timeout}s) results, start_time run_scan(args.host, ports, args.workers, args.timeout) write_markdown(args.report, results, start_time, args.host) write_json(args.report.replace(.md, .json), results, start_time, args.host) print(f扫描完成开放端口 {len(results)} 个报告已写入 {args.report})parse_ports 支持逗号分隔和横线范围还会自动排序去重避免重复扫同一个端口。合规校验这段是血泪经验的结晶早期版本我直接在命令行里传公网 IP 就扫结果有一次把未授权的目标写进了报告差点出事故。现在代码里强制要求公网目标必须显式加 --confirm-authorized虽然只是多敲一个参数但能挡住大部分手滑操作。参数设计上我建议你把 workers 和 timeout 暴露成命令行参数而不是写死在代码里。不同网络环境对超时和并发的敏感度差很多写死的话换一个环境就要改代码。下面这张表是常用参数的经验取值按网络条件调整场景workerstimeout说明本机/回环地址2000.30.5速度快几乎无丢包局域网1000.51.0稳定内网首选跨网段501.52.0减少丢包导致的误报高并发目标1501.0目标性能强时可适当提高4.3 最小运行示例扫本机前 1000 个端口组装好 main.py 之后最小可用的验证命令是扫本机回环地址。回环地址不存在授权问题也最容易观察结果。python main.py --host 127.0.0.1 --ports 1-1000 --workers 100 --timeout 1.0 --report scan_report.md这段命令的意思是扫描本机 1 到 1000 号端口并发 100端口探测超时 1 秒报告写到 scan_report.md。如果本机没开任何服务报告里会显示 0 个开放端口这不算失败而是说明扫描链路本身是通的。真正要验证模块是否工作得先起一个会回包的服务这就是第 6 章要干的事。4.4 输出报告Markdown 要让人一眼看懂报告模块把 ScanResult 列表渲染成 Markdown 表格让非技术背景的人也能快速读出哪个端口开着、跑的是什么、要不要处理。def write_markdown(filename: str, results: list, start_time: str, host: str): lines [ # 综合扫描报告, , f 扫描目标: {host} , f 扫描时间: {start_time} , f 开放端口数: {len(results)}, , | 端口 | 状态 | 产品 | 版本 | 风险 | 建议 |, | --- | --- | --- | --- | --- | --- |, ] for r in results: product r.product if r.product else unknown version r.version if r.version else - lines.append(f| {r.port} | {r.state} | {product} | {version} | {r.risk} | {r.advice} |) with open(filename, w, encodingutf-8) as f: f.write(\n.join(lines))Markdown 表格的渲染逻辑不复杂但有一个细节容易被忽视当表格单元格里出现竖线|时必须转义成\|否则表格会破列。Banner 文本里恰好经常出现竖线所以我建议报告表格只放结构化字段把原始 Banner 单独放到 JSON 里不塞进 Markdown 表格。这样既保住了可读性又保住了原始证据。5. 避坑实录自研扫描器最常翻车的五个问题自研扫描器最大的坑不是代码写不出来而是跑起来之后的表现和预期不一致。我从第一版到现在踩了不少挑五个最常见的记录在这里每条按现象、原因、解决来讲希望你绕开。5.1 扫描慢到像死机先查并发和超时现象扫 1000 个端口用了 20 分钟进度条一动不动以为代码死循环了。 原因端口探测是串行执行的每个端口还要等满 timeout如果把 timeout 设成 3 秒1000 个端口串行就要 50 分钟。 解决用 ThreadPoolExecutor 把端口列表并发提交同时把 timeout 调整到合理范围。局域网内 1 秒足够跨网段再放宽到 2 秒。还有一个辅助手段分批提交每 5000 个端口一组扫完一组打印一行进度至少能确定程序没卡死。5.2 一堆端口显示 unknownBanner 一个字都没抓到现象端口明明开放但报告里 product 全是 unknownbanner 字段为空。 原因最常见的是抓 Banner 时先发了探测词把 FTP、SSH 这类等待客户端先说话的服务搞蒙了另一个原因是超时太短Banner 还没吐完就被截断。 解决改成先读后写先用 recv(256) 试探服务端是否主动推送再对 HTTP 端口补发 HEAD 请求。Banner 超时建议至少 2 秒。另外对 443 这类 TLS 端口不要发明文 HTTP 请求直接读被动 Banner识别不了就标记 unknown交给人工确认别硬猜。5.3 并发一高目标服务先崩了现象扫描脚本没崩目标机器的 CPU 被打满服务响应变慢甚至连接被拒。 原因workers 设得太大同时发起的连接数超过了目标服务的承受能力尤其是一些轻量级设备或自带连接池限制的中间件。 解决把端口扫描和 Banner 抓取的并发分开设置端口扫描可以用 100Banner 抓取减半到 50。如果目标是对外提供业务的机器再保守一点workers 压到 30 以下并且把端口列表分成多批批与批之间 sleep 1 秒。扫描工具的价值是发现风险不是把业务扫挂。5.4 同一台机器两次扫描结果对不上现象上午扫出 3 个开放端口下午再扫变成 2 个另一个显示 closed。 原因网络丢包、目标服务临时繁忙、timeout 设置太紧以及端口探测本身受中间设备影响这些都属于正常波动。还有一个隐蔽原因是扫描顺序和超时抖动叠加结果不可复现。 解决给每次扫描记录开始时间、参数和原始 BannerJSON 报告不要覆盖旧文件文件名加时间戳或随机后缀。对可疑端口做二次重试第一次失败后隔 1 秒再探一次两次都失败才判定为 closed。网络是最玄学的部分与其追求单次完美不如把过程记录下来让结果可追溯。5.5 扫了不该扫的地址合规问题比技术问题更致命现象有人为了测试直接把 --host 设成公网 IP扫完才发现目标没授权或者日志里留下了大量探测公网地址的记录。 原因工具没有做任何目标范围约束命令行传什么就扫什么使用者一顺手就出错。 解决在入口加合规校验私网和回环地址直接放行公网和域名必须要 --confirm-authorized 才能继续。这是成本最低、效果最好的保护。更严格的做法是维护一个许可网段白名单启动时校验目标是否在白名单内。安全工具的第一原则是授权这条不守住技术做得再漂亮也会变成事故。6. 进阶验证本地起一个假服务端到端证明扫描器没白写如果只扫一个没开服务的 127.0.0.1你永远不知道指纹匹配到底有没有生效。最可靠的验证方式是本地起一个带特征 Banner 的假服务然后用扫描器去扫它看能不能认出产品名和版本。6.1 写一个假 Nginx 服务import socket def fake_nginx(): 在 127.0.0.1:8080 起一个假 HTTP 服务返回带 Nginx 特征的响应头。 server socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind((127.0.0.1, 8080)) server.listen(5) print(fake server on 127.0.0.1:8080) while True: conn, _ server.accept() conn.recv(1024) conn.sendall(bHTTP/1.1 200 OK\r\nServer: nginx/1.18.0\r\nContent-Length: 0\r\n\r\n) conn.close()这个假服务的原理是手工组装一个 HTTP 响应头里面带着明显的 Server: nginx/1.18.0。它不算攻击工具只是用来验证你的指纹库能不能被唤醒。运行python fake_server.py后另开一个终端执行python main.py --host 127.0.0.1 --ports 8080 --report fake_scan.md打开 fake_scan.md预期在表格里看到端口 8080、状态 open、产品 nginx、版本 1.18.0、风险低。这说明从 TCP 探测到 Banner 抓取再到指纹匹配整条链路都通了。6.2 把这次验证变成习惯往后每加一条指纹规则都用这种方式起一个对应特征的假服务跑一遍比什么都管用。我自己第一版扫描器只扫端口结果被问那这个端口是什么服务时当场卡住后来老老实实补上 Banner 抓取和指纹匹配并且坚持用假服务做回归验证才敢拿它去做内网自查。再往后扩展可以按这个方向走用 ssl 模块做 HTTPS Banner 识别、把指纹库外置成 JSON 文件、加入 Web 页面标题抓取、对 UDP 服务做补充探测。但每加一个能力都要先回答一个问题这个能力能不能被测试、结果能不能被解释。能解释的扫描结果才值得写进报告这比跑得快重要得多。希望帮到你。本文还有配套的精品资源点击获取
