简介:这是一套基于Python的漏洞扫描系统毕业设计/课程设计资源包,面向计算机专业学生与网络安全学习者,帮助理解漏洞检测、端口扫描、Web安全等完整流程。项目以Python+Django实现,涉及网络探测、漏洞匹配、结果分析等核心模块,也包含SQL注入、XSS、CSRF等常见漏洞类型的检测思路,并使用扫描工具完成主机发现与端口探测,适合课程实践或毕业论文研究。压缩包共374个文件,大小74.98MB,包含Python源码及编译文件、前端页面资源、演示图片、数据库脚本和可执行程序,素材完整便于复现。包内另附安装依赖的视频教程和数据库、程序等目录,能辅助快速搭建环境、核对数据结构并运行系统。该资源已有648人学习下载,对毕业设计选题、漏洞扫描开发入门和网络安全能力提升都很有参考价值。
1. 为什么还要自己写一个漏洞扫描器:从 Nmap 到 Web 报告的最后一公里
搞过安全工作或者做过网络安全方向毕业设计的人,基本都被同一件事卡过:Nmap、OpenVAS 这些工具扫描能力很强,但结果是一堆 XML 或纯文本,拿给导师、甲方或者期末答辩评委看,根本不直观。而 Nessus 这类商业产品又贵又重,装一次够折腾半天。这份“基于 Python 的漏洞扫描系统”解决的就是这个断层——把端口扫描、服务识别、漏洞指纹匹配和 Web 报告捏合成一条完整的链路。它的核心逻辑是用 Python 自研扫描引擎,借助 Django 把检测过程变成可视化的 Web 应用,扫码即得结果。适合三类人:做毕业设计需要完整前后端项目的学生、刚入行想弄懂扫描器内部原理的安全从业者、以及手里有台 Linux 服务器想搞内网资产普查的运维。它不试图替代 Nmap,而是让你看懂一套扫描器从 socket 探活到漏洞判定的完整实现路径。
2. 先把项目跑起来:资源结构、依赖安装与初始化
拿到压缩包的第一件事,不是急着看代码,而是把目录结构摸清楚。这份资源里除了 Python 源码,还带了一份视频教程“扫描需要安装的包视频-必须操作”,名字里就写着“必须操作”,说明作者自己也清楚这项目的依赖安装环节容易翻车。目录里同时出现了 nikto.1、nikto.conf 和一堆 CSS 文件(bootstrap.css、layui.css、font-awesome.css),这透露了一个重要信息:扫描器的漏洞规则库参考了 nikto 的指纹定义,而前端直接用了 Layui 和 Bootstrap 两套框架。
2.1 先盘一下压缩包里的真实结构
解压之后,我建议你按下面的清单核对一遍文件,缺什么心里有数:
| 路径 | 类型 | 作用 |
|---|---|---|
| 扫描需要安装的包视频-必须操作 | 视频 | 依赖安装演示,建议先看一遍再动手 |
| 数据库/ | 目录 | Django 的数据库迁移文件,含模型定义 |
| 程序/ | 目录 | 扫描器核心源码,含 Django 工程和应用 |
| nikto.1 / nikto.conf | 文件 | nikto 的规则参考与格式样例,用于理解漏洞指纹写法 |
| bootstrap.css / layui.css / font-awesome.css | 静态文件 | Web 前端样式,报告页面的展示基础 |
这里有两个点要特别留意。第一,nikto.conf 不是直接给 Python 用的,它是拿来对照参考的——你需要理解 nikto 的漏洞规则是怎么组织的,然后在自己的扫描引擎里按类似结构定义检测项。第二,前端用的是 Layui 而不是纯 Bootstrap,这说明项目的管理后台和报告页面可能不是一个前端技术栈,改样式时别找错文件。
2.2 安装步骤与常见依赖项
先看视频里演示的安装顺序,再按下面的流程操作。我的建议是直接用虚拟环境,别把依赖装进系统 Python,否则后续折腾 Django 版本时会哭。
# 1. 创建并激活虚拟环境(Windows 用 venv,Linux/macOS 同样适用) python -m venv scan_env # Windows 下激活: scan_env\Scripts\activate # Linux/macOS 下激活: source scan_env/bin/activate # 2. 安装核心依赖(注意加 -i 指定国内镜像,否则可能卡在超时上) pip install django==3.2.25 -i https://pypi.tuna.tsinghua.edu.cn/simple pip install requests beautifulsoup4 sqlalchemy -i https://pypi.tuna.tsinghua.edu.cn/simple pip install python-nmap -i https://pypi.tuna.tsinghua.edu.cn/simple # 3. 进入项目目录并完成 Django 迁移 cd 程序 python manage.py makemigrations python manage.py migrate # 4. 启动 Web 服务 python manage.py runserver 0.0.0.0:8000每一条命令都有讲究。虚拟环境这一步是为了隔离项目依赖,防止 Django 版本和系统里其他项目冲突;指定清华镜像是因为 pypi 官方源在国内的连通性经常不稳定,换成镜像后下载速度能快一个量级;makemigrations 和 migrate 这两步是 Django 的固定动作,前者根据 models.py 生成迁移脚本,后者把表结构真正写入数据库——如果跳过了直接 runserver,启动时会直接报 “No migrations applied” 或者页面里全是表不存在的异常。
依赖里除了 Django,requests 是给扫描模块发 HTTP 探测用的,BeautifulSoup 负责解析响应体里的关键内容,SQLAlchemy 在报告导出和数据分析时用得上,python-nmap 则是调用本机 Nmap 做端口扫描的桥梁。
2.3 启动后的第一眼验证
服务跑起来后,浏览器访问 http://127.0.0.1:8000,应该能看到登录页或任务管理页。如果页面样式是乱的、CSS 全丢了,大概率是静态文件没有收集——执行一条python manage.py collectstatic就能解决。到这里项目就算跑通了,接下来才是重头戏:搞清楚扫描器内部在做什么。
3. 扫描引擎拆解:从端口探活到服务指纹识别
这套系统的扫描引擎不是用别人的 SDK 一包了之,而是自己实现了一套从 TCP 连接到服务判定的链路。理解这段代码,是你能在答辩里讲清楚“系统是自己写的”的关键证据。整个流程分三步:并发端口扫描、banner 抓取、服务指纹匹配,最后才是漏洞规则匹配。
3.1 端口扫描模块:socket 并发与超时控制
端口扫描是扫描器的基础层。这个项目用的是 Python 的 socket 原生实现,配合线程池做并发。看核心代码:
import socket import concurrent.futures as futures def tcp_scan_port(host, port, timeout=3): """ 对单个端口执行 TCP connect 扫描 host: 目标 IP 或域名 port: 目标端口 timeout: 连接超时时间,单位秒 """ try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) result = sock.connect_ex((host, port)) sock.close() return port, (result == 0) except socket.error: return port, False def scan_ports(host, ports, max_workers=100): """ 并发扫描多个端口 ports: 端口列表 max_workers: 线程数,控制并发力度 """ open_ports = [] with futures.ThreadPoolExecutor(max_workers=max_workers) as executor: future_map = {executor.submit(tcp_scan_port, host, p): p for p in ports} for future in futures.as_completed(future_map): port, is_open = future.result() if is_open: open_ports.append(port) return sorted(open_ports)这段代码有三个参数是关键:timeout、max_workers 和端口范围。timeout 默认设 3 秒,局域网内够用,但扫公网或者延迟高的目标时建议调到 5-6 秒,否则丢端口;max_workers 控制并发线程数,默认 100,对个人电脑和普通服务器都安全,如果扫的目标网段很大,可以调到 200,但再高就容易触发系统层面的文件描述符限制了。connect_ex 方法比 connect 好在不抛异常,而是通过返回值判断端口状态,返回值 0 代表连接成功,非 0 就是不可达。
3.2 服务指纹识别:用 banner 猜出目标在跑什么服务
端口开放只是第一步,知道端口背后跑的是什么服务才有意义。这套系统抓取服务 banner 的方式是连接成功后主动发送探测字符串,然后从响应里匹配特征。看代码:
def get_service_banner(host, port, timeout=3): """ 连接目标端口并发送探测报文,读取 banner 信息 返回格式: (端口, 服务名, 完整banner字符串) """ probes = { 80: b"GET / HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n", 22: b"SSH-2.0-OpenSSH_Probe\r\n", 443: b"GET / HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n", } default_probe = b"\r\n\r\n" try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) sock.connect((host, port)) probe = probes.get(port, default_probe).format(host=host).encode() sock.send(probe) banner = sock.recv(1024).decode("utf-8", errors="ignore").strip() sock.close() service = match_service_fingerprint(banner, port) return port, service, banner except Exception: return port, "unknown", ""探测字符串的构造是有讲究的:对 Web 端口发 HTTP GET 请求,对 SSH 端口发送客户端的版本声明,这样才能诱使服务端把版本信息吐出来。match_service_fingerprint 是一个指纹匹配函数,内部维护了一个「关键词到服务名」的映射表,比如 banner 里出现 nginx 就判定为 nginx,出现 Apache 就判定为 Apache。这个映射表在实际项目中就是对照 nikto.conf 里的服务指纹段抄出来的,不需要自己从零发明。
3.3 漏洞规则匹配:指纹命中后的判定逻辑
拿到了服务名和版本号,漏洞检测就变成了一个查表过程。系统的规则库维护着(服务名, 版本范围, CVE编号, 风险等级, 描述)这样的结构,判定逻辑如下:
def check_vulns(service, version, vuln_db): """ service: 服务名称,如 nginx version: 版本号,如 1.14.2 vuln_db: 漏洞规则库,list of dict """ hits = [] for rule in vuln_db: # rule 结构示例: # {'service': 'nginx', 'affected_versions': '<1.18.0', # 'cve': 'CVE-2021-23017', 'risk': '高危', 'desc': 'DNS 解析拒绝服务'} if rule['service'] == service and version_in_range(version, rule['affected_versions']): hits.append(rule) return hitsversion_in_range 是自己实现的版本比较函数,因为字符串形式的版本号不能直接用字典序比较,需要按照「主版本号、次版本号、修订号」逐段拆开后按整数比。这个模块是整个系统里最容易翻车的地方——很多人图省事直接拿字符串比,结果 “1.9.0” 被判定成大于 “1.10.0”,误报和漏报全来了。
4. 业务层落地:Django 模型设计、任务编排与报告生成
扫描引擎跑通了,下一步是把能力包装成 Web 应用。这个项目用 Django 做业务层,涉及三个核心模块:数据模型定义、扫描任务调度、报告展示与导出。这也是毕业设计答辩时导师最关注的环节——能不能讲清楚数据怎么流转、任务怎么管理。
4.1 数据模型设计:任务、主机、端口、漏洞四张表
数据库设计决定了整个系统能记录多少信息。这套系统最核心的模型关系是「一个扫描任务关联多个主机,一个主机关联多个开放端口,每个端口可以命中多条漏洞记录」。对应 Django 的 models.py 核心代码:
from django.db import models class ScanTask(models.Model): """扫描任务表:一次扫描的完整记录""" name = models.CharField(max_length=100, verbose_name="任务名称") target = models.CharField(max_length=255, verbose_name="扫描目标") status = models.CharField(max_length=20, default="pending", verbose_name="任务状态") created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") class Host(models.Model): """主机表:任务下扫描到的主机""" task = models.ForeignKey(ScanTask, on_delete=models.CASCADE, related_name="hosts") ip = models.GenericIPAddressField(verbose_name="主机IP") os_guess = models.CharField(max_length=100, blank=True, verbose_name="系统指纹") class PortRecord(models.Model): """端口记录表:主机上开放的端口及服务信息""" host = models.ForeignKey(Host, on_delete=models.CASCADE, related_name="ports") port = models.IntegerField(verbose_name="端口号") service = models.CharField(max_length=50, default="unknown", verbose_name="服务名称") class VulnRecord(models.Model): """漏洞记录表:命中的漏洞信息""" port = models.ForeignKey(PortRecord, on_delete=models.CASCADE, related_name="vulns") cve_id = models.CharField(max_length=20, verbose_name="CVE编号") risk_level = models.CharField(max_length=10, verbose_name="风险等级") description = models.TextField(verbose_name="漏洞描述")四张表的关系是层级递进的:ScanTask 可以包含多个 Host,一个 Host 下有多条 PortRecord,每条 PortRecord 又可以命中多个 VulnRecord。用 ForeignKey 关联的好处是可以在 Django 管理后台里一层层往下钻取数据,从任务直接点到漏洞详情,这对演示效果帮助很大。repr 里的 related_name 参数是给反向查询起名字,比如task.hosts.all()就是拿一个任务下的全部主机,命名不写写的话 Django 会自动生成<model>_set,代码可读性会差一截。
4.2 任务编排:从提交目标到扫描完成的完整流程
用户在页面上提交一个 IP 或域名后,Django 视图层要做的事是:先创建 ScanTask 记录,然后调起扫描引擎跑端口扫描、服务识别、漏洞匹配,每一步结果写入对应的表。这个过程可以是同步的——用户提交后页面等待扫描完成,也可以是异步的——任务在后台跑,用户通过刷新页面看状态。这个项目的初始版本用的同步方式,代码如下:
from django.shortcuts import render from .models import ScanTask, Host, PortRecord, VulnRecord from .scanner import scan_ports, get_service_banner, check_vulns def start_scan(request): if request.method == "POST": target = request.POST.get("target") # 1. 创建任务 task = ScanTask.objects.create(name=f"scan-{target}", target=target, status="running") # 2. 扫描常见端口 open_ports = scan_ports(target, [21, 22, 23, 25, 80, 110, 143, 443, 3306, 3389, 8080, 8443]) # 3. 创建主机记录 host = Host.objects.create(task=task, ip=target) for port in open_ports: service, banner = get_service_banner(target, port) port_obj = PortRecord.objects.create(host=host, port=port, service=service) vulns = check_vulns(service, banner, load_vuln_db()) for v in vulns: VulnRecord.objects.create(port=port_obj, cve_id=v['cve'], risk_level=v['risk'], description=v['desc']) task.status = "completed" task.save() return render(request, "result.html", {"task": task}) return render(request, "scan.html")这段代码里值得注意的细节:端口列表写死了 21、22、23 这类常见端口,这是因为扫描系统面向的教学场景,不是做全网端口普查;如果你想扫描更多端口,可以改成传入一个 range 区间。get_service_banner 返回的是二元组,但在实际调用中写的是service, banner =,这要求函数签名跟上一章的版本保持一致,如果你自己魔改过扫描模块,这里要留意解包参数的数量。
4.3 报告生成:页面渲染与 CSV 导出
报告模块是这个项目区别于纯后端扫描器的价值点。页面端用 Django 模板渲染结果,展示任务基本信息、端口开放情况、漏洞列表和风险等级。同时系统支持导出 CSV 报告,方便往毕业论文里贴数据:
import csv from django.http import HttpResponse def export_report(request, task_id): task = ScanTask.objects.get(id=task_id) response = HttpResponse(content_type="text/csv") response["Content-Disposition"] = f'attachment; filename="scan_report_{task_id}.csv"' writer = csv.writer(response) writer.writerow(["IP", "端口", "服务", "CVE编号", "风险等级", "描述"]) for host in task.hosts.all(): for port in host.ports.all(): for vuln in port.vulns.all(): writer.writerow([host.ip, port.port, port.service, vuln.cve_id, vuln.risk_level, vuln.description]) return response这个导出函数把四张表的数据横向展开成一张扁平的 CSV,每一行是一条漏洞记录。Content-Disposition 头里的 attachment 参数是告诉浏览器“这是一个下载文件”而不是“在页面里打开”。给论文配图的时候,用这个导出的 CSV 配合 Excel 做个透视表,能画出端口分布饼图和风险等级柱状图。
5. 踩坑记录与排查清单:依赖、并发、误报与数据库锁
这部分按“现象 → 原因 → 解决”的格式,整理这份资源最容易翻车的 5 个点。我拆过不少类似的毕业设计项目,这些问题百分之百会遇到,越早看到越省时间。
5.1 现象:pip install 卡在 “Building wheel” 不动
原因:部分依赖需要源码编译,而本机缺少编译工具链。最常见的是 python-nmap 安装时拉取依赖超时,或者是 Django 版本和 Python 版本不匹配——比如 Python 3.11 配 Django 2.2 就有兼容问题。
解决:先确认 Python 版本是 3.8 或 3.9,然后统一用清华源安装全部依赖。如果某个包编译失败,到 https://pypi.org/project/ 看这个包是否有 Windows 预编译的 wheel 文件,有的话直接下载 whl 文件本地安装。
5.2 现象:runserver 启动成功,页面却报 “table doesn’t exist”
原因:makemigrations 只生成了迁移文件,没有执行 migrate 把表建进数据库。很多人执行到第二步就停了,结果页面访问任何模型相关的数据都直接报 500 或 OperationalError。
解决:在项目目录下执行python manage.py migrate,如果之前已经启动过服务导致半中间状态,先删掉项目下的 db.sqlite3 文件再重新 migrate——反正初始环境没什么值钱的数据。
5.3 现象:扫同一台机器,结果和 Nmap 不一致,总是少端口
原因:自带的常见端口列表只有 12 个,而目标服务器的服务可能跑在 8001、9000、6379 这类非主流端口上,端口列表覆盖不到。另一个原因是 timeout 设太短,高延迟网络下连接握手超过了 3 秒就被判定为超时过滤掉了。
解决:去start_scan视图里把端口列表改成一个 range,比如range(1, 1024)先跑一遍全扫,确认哪些端口是稳定的,再用精简列表做后续重复扫描。timeout 调大到 5 秒,同时把 max_workers 降到 50,减少网络抖动的影响。
5.4 现象:漏洞判定全是“未知”或“无漏洞”,但 Nmap 能查到
原因:漏洞规则库太薄了。资源的规则库只覆盖了几十个常见 CVE 条目,服务版本只要不在库里的受影响范围内,就会被判成“无漏洞”。Nmap 的 NSE 脚本库有几千条检测脚本,自然查得多。
解决:要么自己往漏洞数据库里补 CVE 和受影响版本范围,要么去 CVE 官网下载公开的 CPE 数据,用脚本导入到系统的规则表里。补数据的格式照着 check_vulns 接收的 dict 结构写就行。
5.5 现象:数据库频繁报 “database is locked”
原因:SQLite 只允许一个进程写数据。扫描任务跑起来后,多线程并发写 PortRecord 和 VulnRecord,再加上用户同时打开了报告页面触发读操作,SQLite 就会锁库。
解决:有两个方案。第一个是暴力方案,在数据库连接参数里加timeout=30,让写入等待锁释放——但这个方案对长时间扫描依然不保险。第二个是规范方案,把扫描任务的写入改成单线程串行,也就是在 views 里先把扫描结果收集到内存列表,扫描全部结束再一次性写入数据库,这样只发生一次写操作,锁冲突概率大大降低。
6. 验证与扩展:用本地靶机跑一遍,再往实战方向改造
项目跑通不等于能用。我验证这类扫描器有一个固定套路:本地起一台 Metasploitable 2 或者 DVWA 靶机,用它当目标,跑完整扫描,拿结果对照 Nmap 的输出。这比直接扫外网安全得多,也不会触碰到授权边界。
6.1 靶机验证的操作方法
VMware 里导入 Metasploitable 2 镜像,网络模式设为仅主机模式,这样宿主机可以直接访问靶机 IP,又不会暴露给物理网络。然后在系统的扫描页面填靶机 IP,提交任务,观察扫描结果:
- 172.16.56.128 这台靶机默认开了 21、22、23、25、80、3306 等端口,扫出来应该和靶机的服务清单一一对应;
- 系统如果命中 vsftpd 2.3.4 的 CVE-2011-2523,会记一条高危漏洞记录——这个 CVE 在 Metasploitable 2 里是标配,验证规则库是否有能力覆盖。
对照方法是nmap -sV -p 1-1000 <靶机IP>,把 Nmap 识别的服务版本和系统的 banner 判定结果放在一起比。如果出现版本号不一致,优先检查系统抓 banner 用的探测字符串是不是被目标服务器的防火墙或者特殊配置干扰了。
6.2 三个扩展方向,建议按需选做
验证通过之后,如果想让这个项目从“毕业设计作品”变成“自用工具”,以下几个扩展点是高性价比的:
第一,接外部 CVE 数据源。抓 CIRCL CVE API 或 NVD 的 JSON 数据,定期同步到本地漏洞库,避免规则停留在 2020 年之前的旧数据上。第二,把同步扫描改成异步任务。用 Celery 或简单的threading.Thread把扫描放到后台跑,页面立即返回“任务已创建”,用户刷新查看进度,体验提升非常明显——代价是需要处理线程安全,建议把 SQLite 换成 PostgreSQL。第三,生成 HTML 格式报告。CSV 导出只是勉强够用,写一个report.html模板,把扫描结果渲染成带表格和颜色标记的完整报告,论文附录可以直接截图用。
我从那次拆解起养成一个习惯:任何扫描器项目拿到手,第一件事不是读代码,而是先起一台靶机跑一遍全流程,确认扫描链路是通的,再去看具体实现。这不是浪费时间,而是给自己建立一条验证基线——项目能不能用,跑一遍全知道了。希望这份拆解能帮你省掉几个晚上的踩坑时间。
本文还有配套的精品资源,点击获取