ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

基于Python的Web安全渗透测试工具集:原理与工程实现解析

基于Python的Web安全渗透测试工具集:原理与工程实现解析 简介这是一套基于Python开发的Web安全渗透测试工具集成资源包含完整源代码与使用文档主要面向计算机相关专业学生、安全测试初学者以及需要毕设/课设项目的开发者。工具提供从漏洞扫描到渗透测试的多种功能模块并附带若干CVE漏洞检测脚本如CVE-2020-14882、CVE-2018-2894等与弱口令字典可用于网站安全防护与漏洞验证。压缩包共124个文件其中49个Python源码文件为核心程序49个txt文件为说明文档与密码列表另有20个abak备份文件及Markdown文档、License、RAR压缩包等辅助材料整体约29.19MB结构清晰便于查阅。目前已有48人学习下载代码经作者毕设实测通过答辩平均分96分适合在此基础上进行二次开发或迁移到实际安全测试场景。学习时建议优先阅读README文档按指引配置运行环境即可快速上手并理解渗透测试工具的实现思路。1. 一套能跑通答辩的 Python 渗透测试工具先看它解决什么问题做网络安全方向的毕业设计最怕的不是写不出代码而是答辩时被老师追问“你这套工具和网上现成的扫描器有什么区别”。如果你手里只有一堆零散脚本这个问题基本答不上来但如果有一套自己一行一行敲出来的工具集还带了完整文档说明整个答辩逻辑就顺了。这套“基于 Python 的 Web 安全渗透测试工具集成”就是干这个用的它不是某个单点脚本而是把端口扫描、子域名探测、SQL 注入检测、XSS 检测、报告生成串起来的工具集压缩包里带着源代码和文档说明。适合两类人——一类是安全方向或软件工程方向要做毕业设计的学生另一类是刚入门渗透测试、想靠读源码理解工具原理的开发者。它解决的最大问题不是“扫描功能多强”而是“有完整源码、能讲清每个模块怎么工作、可复现”。2. 整体架构与核心脚本把工具集拆开看它由哪几块组成拿到压缩包先别急着跑 demo把目录结构看清楚。这类毕设工具集最怕的就是“一个文件里塞了所有功能”跑是能跑答辩时老师一看代码组织就给减分。这套项目用的是比较稳妥的分层写法入口、模块、数据、文档四层分开每个检测模块独立成文件后续加功能不需要动主入口。2.1 目录结构先分清工具模块和辅助脚本典型的结构是这样的websec-toolkit/ ├── main.py # 入口命令行参数解析、模块调度 ├── requirements.txt # Python 依赖清单 ├── modules/ │ ├── port_scanner.py # 端口扫描模块 │ ├── subdomain_brute.py # 子域名爆破模块 │ ├── sql_detector.py # SQL 注入检测模块 │ ├── xss_detector.py # XSS 检测模块 │ └── reporter.py # 报告生成模块 ├── data/ │ ├── payloads/sqli.txt # SQL 注入 payload 字典 │ ├── payloads/xss.txt # XSS payload 字典 │ └── subdomains.txt # 子域名词典 └── docs/ ├── README.md # 项目说明 ├── 安装与使用.md # 环境搭建与命令示例 └── 测试报告示例.md # 答辩用的报告模板这个目录设计里有两个点是答辩容易被问到的。第一data 目录单独放说明 payload 字典和数据文件是解耦的换字典不用改代码——老师问“我能不能换成自己的字典”时你只需要回答“把文件放进去就行”。第二docs 目录里的测试报告示例不是摆设它是照着真实渗透测试报告格式写的里面规定了漏洞描述、危害等级、复现步骤、修复建议四段结构。依赖清单里需要重点说一句的是版本问题。requirements.txt 里一般会锁 Python 版本要求常见的是 Python 3.8requests 和 dnspython 这两个是核心。装依赖前先确认本机 Python 版本用 python --version 看一眼低于 3.8 的话很多新语法用不了高于 3.12 的话某些旧版依赖安装会出问题。这里我一般建议直接建一个虚拟环境避免把系统 Python 环境搞乱。2.2 端口扫描模块从 socket 连接到线程池并发端口扫描是整个工具集里最容易理解也最适合在答辩时现场讲的模块。原理不复杂对目标主机的每个端口尝试建立 TCP 连接连得上就说明端口开放。但实现上有三个细节决定扫描质量超时时间、并发方式、端口列表。# modules/port_scanner.py import socket from concurrent.futures import ThreadPoolExecutor, as_completed COMMON_PORTS [21, 22, 23, 25, 53, 80, 443, 3306, 3389, 8080, 8443] DEFAULT_TIMEOUT 3 # 单次连接超时单位秒 DEFAULT_WORKERS 50 # 并发连接线程数 def check_port(host, port, timeoutDEFAULT_TIMEOUT): 对单个端口发起 TCP 连接测试返回 (端口号, 是否开放) try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.settimeout(timeout) s.connect((host, port)) return port, True except (socket.timeout, OSError): return port, False def scan(target, portsNone, timeoutDEFAULT_TIMEOUT, max_workersDEFAULT_WORKERS): 并发扫描目标主机的指定端口列表 ports ports or COMMON_PORTS open_ports [] with ThreadPoolExecutor(max_workersmax_workers) as pool: futures {pool.submit(check_port, target, p, timeout): p for p in ports} for fut in as_completed(futures): port, opened fut.result() if opened: open_ports.append(port) return sorted(open_ports)这段代码的选型理由值得讲清楚。为什么用多线程而不是多进程因为端口扫描的大部分时间花在等待 TCP 握手上这个等待是网络 IO 阻塞线程在阻塞时会主动让出 CPU所以 50 个线程跑下来 CPU 占用几乎可以忽略多进程反而要额外承担进程创建和 IPC 的开销。为什么用 ThreadPoolExecutor 而不是自己写 thread.join 循环因为线程池替你管了任务的提交、执行、异常回收代码量少一半答辩时也更好解释。参数调整按场景走。timeout3 适合本机、局域网或者你已知目标网络质量不错的环境扫描公网目标建议调到 5因为跨运营商丢包严重3 秒经常误判“连接超时”把本来开放的端口漏掉。max_workers50 是一个相对保守的值校园网环境下跑不会触发系统限制你拉到 200 试试很快会碰到系统报错这个在第五章展开讲。另外一个容易忽略的细节是端口列表默认列表里的 8443 是很多管理后台的备用 HTTPS 端口3306 是 MySQL3389 是 Windows 远程桌面这几个端口的命中结果对后续的漏洞检测方向很有参考价值。2.3 子域名爆破模块字典驱动加 DNS 并发解析子域名收集在渗透测试里的作用有两个扩大攻击面、找到测试环境中直接暴露的内部服务。这个模块做的事情很简单——拿字典里的每个前缀拼上主域名然后做 DNS 的 A 记录解析能解析出 IP 就是有效子域名。# modules/subdomain_brute.py import dns.resolver from concurrent.futures import ThreadPoolExecutor def resolve_domain(full_domain): 解析域名的 A 记录返回 IP 列表解析失败返回空列表 try: answers dns.resolver.resolve(full_domain, A) return [answer.address for answer in answers] except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.exception.Timeout): return [] def subdomain_brute(domain, dict_path, threads20): 读取字典文件逐个拼接子域名并做并发解析 found {} with open(dict_path, r, encodingutf-8) as f: words [line.strip() for line in f if line.strip()] with ThreadPoolExecutor(max_workersthreads) as pool: futures { pool.submit(resolve_domain, f{word}.{domain}): word for word in words } for fut in futures: ips fut.result() if ips: found[futures[fut]] ips return found这里有一个技术选型容易踩坑的点为什么不用 Python 自带的 socket.getaddrinfo 来做解析而是装一个 dnspython 依赖socket.getaddrinfo 对“域名不存在”和“DNS 服务器暂时无响应”这两类情况都会抛异常你没法干净地判断到底哪种情况会把大量正常失败当成网络异常直接重试而 dns.resolver 能精确捕获 NXDOMAIN域名不存在和 Timeout查询超时这两个分支在代码里分开处理逻辑就清晰很多调试的时候也容易定位问题。并发线程数 threads20 是刻意比端口扫描低的选择原因是 DNS 查询的请求量非常大——字典里如果有几千条记录解析失败的情况也占了绝大多数如果线程数拉太高很容易把校内 DNS 服务器或者运营商解析器打出过载限流到时候整个网段的解析都会变慢反而拖累扫描。另外这个模块的输出格式是字典键是子域名字符串值是 IP 列表后续如果想做“子域名指纹识别”可以直接对着这里的 IP 列表去做 HTTP 请求探测数据结构不需要改。3. 核心检测模块SQL 注入与 XSS 的检测逻辑和参数调优如果说第二章的端口扫描和子域名爆破是“工具集的外围”那 SQL 注入检测和 XSS 检测才是这套渗透测试工具的“丹田”。这两个模块的检测逻辑直接决定你答辩时能不能讲出深度。很多毕设写到这一步就露怯了因为实现上只会拿正则去匹配 payload 特征漏报和误报都严重。这套工具带的做法值得一提用“状态码变化 返回体错误特征”双信号判断 SQL 注入用“标记字符串探路 payload 反射验证”判断 XSS下面拆开讲。3.1 SQL 注入检测双信号判断给 payload 分层次SQL 注入的检测原理说起来很直白往参数里塞破坏 SQL 语句结构的 payload然后看数据库和 Web 应用的反应。但反应的类型很复杂有的报错、有的返回状态码 500、有的页面白屏、有的执行时间变长。如果只靠一两种信号误报率会很高。这套工具的检测模块用了双信号判断同时看 HTTP 状态码变化和返回体里是否出现数据库错误特征。# modules/sql_detector.py import re import requests # 按探测强度分层先轻后重避免上来就用时间盲注把目标拖垮 SQLI_PAYLOADS [ , # 第一层探测单引号闭合是否被破坏 OR 11, # 第二层恒真条件判断是否存在注入点 OR 11-- -, # 第三层注释掉尾部语句确认注入成立 AND SLEEP(3)-- -, # 第四层时间盲注验证靠响应延迟判断 ] ERROR_PATTERNS [ rSQL syntax, # MySQL 报错 rUnclosed quotation mark, # SQL Server 报错 rmysql_fetch, # PHP 老接口 rORA-[0-9]{4,5}, # Oracle 报错 rSQLite3::, # SQLite 报错 rMicrosoft OLE DB, # Access 报错 ] def test_sqli(base_url, param, payload, session, timeout5): 对单个参数、单个 payload 执行一次 SQL 注入探测 normal_params {param: 1} test_params {param: payload} # 先请求正常值作为基线再请求带 payload 的地址 normal_resp session.get(base_url, paramsnormal_params, timeouttimeout) test_resp session.get(base_url, paramstest_params, timeouttimeout) # 信号一状态码差异 if test_resp.status_code ! normal_resp.status_code: # 信号二返回体中匹配数据库错误特征 if test_resp.text and any(re.search(p, test_resp.text) for p in ERROR_PATTERNS): return True return False def scan_target(base_url, param_list): 对目标 URL 的所有参数逐一执行 SQL 注入检测 session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), }) for param in param_list: for payload in SQLI_PAYLOADS: try: if test_sqli(base_url, param, payload, session): print(f[] SQL 注入发现: 参数 {param}, payload: {payload}) break except requests.RequestException: # 单次请求异常不影响整体扫描跳过继续 continue这段代码里有几个位置需要仔细解释。第一请求参数用的是 params 而不是自己拼字符串这个区别非常关键——requests 的 params 会自动做 URL 编码payload 里的单引号、空格、反斜杠交给库处理如果自己拼碰到特殊字符就要手动 quote 一遍漏一次就白搭一个 payload。第二payload 分层的思想体现在列表顺序上第一层是裸单引号只用于探测“SQL 结构是否被打乱”很多站点对裸单引号会直接 500第三层才上恒真条件确认注入成立时间盲注放在最后因为它消耗目标资源不能一上来就用于所有参数。第三ERROR_PATTERNS 里的正则其实覆盖了多种数据库答辩时如果被问“你的工具支持哪些数据库”直接对着这个列表念就行。超时参数 timeout5 是给检测模块单独设的比端口扫描高是因为 SQL 注入请求里包含 SLEEP(3) 这样的拖延型 payload超时太短会直接中断响应读取时间盲注信号就丢了。requests.Session 只创建一次并复用的原因以实际场景再确认一遍Session 会自动保持 TCP 连接还会带上前一次请求拿到的 Cookie保持登录态的同时提升扫描速度也能降低被 WAF 封 IP 的风险。3.2 XSS 检测先探反射点再验证 payload过滤转义干扰XSS 的检测思路和 SQL 注入完全不同。SQL 注入看的是后端数据库的报错XSS 看的是前端页面的响应内容——你提交进去的 payload 是否原样反射回了响应体里。这里的难点有两个一是怎么区分“反射了”和“没反射”二是怎么处理 HTML 实体编码造成的误判。这套工具的 XSS 检测模块用了一个非常实用的两阶段策略。# modules/xss_detector.py import requests XSS_PAYLOADS [ scriptalert(1)/script, img srcx onerroralert(1), \svg/onloadalert(1), ] def detect_xss(base_url, param, session, timeout5): 对单个参数执行 XSS 检测返回可触发的 payload 或 None # 阶段一用唯一标记字符串探测反射点 marker xss_marker_2024 probe_params {param: marker} probe_resp session.get(base_url, paramsprobe_params, timeouttimeout) if marker not in probe_resp.text: return None # 没反射直接跳过省流量 # 阶段二逐个 payload 做验证 for payload in XSS_PAYLOADS: test_params {param: payload} resp session.get(base_url, paramstest_params, timeouttimeout) # 先看 payload 是否原样出现在响应中 if payload not in resp.text: continue # 再看是否被 HTML 实体转义处理过 # 如果页面把 转成了 lt;payload 虽然反射了但不可执行 if script in resp.text or img in resp.text: return payload return None阶段一的做法是很多扫描器的通用套路用一串几乎不可能出现在正常页面里的标记字符串xss_marker_2024做一次探测请求响应里有它说明这里存在反射点没有就直接跳过这个参数不浪费时间。好处很明显——XSS payload 往往有十个以上如果不先确认反射点每个 payload 都要发一次完整请求流量和耗时都是成倍增加。阶段二的转义检查是这个模块里最容易被忽略但最值钱的部分。很多目标站点的过滤方式是 HTML 实体编码也就是把 转成 、 转成 。这种情况下 payload 确实反射回了响应体里但它被浏览器当作文本渲染了不会执行不能算漏洞。如果只拿“payload in response.text”做判断这种场景就会大量误报。加一个“响应里是否含有原始标签”的判断就能把实体编码干扰滤掉。这里有一个边界要承认如果过滤方式是去掉 script 关键字但保留尖括号比如把
RELATED READING

延伸阅读

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