
简介这是一份基于Python漏洞扫描系统的毕业设计完整实现包面向计算机、网络空间安全等专业学生可作为毕业设计、课程项目或实训参考。系统围绕Python框架与MySQL数据库搭建覆盖用户登录注册、可视化首页、端口扫描以及扫描列表等核心模块输入IP地址与端口后即可执行扫描并返回结果首页借助图表展示检测概况适合需要从零搭建安全类Web项目的学习者参考。压缩包共541个文件、约91.21MB内容涵盖py/pyc源码、html/css/js前端资源、sql及数据库相关文件、项目文档、图片与演示视频等既有可直接运行的代码也包含便于理解项目结构的说明材料和数据初始化相关文件。目前已吸引2978人浏览学习适合快速复现系统、学习端口扫描原理并借鉴毕业设计的模块划分、数据库设计与答辩展示思路。1. 用 Python 做漏洞扫描系统当毕设先看清难度曲线每年开题季都会有一批人 pick 这个题目基于 Python 的漏洞扫描系统。听着像是高年级黑客才会碰的东西实际上课程设计级别的扫描器和网上流传的黑客工具差距很大你要做的是一套能完成探测目标→发现弱点→入库→出报告流程的教学级系统而不是一个用来搞事的武器。这套东西的交付物也不复杂源码、数据库、演示视频三件套齐了就能答辩。这个方向适合两类人第一类是网安专业需要做系统型毕设的学生第二类是 Python 基础尚可、想通过一个真实项目把网络编程、并发、数据库全部串起来的开发者。难度曲线比你想得平缓——核心扫描器用 socket 和 requests 就能写数据库用 MySQL 三类表就能存不存在需要啃源码原理的深水区。真正的坎不在技术本身而在你把模块拼起来之后各种环境、并发、误报问题会让你的演示翻车。这篇文章就按我自己做这类毕设辅导时用的顺序把模块设计、核心代码、数据库落点和坑全部过一遍。2. 六个模块拆出扫描器骨架选型理由与数据流转2.1 为什么用 Python不是技术偏好是毕业设计的时间约束常见做法是用 Python 3.8 做整个系统不需要碰 C/C。理由很现实你只有几个月时间论文主体是系统设计与实现不是底层引擎研发。Python 在这类项目里最值钱的地方有三块。第一是网络库覆盖得全socket 做端口探测、requests 发 HTTP 请求、BeautifulSoup 解析页面链接整个链路不需要自己封装协议细节。第二是并发模型够用扫描器是典型 IO 密集型程序等端口超时、等 HTTP 响应的时候线程睡在那里Python 的 GIL 在这种场景下基本不影响吞吐量。第三是答辩现场改需求时改代码、重启服务都比底层语言快得多。有人会质疑性能问题说 Python 慢。这个说法要分场景。扫描一批 C 段地址、每个地址扫几十个常用端口瓶颈在网卡、对端防火墙丢包和你的超时设置不在语言本身。就算真的慢毕设验收也只看你有没有把并发做上去不会要求你跑出 Nessus 的商业级吞吐。2.2 模块划分让论文结构和代码结构对得上我一般把整个系统拆成六个模块论文目录跟着模块走答辩时讲起来也顺任务调度模块接收用户输入的扫描目标域名或 IP生成任务 ID维护任务状态。主机探测模块先用 ICMP Ping 判断主机是否存活存活再进入端口扫描避免对死主机浪费时间。端口扫描模块对指定端口列表做 TCP Connect 扫描返回开放端口列表。Web 指纹识别模块对开放 80/443 端口的地址发 HTTP 请求从响应头和页面特征判断中间件类型Nginx、Apache、PHP、Tomcat 等。漏洞检测模块针对常见 Web 漏洞做基于请求响应的探测比如 SQL 注入的报错/时间盲注特征、XSS 的输入回显检测。数据持久化与报告模块把任务、主机、漏洞结果写入 MySQL扫描结束后生成 HTML 或 CSV 报告。这个拆法还有一层隐藏好处对你来说每个模块都能单独调试对答辩老师来说每个模块都能对应到论文里的一章。最大的忌讳是把所有逻辑塞进一个 main.py最后代码 2000 行论文却不知道从哪一章开始写。2.3 数据流转一条扫描请求在系统里走完整路径把数据流转先理清写代码时边界就清楚了。用户在页面上提交一个目标比如192.168.1.0/24或者http://test.com这个操作触发任务调度模块向scan_task表插入一条任务记录状态为排队中。后台调度器轮询到新任务后把状态改成扫描中开始做主机探测。探测存活的 IP 被送进端口扫描线程池扫描结果插入scan_host表。随后对这些主机的 Web 端口做指纹识别和漏洞检测每发现一条问题记录就写入scan_vuln表。全部结束后任务状态改成已完成报告模块从三张表里关联查询出结果渲染成 HTML 报告。这个流程里数据库不是附属品而是核心枢纽。线程之间不直接传数据而是通过谁把结果写库、谁负责下一阶段取任务来协作。这样有两个好处一是扫描引擎崩溃了数据库里已有结果不会丢任务重启后还能从断点继续二是答辩演示时可以不开扫描界面直接在 Navicat 里对数据库做增删改查向老师展示数据结构设计这部分很加分。2.4 环境准备先把运行底座立起来常见做法是 Windows 上用 VSCode 做开发在 Python 官方安装教程走完一遍之后装依赖。建议单独建虚拟环境避免和别的项目互相污染python -m venv venv venv\Scripts\activate # Windows pip install requests beautifulsoup4 pymysql sqlalchemy这里说明一下为什么用 SQLAlchemy 而不是直接上 pymysql扫描器是多线程并发写库如果用裸 pymysql 每个线程各建一个连接几十个线程一跑MySQL 的max_connections很快被打满。SQLAlchemy 自带连接池pool_size控制空闲连接数max_overflow控制峰值扩容上限代码层面少写很多连接管理逻辑。VSCode 里配置 Python 环境时注意选./venv/Scripts/python.exe作为解释器否则终端跑得起来、F5 调试却报模块找不到这是个毫无技术含量却能卡你半小时的坑。3. 把核心扫描器跑起来端口扫描、指纹识别与 SQL 注入检测3.1 端口扫描模块用 socket 连接探测加线程池提速端口扫描是整个系统里最好写也最好验证的模块。常见做法是 TCP Connect 扫描对目标的某个端口发起一次完整的 TCP 三次握手握手成功就视为端口开放。Python 的socket.create_connection()把这一步封装好了我们要做的只是控制超时和并发。import socket from concurrent.futures import ThreadPoolExecutor, as_completed COMMON_PORTS [ 21, 22, 23, 25, 53, 80, 110, 135, 139, 143, 443, 445, 993, 995, 1433, 1521, 3306, 3389, 5432, 6379, 8080, 8443, 8888, 9200 ] def scan_port(ip: str, port: int, timeout: float 1.0) - tuple: 扫描单个端口返回 (port, state)。 try: sock socket.create_connection((ip, port), timeouttimeout) sock.close() return port, open except (socket.timeout, ConnectionRefusedError, OSError): return port, closed def scan_host(ip: str, ports: list, timeout: float 1.0, workers: int 100) - list: 多线程扫描一个主机的多个端口。 results [] with ThreadPoolExecutor(max_workersworkers) as executor: futures { executor.submit(scan_port, ip, port, timeout): port for port in ports } for future in as_completed(futures): port, state future.result() if state open: results.append(port) return sorted(results)代码逻辑不复杂scan_port里socket.create_connection一旦成功就返回失败则按异常类型区分为超时还是拒绝连接。scan_host用线程池把端口列表摊给多个线程同时探测as_completed等待所有任务返回。这里几个参数是真正的调优点。timeout设 1.0 秒是个折中值——设 0.5 秒速度快但跨运营商网络容易误报关闭设 3 秒准确但扫 25 个端口要 75 秒起步毕设演示等不起。workers建议 50100Windows 上线程开销较轻开 200 以上会出现大量 socket 超时并发的错乱现象而这个错乱用调试器看不出来只能靠调参解决。3.2 Web 指纹识别解析响应头和页面特征端口扫描做完拿到一批开放端口其中 80、443、8080 这些要送到 Web 指纹模块去识别中间件。常见做法是发一次带默认 UA 的 GET 请求从响应头的Server、X-Powered-By字段和页面 body 中的特征片段做匹配。这套逻辑和爬虫很像本质就是请求→解析→匹配规则所以很多做爬虫的库在这里直接复用。import requests import re FINGERPRINT_RULES [ {name: Nginx, header: rnginx, weight: 3}, {name: Apache, header: rapache, weight: 3}, {name: PHP, header: rX-Powered-By:\s*PHP, weight: 2}, {name: Tomcat, header: rApache-Coyote, weight: 2}, {name: WordPress, body: rwp-content|wp-includes, weight: 2}, {name: ThinkPHP, body: r/Public/|ThinkPHP, weight: 2}, ] def fingerprint(url: str, timeout: float 3.0) - list: 识别目标 Web 服务的中间件指纹。 detected [] try: resp requests.get(url, timeouttimeout, verifyFalse) header_text \n.join(f{k}: {v} for k, v in resp.headers.items()) except requests.RequestException as e: return detected for rule in FINGERPRINT_RULES: target header_text if header in rule else resp.text pattern rule.get(header) or rule.get(body) if re.search(pattern, target, re.IGNORECASE): detected.append(rule[name]) return detected这段代码的说明分两层。第一层是请求参数verifyFalse是为了处理自签名证书的站点不设的话很多内网测试目标会直接抛 SSL 错误timeout3.0必须设不设的话 requests 默认行为是无限等待一个页面卡住会让整个扫描线程池挂掉。第二层是规则匹配优先级先匹配header字段的规则再匹配body特征但实际工程里 Nginx 反代后面挂 Tomcat 的情况很常见Server头是 Nginx、X-Powered-By又暴露了 PHP所以检测结果不能只取一个应该返回列表而不是单个字符串。我给每条规则加了weight字段在报告阶段按权重排序让最可信的指纹排在最前面。3.3 漏洞检测模块用时间差判断 SQL 注入SQL 注入检测是毕设的门面功能答辩老师大概率会问。常见做法是在 URL 参数上拼接探测用的 SQL 片段观察响应差异。这里必须强调一个边界所有测试只能针对你自己搭的靶场或你有权测试的系统毕设系统默认扫描目标是本地地址或测试环境这个约束要在代码注释和论文里都写清楚。写一个最经典的时间盲注探测对参数同时发起sleep(0)和sleep(3)两个请求比较响应耗时。如果带sleep(3)的请求明显变慢超过 2 秒差值说明参数值被拼进了后端 SQL 语句并被执行。import requests import time def time_based_sql_injection(base_url: str, param: str, timeout: float 5.0) - bool: 基于时间差检测 SQL 注入只对授权目标使用。 payloads { baseline: f{param}1 AND SLEEP(0) -- , test: f{param}1 AND SLEEP(3) -- , } times {} for name, query in payloads.items(): try: start time.time() requests.get(base_url, paramsquery, timeouttimeout) times[name] time.time() - start except requests.RequestException: return False # 连续测三次 test 载荷取中位数排除网络抖动 delays [] for _ in range(3): try: start time.time() requests.get(base_url, paramspayloads[test], timeouttimeout) delays.append(time.time() - start) except requests.RequestException: pass if not delays: return False median_delay sorted(delays)[len(delays) // 2] return (median_delay - times[baseline]) 2.0这里的逻辑和参数值得展开说。第一SLEEP(0)是基线请求SLEEP(3)是探测请求两者的时间差才是判断依据——只看单次请求耗时超过 3 秒是不科学的因为目标机器本身可能负载很高页面正常响应就要 3 秒。第二为什么探测要测三次取中位数网络抖动是玄学第一次 3.1 秒、第二次 0.3 秒、第三次 2.9 秒取平均会被拉歪取中位数最稳。第三时间差 2.0这个阈值的选法SLEEP(3)在注入成功时会让查询最少睡 3 秒扣除网络自身波动 1 秒2 秒阈值既有区分度又不会因为网络轻微抖动误报。第四这段代码只做了 GET 请求实际应用中还要处理 POST 表单参数和 JSON 参数但思路一致把 payload 放进对应的参数载体里即可。3.4 XSS 检测从响应回显找线索XSS 检测比 SQL 注入简单把一段标记字符串作为输入提交检查响应页面里是否原样回显了这段字符串。如果回显且没有转义就标记为潜在反射型 XSS。常见做法是构造一个无害但特征明显的字符串比如passtest123qv避免把真实攻击 payload 写进代码库——毕设代码会被查重和检查保持教学级写法比炫技安全得多。4. 把扫描结果写进 MySQL表设计、连接池与任务管理4.1 三张核心表任务、主机、漏洞的分层设计数据库部分直接决定了答辩时老师问你的系统数据怎么组织的你答得顺不顺。常见做法是建三张表scan_task存扫描任务scan_host存主机和端口结果scan_vuln存漏洞详情。拆成三张而不是塞进一张大宽表有三个现实原因一个任务会扫多个 IP一个 IP 会开放多个端口一个主机可能命中多个漏洞这是天然的一对多关系报告模块按任务 ID 关联查询SQL 写起来比解析 JSON 字段清晰论文里的 E-R 图画出来也规范。下面是核心建表语句CREATE TABLE scan_task ( id INT AUTO_INCREMENT PRIMARY KEY, target VARCHAR(255) NOT NULL COMMENT 扫描目标支持IP或域名, status TINYINT DEFAULT 0 COMMENT 0排队 1扫描中 2完成 3失败, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, finish_time DATETIME NULL, description VARCHAR(512) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE scan_host ( id INT AUTO_INCREMENT PRIMARY KEY, task_id INT NOT NULL, ip VARCHAR(45) NOT NULL COMMENT IPv4或IPv6, open_ports VARCHAR(255) COMMENT 逗号分隔如 80,443,3306, alive TINYINT DEFAULT 0, KEY idx_task (task_id), CONSTRAINT fk_host_task FOREIGN KEY (task_id) REFERENCES scan_task(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE scan_vuln ( id INT AUTO_INCREMENT PRIMARY KEY, host_id INT NOT NULL, task_id INT NOT NULL, vuln_type VARCHAR(50) COMMENT sql_injection / xss / info_leak, risk_level TINYINT DEFAULT 1 COMMENT 1低危 2中危 3高危, url TEXT COMMENT 漏洞触发地址, detail TEXT COMMENT 漏洞描述和检测依据, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_task (task_id), CONSTRAINT fk_vuln_host FOREIGN KEY (host_id) REFERENCES scan_host(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;表设计里有几个细节是数据库课程设计里不太会讲、但实际项目必须处理的。第一所有外键字段都建了普通索引KEY idx_task因为按任务查报告是最高频的查询没有索引会全表扫。第二open_ports用 VARCHAR 存逗号分隔字符串严格来说这违反第一范式但毕设场景里一个主机开放的端口最多几十个用逗号串存的读取成本远低于拆一张端口表再 JOIN这是用空间换实现成本的务实选择答辩时主动解释这一点反而显得有工程判断力。第三字符集用utf8mb4而不是utf8因为有些页面内容带 emoji 或特殊符号utf8在插入长度为 4 字节的字符时会直接报错这个错在扫描内网站点时极易触发。4.2 连接池配置别让几十个线程把数据库打死前面提过要用 SQLAlchemy这里给出具体配置和理由。续写过程中填入代码from sqlalchemy import create_engine engine create_engine( mysqlpymysql://root:password127.0.0.1:3306/vulnscan?charsetutf8mb4, pool_size10, # 连接池保持 10 个连接 max_overflow5, # 超负荷时最多再开 5 个 pool_recycle3600, # 每个连接最长存活 1 小时后复用 pool_pre_pingTrue # 取连接前先探测避免拿到已断开的连接 )参数选择是有讲究的。pool_size10对应扫描线程池 50 个线程的场景因为扫描线程不是一直在写库高峰期同时写库的可能就十来个10 个基础连接配 5 个溢出连接已经足够。pool_recycle3600是防 MySQL 服务端的wait_timeout把空闲连接断掉如果连接池里的连接被数据库主动关了下一次取用直接报MySQL server has gone away这个错是网上提问最多的问题之一提前配置就能绕开。pool_pre_pingTrue是 SQLAlchemy 推荐开启的每次从池里拿连接时先执行一次轻量探测比让应用异常后重试优雅得多。4.3 批量入库用 executemany 替代逐条 insert扫描线程发现一个漏洞就写一条记录如果每个线程每次只INSERT一条几千条结果能让系统慢得肉眼可见而且 MySQL 的 binlog 和 InnoDB 的刷盘机制会让这种小事务写入放大。常见做法是在内存里累计一批结果凑够一定数量或一定时间后再批量写入。SQLAlchemy 的批量插入有现成接口from sqlalchemy import text def batch_insert_hosts(conn, task_id: int, host_results: list): host_results: [{ip, open_ports, alive}, ...]批量写入主机结果。 stmt text( INSERT INTO scan_host (task_id, ip, open_ports, alive) VALUES (:task_id, :ip, :open_ports, :alive) ) conn.execute(stmt, [ { task_id: task_id, ip: item[ip], open_ports: ,.join(map(str, item[ports])), alive: item[alive], } for item in host_results ])逻辑说明conn.execute传入一个 SQL 语句加一个字典列表SQLAlchemy 底层会将其转成 MySQL 的批量参数化插入一条网络往返提交多条数据。这个接口的细节是text()里的参数名必须和字典键完全一致写错一个在运行时才报错没有编译期检查。用这个模式以后扫描结果收集线程只需要维护一个列表列表长度到了 50 或者时间过了 2 秒就 flush 一次写库内存占用和数据库压力都可控。4.4 生成报告的入门做法拼接 HTML 字符串报告模块不一定要做得很花哨。常见做法是扫描结束后查三张表把结果渲染成 HTML。最简单的实现是模板字符串加循环拼接不引入 Jinja2 这种重量级模块毕设足够def render_report(task_id: int) - str: html_parts [] hosts query_hosts_by_task(task_id) for host in hosts: html_parts.append(fh3{host[ip]}/h3) html_parts.append(fp开放端口: {host[open_ports]}/p) vulns query_vulns_by_host(host[id]) for v in vulns: html_parts.append( fp stylecolor:red[{v[risk_level]}] {v[vuln_type]} fat {v[url]}/p ) return htmlbody .join(html_parts) /body/html这段代码不追求性能追求的是能跑、能演示、能看懂。报告的样式可以在后面用 CSS 美化毕设论文截图需要色彩对比高危漏洞标红这个细节对答辩展示很友好。如果时间充裕可以加一个按风险等级统计的饼图用 ECharts 引入一个 HTML 模板不用写后端图表逻辑。5. 毕设避坑指南5 个让漏洞扫描系统翻车的高频原因5.1 扫描速度慢到以为程序死掉了现象扫描一个 C 段地址跑了十分钟还没出第二条结果界面看起来像卡死。 原因绝大多数情况是没开并发或者把 socket 超时设成了 3 秒、5 秒效果是每个端口都要等满超时时间才放弃25 个端口乘 254 个 IP单线程跑一遍就是天文数字。 解决第一对不响应请求的 IP 先做 Ping 存活探测过滤掉大半目标第二把超时降到 1 秒并发提到 50 以上第三给扫描界面加当前正在扫描 XX / 已完成 XX的进度刷新这样哪怕慢答辩现场也不会显得像死机。诊断方法很简单看任务管理器的线程数如果线程数一直是 1那就是没走线程池。5.2 数据库连接被扫爆报 too many connections现象扫描跑到一半程序开始抛pymysql.err.OperationalError: (1040, Too many connections)扫描线程集体崩溃。 原因每个线程都新建连接、用完不关或者把pool_size设到了 50 但扫描线程有 100 个。很多数据库课程设计的习惯是用之前 connect 一次用完之后不关单线程没问题一上并发就翻车。 解决统一走 SQLAlchemy 连接池把pool_size控制在合理的 1020max_overflow设 5同时给数据库建一个监控查询出问题时先看SHOW PROCESSLIST确认是不是自己的程序把连接占满了。这个坑在答辩现场非常致命因为演示时数据库一崩溃后面报告模块全废提前用连接池能彻底规避。5.3 SQL 注入检测误报率奇高扫什么都是高危现象扫描自己的测试站几乎所有带参数的 URL 都报 SQL 注入扫描一个静态官网也报注入。 原因时间盲注的判断只有延时超过 2 秒这一条没有排除网络抖动和页面本身慢报错型检测只看响应里出现SQL syntax error字样没区分是数据库真报错还是页面文案里恰好提到了这个词。 解决时间盲注必须引入基线请求SLEEP(0)加三次重试取中位数上一章写的代码就是干这个的同时加一条测试载荷返回页面大小与基线请求一致的校验因为注入成功时的页面结构通常和正常请求相同如果页面都变了说明是别的原因。这条规则加上以后误报能降一半以上。5.4 演示视频录的时候漏洞扫描结果是空的现象答辩前录演示视频对着一个公网博客站点扫了半天结果一个漏洞都没有场面极其尴尬。 原因公网站点通常有 WAF 或云防护扫描请求被拦截就算没有防护一个做得比较规范的站点也不会那么容易测出 SQL 注入毕设扫描器用的是最基础的探测逻辑命中率本来就不高。 解决提前在本地搭一套 DVWA 靶场用 Docker 一条命令就能起把扫描目标填成http://127.0.0.1:8080DVWA 里的 SQL 注入靶场页面是必现漏洞演示效果稳定。不要扫公网站在线演示现场网络抖动不可控。这也是标题里演示视频存在的意义——录屏素材必须提前准备好而不是现场碰运气。5.5 源码发别人电脑上跑不起来现象把自己电脑上跑得好好的项目打包发给指导老师或同学对方一运行报各种找不到模块连不上数据库。 原因最常见的是没做环境隔离和配置外置。pip install的包没有整理成requirements.txt数据库的连接参数写死成root:password127.0.0.1对方 MySQL 的 root 密码不是这个init_db.sql里的建表语句顺序不对外键引用的表还没建就先建了子表。 解决项目根目录放一个requirements.txt固定所有依赖版本数据库配置放在config.py里集中管理不要散落在各个模块里提供一个init_db.py脚本启动时先连 MySQL、自动执行init_db.sql、再启动扫描引擎。这些在毕设文档里写清楚能显著减少老师在你机器上跑不了的负面评价。6. 用本地靶场把系统调到可演示答辩验证路径系统开发完以后最后的调试阶段别拿真网络练手直接用靶场做全链路验收。我自己习惯的验证路径是三条先确认数据库三张表能正常写入和关联查询再确认一个完整任务能跑通排队→扫描→完成的状态流转最后录演示视频时重点拍扫描进行中数据库行数在涨这个动态画面。有一个小技巧值得分享答辩现场如果你提前打开了靶场页面老师可能以为你在演示别人做的网站所以视频里最好把系统界面和 Navicat 的数据库刷新放在同一个画面里。具体操作是先开始扫描任务然后切到 Navicat 里对scan_host或scan_vuln表反复按刷新看到行数在几十秒内持续增加这个画面比任何代码讲解都有说服力——它直观证明了扫描器在工作、数据在落库、模块间在协作。等扫描结束后打开生成的 HTML 报告指着高亮的高危漏洞说明检测逻辑整个答辩主流程就闭环了。我这几年看下来毕设做漏洞扫描系统的翻车点从来不在写不出代码而在没想清楚给谁看、怎么看。代码写再多演示不起来就是零分数据库表设计得再规范现场拿不出数据就是白做。所以最后的建议是不要在开发期把演示视频留到最后一天才录每完成一个模块就录一小段到最后拼成完整视频你的心态会稳很多。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。