安全从业者的工具箱里,漏洞扫描器是出场率最高的家伙。市面上的成品要么贵(Nessus),要么重(OpenVAS),要么只做一件事(Nmap)。这篇带你从零搭一个能打的全功能扫描器——端口扫描、目录爆破、SQL 注入/XSS/命令注入检测、子域名枚举、HTML 可视化报告,一个不少。
最终成品
一个 Python CLI 工具,输入一个域名或 IP,自动跑完四条扫描管线,生成带风险可视化图表的 HTML 报告。
1 | # 单目标全量扫描 |
完整代码:CyberThreatHunter(基于 J09715/Vulnerability-Scanning-tool 二次开发)
一、架构设计:流水线 + 并发调度
1 | main.py (CLI入口) |
核心设计决策:
- 目标级并发 + 管线内顺序执行:多个目标用
ThreadPoolExecutor并行跑,但每个目标内部四个模块按顺序执行(端口 → 目录 → 漏洞 → 子域名)。因为漏洞扫描需要先知道开放端口,目录爆破需要先确认 Web 服务可达。 - 统一的发现项数据模型:所有子模块产出的都是一个
List[Dict],每个 dict 有固定的字段结构(type/name/severity/cvss_score/evidence/remediation)。统一的输出格式让报告生成器不用关心数据来源。
二、统一的数据模型:ScanResult
1 | from dataclasses import dataclass, field |
每个 finding 的字段契约:
1 | { |
为什么选 dataclass 而不是 Pydantic:这个项目的场景是内部数据传递,不需要运行时验证和序列化。dataclass 零依赖、性能更好、代码更简洁。
三、核心引擎:VulnerabilityScannerMain
1 | class VulnerabilityScannerMain: |
三个关键设计:
- 顺序管线:端口 → 目录 → 漏洞 → 子域名。这保证了依赖关系——如果 80 端口没开放,目录爆破和漏洞扫描自然跳过。
- 异常隔离:单个目标扫描失败不影响其他目标,错误被收集到
ScanResult.errors中。 - 配置驱动:所有参数(超时、线程数、字典路径、端口范围)都在
config.yaml中,不需要改代码。
四、模块一:端口扫描
端口扫描是信息收集的第一步。这个模块实现了双引擎策略:
1 | try nmap (SYN 半开扫描) → 成功 → 返回 |
4.1 Nmap 引擎(优先)
1 | import nmap |
4.2 Socket 回退引擎
1 | import socket |
4.3 Banner 抓取 + 服务识别
1 | 开放端口 → 发送 GET / HTTP/1.0 → 正则匹配响应头 |
1 | def _grab_banner(self, target, port, timeout=3): |
4.4 端口风险评估
每个端口预设了风险等级,不是所有开放端口都需要紧张:
1 | port_risks = { |
五、模块二:Web 目录爆破
5.1 字典 + 扩展名变换
1 | # 基础路径: admin, backup, config, .git, .env, phpinfo.php... |
5.2 并发请求 + 指纹伪装
1 | from concurrent.futures import ThreadPoolExecutor |
5.3 敏感路径自动分级
1 | sensitive_paths = { |
5.4 HTTP 方法检查
除了目录存在性,还检查目标支持哪些 HTTP 方法——PUT 和 DELETE 如果开放而无认证,危害巨大:
1 | methods = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD', 'PATCH', 'TRACE'] |
六、模块三:Web 漏洞扫描(核心)
这是整个扫描器里最有技术含量的部分。实现了三种注入检测。
6.1 页面爬取
先爬取目标页面,提取所有链接和表单参数:
1 | import re |
6.2 SQL 注入检测:基于错误回显
1 | sql_payloads = [ |
检测逻辑:对每个 GET/POST 参数注入 payload,检查响应中是否匹配上述 20 个错误特征正则。命中任何一个即判定存在 SQL 注入。
1 | def _test_sql_injection_param(self, url, param, method='GET'): |
6.3 XSS 检测:反射验证法
1 | xss_payloads = [ |
为什么用”完整包含”而不是更复杂的 DOM 分析:反射型 XSS 的核心特征是 payload 原样出现在响应中。这个简单方法误报率低、速度快、不需要浏览器渲染。
6.4 命令注入检测
1 | cmd_payloads = [ |
七、模块四:DNS 子域名枚举
7.1 字典枚举(第一层)
1 | import dns.resolver |
7.2 通配符 DNS 检测
有些域名配置了通配符 DNS(*.example.com),会导致所有不存在的子域名都解析成功。用随机子域名测试:
1 | import time |
7.3 安全记录检查
1 | # SPF 记录(防邮件伪造) |
八、报告引擎:让扫描结果可读
扫描结果再多,老板/客户看不懂也白搭。HTML 报告是默认输出,包含:
8.1 统计卡片
1 | ┌──────────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ |
8.2 可折叠详情面板
每个发现项默认折叠,点击展开显示 CVSS 评分、原始证据、修复建议和 JSON 数据:
1 | <div class="finding" onclick="toggleDetails(this)"> |
8.3 风险分布可视化
1 | .risk-bar { |
8.4 Excel 双工作表
1 | import pandas as pd |
九、配置系统设计
200+ 行的 config.yaml 集中管理所有参数,改了配置不需要动代码:
1 | scanning: |
十、这个项目中值得复用的设计模式
10.1 策略模式——双引擎端口扫描
nmap 和 socket 两个引擎实现了同一接口。调用方不关心用的是哪个引擎:
1 | def scan(self, target): |
10.2 管线模式——模块串联
四个模块串联成一条流水线,数据单向流动(端口结果 → 目录扫描 → 漏洞测试 → 子域名枚举),每个模块的输出是下一个模块的上下文。
10.3 工厂模式——扫描器注册
1 | self.scanners = { |
要加新模块?在 _init_scanners 里加一行,在 scan_types 列表里加个名字就行。
十一、总结
这个扫描器从零写下来大概 2000+ 行 Python,核心逻辑其实不复杂,但架构设计决定了可维护性和可扩展性:
- 统一数据模型 → 报告引擎零耦合
- 配置驱动 → 改参数不碰代码
- 双引擎策略 → nmap 不可用时自动降级
- 异常隔离 → 单目标失败不影响批量
法律声明:此工具仅限授权安全测试使用。未经授权扫描他人系统可能违反《网络安全法》。