ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python漏洞扫描系统毕业设计:端口扫描到漏洞匹配全实现

Python漏洞扫描系统毕业设计:端口扫描到漏洞匹配全实现 简介这是一套基于Python实现的漏洞扫描系统毕业设计完整资源面向计算机相关专业毕业生及需要快速搭建安全类项目的学习者。系统采用Python框架结合MySQL数据库完成功能模块开发核心包括IP端口扫描、扫描结果可视化展示、用户注册登录及端口列表管理等模块从代码到演示均有覆盖。资料包共541个文件大小约91.21MB涵盖Python源码py/pyc、前端页面html/css/js、演示素材png/jpg/gif以及数据库脚本和说明文档结构完整便于按目录检索。目前已有2978人学习。通过源码与演示视频读者可快速掌握漏洞扫描系统的设计思路与实现细节同时利用配套的数据库和文档辅助毕业设计写作与答辩准备适合用作课程设计或毕设项目蓝本。1. 基于python漏洞扫描系统的毕业设计先想清楚它“四不像”在哪里毕业设计选“基于python漏洞扫描系统”这个题目听起来像要写一个攻击工具其实它是一道安全检测方向的综合题。你得把端口发现、服务识别、漏洞匹配、报告生成串成一条完整闭环交付源码、数据库和演示视频三样东西。它的目标不是“攻破哪个目标”而是证明你理解漏洞是怎么被发现的、怎么被量化的。适合信息安全、软件工程和 Python 后端方向的本科生选。真正难的不是写代码而是让整条链路在答辩那天稳定跑通并且让评委一眼看出你的工作量分布在哪。2. 漏洞扫描系统怎么设计从四步链路到技术栈取舍2.1 核心链路端口发现、服务识别、漏洞匹配、报告输出漏洞扫描系统的数据流是固定的先对目标 IP 段做端口连通性检测拿到开放端口列表再对每个开放端口发起连接读取服务软件的 Banner得到软件名称和版本然后拿版本号去漏洞库比对找出对应的 CVE 编号和风险等级最后把结果整理成一份可读报告。这四步看起来简单但每一步都有典型的翻车点。流程阶段输入输出核心难点端口发现IP、端口范围开放端口列表不能漏扫也不能太慢服务识别IP 端口服务名 版本号Banner 正则匹配不稳漏洞匹配版本号 漏洞库CVE 列表版本区间判断逻辑报告输出CVE 列表HTML / Excel风险等级可视化很多同学把时间全砸在端口扫描上等做到漏洞匹配时发现漏洞库不会建最后草草写一个文本框塞几十条假数据蒙混过关。这正好是本篇想帮你避开的。四步链路里最容易被低估的是漏洞库的设计它直接决定系统到底是不是“可演示”的。2.2 为什么用 Python 而不是 Java 或 Go这个题目用 Python 几乎是默认答案。Python 内置 socket 和 sqlite3不需要额外装包就能把端口扫描和数据库存取跑起来这对毕业设计的环境可控性非常重要。Java 写并发要引入线程池框架代码量翻倍Go 并发模型确实漂亮但很多同学到答辩前还在跟 goroutine 和 channel 纠缠。Python 生态里还有 requests、beautifulsoup 这类库做漏洞数据采集和格式化非常顺手。有一个选型陷阱要提醒不要直接调 OpenVAS 或 Nessus 的现成扫描结果。导师看到你交一个封装脚本很难给你高分因为原理部分完全黑匣子化了。常见的做法是底层用原生 socket 自己实现扫描把第三方库当成辅助工具这样论文里能写原理答辩能讲代码工作量也站得住。2.3 三层架构与文件组织让代码能讲清楚我一般会把毕设拆成数据层、扫描层、展示层三层扫描层放 socket 逻辑数据层用 SQLite 存漏洞库和扫描结果展示层输出 HTML 报告。文件组织保持简单直接别上微服务vuln_scanner/ ├── core/ # socket 扫描、Banner 提取 ├── db/ # SQLite 建表与增删改查 ├── vuln_db/ # CVE 数据导入与查询 ├── report/ # 生成 HTML 报告 ├── main.py # 入口串起整个流程 └── requirements.txt分层的好处是答辩时按层讲从底层协议到上层展示逻辑非常顺。而且每一层都可以单独写单元测试比如只测数据库模块或者只测扫描函数这也是答辩加分项。3. 用 Python 实现端口扫描与指纹识别最小可跑代码与参数调整3.1 并发端口扫描socket connect_ex ThreadPoolExecutor先写一个最小可跑的端口扫描器。用connect_ex而不是connect是因为它不会抛异常而是返回错误码0 代表端口开放其他值代表失败这对批量扫描非常友好import socket from concurrent.futures import ThreadPoolExecutor, as_completed def scan_port(ip, port, timeout1.0): 扫描单个端口返回 (端口号, 是否开放) try: sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) result sock.connect_ex((ip, port)) sock.close() return port, result 0 except socket.error: return port, False def scan_ports(ip, ports, max_workers200, timeout1.0): 并发扫描多个端口返回升序排列的开放端口列表 open_ports [] with ThreadPoolExecutor(max_workersmax_workers) as executor: future_map { executor.submit(scan_port, ip, p, timeout): p for p in ports } for future in as_completed(future_map): port, is_open future.result() if is_open: open_ports.append(port) return sorted(open_ports) if __name__ __main__: # 先扫本机再扫局域网里的靶机 target_ip 127.0.0.1 target_ports [22, 80, 443, 3306, 6379, 8080] print(scan_ports(target_ip, target_ports))这段代码逻辑是每个端口开一个线程去 connect线程数由max_workers控制。timeout是最关键的参数设 1.0 秒可以防止慢端口拖垮整个扫描但如果目标网络本身有延迟设 1.0 秒会漏掉真实开放的端口。max_workers默认 200 是因为本机文件描述符数量有限开太多线程反而会报Too many open files。只扫本机时 50 到 100 个线程足够扫虚拟机的话可以适当降到 50避免创建线程本身成为瓶颈。提示先扫 127.0.0.1 验证链路通不通再扫局域网靶机不然你分不清是代码问题还是目标网络问题。3.2 服务指纹识别从 Banner 提取服务名与版本号端口开放只是一个数字评委更关心你能否识别出端口背后跑的是什么服务。Banner 抓取是信息安全里很经典的手法建立连接后读一段服务端返回的欢迎信息再正则匹配特征串。def grab_banner(ip, port, timeout2.0): 尝试从端口读取 Banner返回原始字符串 try: sock socket.socket() sock.settimeout(timeout) sock.connect((ip, port)) # 部分服务等待客户端先说话主动发一个 HTTP 探测 if port in (80, 8080, 8000): sock.send(bHEAD / HTTP/1.0\r\n\r\n) banner sock.recv(1024).decode(errorsignore).strip() sock.close() return banner except Exception: return def parse_banner(banner): 根据 Banner 判断服务类型返回服务名与版本号 text banner.lower() if ssh in text: # 例SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.5 return ssh, extract_version(banner, openssh) if mysql in text: return mysql, extract_version(banner) if apache in text: return apache, extract_version(banner) if nginx in text: return nginx, extract_version(banner) return unknown, 这段代码的逻辑是先按服务特征做一次粗分类再调用extract_version用正则抠出版本号。这里有个实际坑很多服务不会一连接就主动发 Banner比如 HTTP 服务要等客户端先发请求。所以我在代码里加了一个HEAD / HTTP/1.0的主动探测分支。另外recv(1024)读一次不够某些服务响应对端会分批发送稳健的做法是循环读取直到读空或超时。3.3 版本号关联 CVE可解释的匹配逻辑拿到版本号之后怎么匹配漏洞如果只做字符串相等判断漏洞库里版本区间就完全没法表达。正确做法是把漏洞库拆成 组件名 受影响版本范围比如openssh 9.3p1、apache 2.4.49然后做版本号比较。比较用的不是字符串而是packaging库的Version对象from packaging.version import Version def check_vuln(component, version, vuln_db): 在漏洞库中匹配组件版本返回命中的漏洞列表 hits [] real_version version.lstrip(v) # 去掉 v1.2 这种前缀 for vuln in vuln_db: if vuln[component] ! component: continue condition vuln[affected_versions] # 例 9.3p1 op, target condition[0], condition[1:] if op and Version(real_version) Version(target): hits.append(vuln) elif op and Version(real_version) Version(target): hits.append(vuln) return hits这里我按受影响的版本号存成一条规则每一条漏洞记录对应一个 CVE 编号。毕业设计不需要把整个 NVD 库塞进去挑 30 到 50 条典型漏洞就够用了重点是匹配链路能跑通。答辩时评委问“你怎么保证漏洞库是准的”你可以回答漏洞库结构参考 CVE 官方字段演示范围聚焦在常见服务上。4. 用 SQLite 数据库做漏洞库表结构、增删改查与选型原因4.1 五张核心表任务表、主机表、端口表、漏洞库表、结果表漏洞扫描系统至少要五张表扫描任务表记录每次扫描的开始结束时间主机表记录目标 IP端口结果表记录哪些端口开放漏洞库表存 CVE 数据扫描结果表把 IP、端口、服务、CVE 关联起来。建表 SQL 可以直接用CREATE TABLE IF NOT EXISTS scan_task ( id INTEGER PRIMARY KEY AUTOINCREMENT, target TEXT NOT NULL, start_time TEXT, end_time TEXT, status TEXT DEFAULT running ); CREATE TABLE IF NOT EXISTS host_scan ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_id INTEGER NOT NULL, ip TEXT NOT NULL, open_ports TEXT ); CREATE TABLE IF NOT EXISTS vuln_db ( id INTEGER PRIMARY KEY AUTOINCREMENT, cve_id TEXT UNIQUE, component TEXT, affected_versions TEXT, severity TEXT, description TEXT ); CREATE TABLE IF NOT EXISTS scan_result ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_id INTEGER, ip TEXT, port INTEGER, service TEXT, version TEXT, cve_id TEXT, risk TEXT );scan_task的target存的是用户输入的 IP 或域名host_scan的open_ports我用了 TEXT 字段存 JSON 数组而不是拆一张端口明细表。这是毕设里很实用的取舍查询结果时少一次 join评分也不会因为表不够“范式”扣分因为你能解释清楚为什么这么设计。vuln_db的cve_id加 UNIQUE 约束是为了重复导入数据时去重。4.2 用 Python 的 sqlite3 实现漏洞库增删改查数据库模块的代码建议全部集中在一个文件里所有连接都通过contextlib.closing管理避免连接不关闭导致数据库文件被锁。这段代码给出了插入、查询两个核心操作import sqlite3 from contextlib import closing DB_PATH vulns.db def init_db(schema_sql): 初始化数据库执行建表语句 with closing(sqlite3.connect(DB_PATH)) as conn: conn.executescript(schema_sql) conn.commit() def insert_vulns(vuln_list): 批量插入漏洞记录重复 CVE 自动忽略 with closing(sqlite3.connect(DB_PATH)) as conn: conn.executemany( INSERT OR IGNORE INTO vuln_db (cve_id, component, affected_versions, severity, description) VALUES (?, ?, ?, ?, ?) , vuln_list, ) conn.commit() def query_vuln_by_component(component): 按组件名查询漏洞返回 CVE 和受影响版本 with closing(sqlite3.connect(DB_PATH)) as conn: cur conn.execute( SELECT cve_id, affected_versions, severity FROM vuln_db WHERE component ?, (component,), ) return cur.fetchall()executemany一次插入多条记录效率比循环 execute 快一个量级INSERT OR IGNORE配合 UNIQUE 约束CVE 重复时不会报错这在你反复往库里导数据时非常省心。参数绑定用问号占位符而不是拼字符串这一点要养成习惯安全评估工具本身如果写 SQL 注入那笑话就大了。数据库增删改查这个点答辩时大概率被单独提问能把closing和参数绑定的理由说出来比背概念有说服力得多。4.3 为什么毕设选 SQLite 而不是 MySQL同样是数据库课程设计里常遇到的问题很多同学习惯一上来就装 MySQL结果答辩教室的电脑上根本没有 MySQL 服务演示直接翻车。SQLite 是单文件数据库整个库就一个 vulns.dbU 盘拷走就能在任何机器上跑。MySQL 的优势在网络并发和权限管理但这套毕设是本地单机演示完全用不上。MySQL 的数据库连接池、主从同步这些生产级话题放在论文里作为“未来改进方向”提一句反而更合适。我之前见过有个同学硬上了 MySQL写连接池配置花了一个星期最后答辩时被问“为什么要用连接池”他答不上来。如果你导师一定要用 MySQL那也简单把sqlite3.connect(DB_PATH)换成pymysql.connect(host, user, password, database)SQL 基本不用动前提是你提前在答辩机器上确认 MySQL 服务能起得来。5. 避坑指南跑通基于 python 漏洞扫描系统最容易翻车的 5 个坎5.1 端口扫描结果和预期不符明明开了服务却显示关闭现象在本机启动了 Apache但扫描结果显示 80 端口关闭或者只扫出 22 端口。原因很可能是防火墙拦截了入站连接这是 Windows 和 Linux 默认行为。还有一个隐蔽原因timeout设得太短服务响应慢于连接超时上限connect 被判定失败。解决流程先用netstat -ano | findstr 80或ss -tlnp | grep 80确认端口真的在监听然后临时关闭防火墙或放行对应端口最后把timeout从 1.0 调到 2.0 再扫。这三步能排除 90% 的“扫不到”问题。5.2 虚拟机里的靶机扫不到宿主机和虚拟机网络不通现象扫 127.0.0.1 一切正常换成虚拟机的 IP 就全部超时。排查后发现是 VMware 默认 NAT 模式的问题NAT 模式下虚拟机对宿主机不可见或者反过来宿主机扫不到虚拟机这与使用哪种网络模式直接相关。解决方法是把虚拟机网络模式改成桥接并确保两台机器 IP 在同一网段。改完网络后先在宿主机上用浏览器访问虚拟机里启动的 HTTP 服务确认链路通了再去跑扫描器。不要一上来就调扫描参数网络不通时调参数是白费功夫。5.3 第三方库兼容性翻车python-nmap 报 Nmap not found现象按照 python 安装教程把 Python 装好pip install python-nmap也成功了但一运行就报Nmap not found。原因是python-nmap只是 Nmap 的 Python 封装它调用的是系统里的 Nmap 可执行文件没装 Nmap 本体就会报这个错。解决方法是单独安装 Nmap 并确认它的路径在系统 PATH 里或者干脆绕过这个库直接用内置 socket 实现扫描代码量也就多十几行。我倾向于推荐后者毕设里少一个二进制依赖答辩现场就少一个变量。Python 3.12 用户要注意部分历史库用到了已移除的 distutils装不上时不要硬刚用python3 -m venv建干净环境再装。5.4 演示现场断网在线漏洞库加载失败现象写代码时漏洞库从网上实时抓取演示教室的网络一断系统启动后漏洞库为空扫描结果全是“无漏洞”。原因很简单漏洞库获取写在了启动阶段且没有本地缓存断网就等于空库。解决方法是把 NVD 或其他来源的漏洞数据下载一次转成 JSON 后离线导入 SQLite运行时只读本地库不请求任何外网接口。这个习惯和写爬虫时要注意的抓取频率、数据落地是一样的逻辑数据先落地业务再依赖。答辩专用演示环境里断网是常见突发状况离线化之后这个坑就填平了。5.5 演示视频录制时扫描太慢观众看着想睡觉现象录制演示视频时扫 100 个端口花了 5 分钟屏幕基本静止视频画面毫无节奏感。原因很直接默认端口范围太大、timeout 太大、并发不够。解决方法是演示时固定扫 20 个高频端口timeout调成 0.5 秒max_workers保持 200这样整体扫描时间能压到几秒内。另外可以做一层结果缓存同一个 IP 的扫描结果写进数据库第二次扫描直接读库返回这不仅快还能体现你懂性能设计。录视频前先把每种目标跑一遍把耗时写下来答辩 PPT 里放一张耗时对比表效果比任何口头解释都好。6. 进阶把扫描结果生成 HTML 报告让答辩演示更稳6.1 用最少的代码生成带风险等级的 HTML 报告扫码结果不能只躺在数据库里评委要看到产出。最简单的方案是用模板字符串拼 HTML 表格不引入 jinja2 也完全够用def generate_html_report(task_id): rows query_results_by_task(task_id) html [ htmlbodyh1漏洞扫描报告/h1, table border1 cellspacing0 cellpadding6, trthIP/thth端口/thth服务/thth版本/ththCVE/thth风险/th/tr, ] for row in rows: risk_color {高: red, 中: orange, 低: green}.get(row[risk], gray) html.append( ftrtd{row[ip]}/tdtd{row[port]}/td ftd{row[service]}/tdtd{row[version]}/td ftd{row[cve_id]}/td ftd stylecolor:{risk_color}{row[risk]}/td/tr ) html.append(/table/body/html) with open(report.html, w, encodingutf-8) as f: f.write(\n.join(html))这段代码把风险等级渲染成红黄绿颜色评委扫一眼就能看懂。如果想让报告更专业后续可以换成 Flask 的模板渲染但在毕设阶段这段逻辑已经足够说明“我能把结果输出成可交付物”。6.2 答辩演示前的完整演练清单演示前一天我会按这个顺序走一遍先在本地启动一个 HTTP 服务和 MySQL 服务确保至少有一个能识别出漏洞的服务然后用固定命令扫描本机生成报告把截图存进 PPT最后把数据库文件和报告 HTML 复制一份到 U 盘万一现场电脑出问题直接放 U 盘里的演示视频兜底。我当年在这个题目上吃过亏把漏洞数据放在联网自动拉取答辩网络一断就傻眼只能对着代码干讲。后来学乖了凡是演示环境一律假设断网、缺依赖、杀毒软件拦截这三种意外都可能出现所有数据离线化所有第三方库提前装好。这个习惯一直留到现在。希望帮到你。本文还有配套的精品资源点击获取
返回列表