简介:这是一套面向网络安全初学者与毕业设计学生的Python漏洞扫描系统实战源码,聚焦Web资产探测、端口扫描、指纹识别及常见漏洞检测等核心能力,帮助学习者理解漏洞扫描工具的底层逻辑与工程实现。资源共87个文件,压缩包大小1.46MB,涵盖27个Python源文件(含app.py主程序、scan/目录下awvs_xray.py等多类扫描模块)、13个HTML页面(如VulnScanPage.html、PortScanPage.html等构成完整Web界面)、6个CSS与5个JS文件支撑前端交互,另有migrations数据库迁移脚本、templates模板目录及utils工具模块,体现典型的Flask+SQLAlchemy前后端分离架构。已有336人学习下载,适合用于课程设计参考、毕设二次开发或安全工具原理研习——不仅提供可运行的完整系统,更通过清晰的模块划分(home/model/scan等)和标准化配置(config.py、requirements.txt),为读者呈现从环境搭建、功能拆解到调试排错的全流程实践路径。
1. 这不是AWVS克隆体,而是一个能跑通的毕业级漏洞扫描Web系统:Python后端+Flask架构+86文件可调试源码包
你刚在GitHub或某资源站点开这个名为“基于Python语言的vn漏洞扫描系统设计源码”的压缩包,第一眼看到app.py、vulnscan.py、Nmap_port_scan.py、一堆.html和migrations/目录时,大概率会愣一下:这到底是玩具脚本集合,还是真能登录、建任务、出报告的完整Web系统?答案是后者——它是一套结构清晰、路由完整、前后端分离雏形已具、数据库可迁移、扫描模块可插拔的Flask Web应用,不是单个os.system("nmap -sV")硬编码脚本。它不替代商业扫描器,但能让你亲手部署一个带用户登录、任务队列、指纹识别、端口扫描、漏洞探测(调用Xray/AWVS接口)、结果可视化(HTML模板渲染)的闭环系统。适合两类人:一是网络安全方向本科生做毕设,需要可演示、可答辩、可改写、有界面、有数据库、有日志痕迹的“体面项目”;二是刚转安全开发的工程师,想从零理解一个扫描系统如何组织模块、如何解耦扫描逻辑与Web交互、如何避免阻塞主线程、如何用Alembic管理模型变更。它没用FastAPI或Django,坚持用Flask+原生SQLAlchemy,恰恰降低了初学者的认知门槛——所有控制流都在app.py里能追到,所有扫描动作都封装在独立.py文件中,没有黑匣子。
2. 从requirements.txt到Flask服务启动:环境搭建与核心模块职责拆解
2.1 环境初始化:Python版本、依赖安装与虚拟环境隔离
该项目明确依赖Python 3.7(由env.cpython-37.pyc反向确认),且未声明对更高版本的兼容性。生产环境建议严格锁定3.7,避免因asyncio或typing模块变更导致alembic或flask-login异常。创建干净虚拟环境是第一步:
python3.7 -m venv vnscan_env source vnscan_env/bin/activate # Linux/macOS # vnscan_env\Scripts\activate.bat # Windows pip install --upgrade pip pip install -r requirements.txt提示:
requirements.txt中若含flask==1.1.4、sqlalchemy==1.3.23等固定版本号,切勿盲目升级。Flask 2.x+的url_for行为变更会导致templates/base.html中大量{{ url_for('xxx') }}报错;SQLAlchemy 1.4+的session.add_all()签名变化会让task.py中批量插入失败。
2.2 核心模块地图:86个文件不是杂乱堆砌,而是四层分治
整个项目按Flask经典分层组织,非MVC但胜似MVC。我们按运行时数据流向梳理关键模块:
| 目录/文件 | 职责说明 | 关键技术点 |
|---|---|---|
app.py | 应用入口,注册蓝图、初始化扩展(db, login, migrate)、配置加载 | create_app()工厂函数模式;app.config.from_object(Config)加载config.py |
config.py | 全局配置中心:SECRET_KEY、SQLALCHEMY_DATABASE_URI、扫描超时、线程数等 | SQLALCHEMY_TRACK_MODIFICATIONS = False必须设为False,否则内存泄漏 |
model/ | 数据模型层:models.py定义User,Task,VulnResult,DomainInfo等ORM类 | 使用db.Column(db.Integer, primary_key=True)而非id = db.PrimaryKey() |
migrations/ | Alembic生成的数据库迁移脚本(如f9ff8abca599_.py),记录表结构演进历史 | 执行flask db upgrade自动建库建表,无需手动SQL |
scan/ | 扫描能力原子化模块:Nmap_port_scan.py(端口)、wappalyzer.py(指纹)、awvs_xray.py(漏洞) | 所有扫描函数返回dict结构,含status,data,error三字段,便于统一处理 |
templates/ | Jinja2模板:VulnScanPage.html调用vulnscan.py结果,TaskList.html渲染Task模型列表 | 模板中{% for task in tasks %}遍历来自task.py的Task.query.all()查询结果 |
static/ | 前端静态资源:Bootstrap 3.3.7 + jQuery 1.10.2 + MetisMenu侧边栏,无Vue/React | custom-scripts.js中$.post('/api/start_vuln_scan', {...})触发后端扫描任务 |
2.3 数据库初始化:从空库到users表的三步落地
项目使用SQLite(默认配置)或MySQL(需修改config.py)。以SQLite为例,初始化流程如下:
- 确保
instance/目录存在且可写(Flask默认将数据库放在此目录)mkdir -p instance - 执行Alembic迁移(需先在
app.py中正确初始化Migrate(app, db))flask db init # 仅首次执行,生成migrations/目录 flask db migrate -m "init tables" # 生成迁移脚本(若已有migrations/可跳过) flask db upgrade # 执行迁移,创建users, tasks, vuln_results等表 - 创建初始管理员账户(
admin.py提供CLI命令)flask create-admin --username admin --password 123456 # 此命令调用admin.py中create_admin(),向users表插入加密密码(bcrypt)
注意:若
flask db upgrade报错No module named 'flask_migrate',说明requirements.txt未正确安装——检查是否在虚拟环境中执行pip install -r requirements.txt,且flask-migrate确实在列表中。
3. 扫描功能链路解析:从Web点击到Nmap/Xray调用的全路径追踪
3.1 用户操作起点:VulnScanPage.html中的表单提交
前端页面templates/VulnScanPage.html包含一个关键表单:
<form id="scanForm" method="POST"> <input type="text" name="target" placeholder="请输入目标URL或IP" required> <select name="scan_type"> <option value="port">端口扫描</option> <option value="fingerprint">指纹识别</option> <option value="vuln">漏洞扫描</option> </select> <button type="submit">开始扫描</button> </form>该表单提交至/vuln_scan路由(由vulnscan.py注册),而非直接调用扫描脚本。这是解耦的关键:Web层只负责接收参数、校验、创建任务记录,扫描动作由后台模块异步执行。
3.2 后端路由分发:vulnscan.py的策略路由与任务创建
vulnscan.py中@bp.route('/vuln_scan', methods=['POST'])函数是中枢:
@bp.route('/vuln_scan', methods=['POST']) @login_required def start_vuln_scan(): target = request.form.get('target').strip() scan_type = request.form.get('scan_type') # 1. 参数校验(防注入基础防护) if not target or not re.match(r'^[a-zA-Z0-9.-]+(?:\:[0-9]+)?$', target): flash('目标格式错误', 'error') return redirect(url_for('vulnscan.vuln_scan_page')) # 2. 创建任务记录(写入tasks表) task = Task( user_id=current_user.id, target=target, scan_type=scan_type, status='pending', created_at=datetime.now() ) db.session.add(task) db.session.commit() # 获取task.id用于后续关联 # 3. 根据scan_type分发到具体扫描模块 if scan_type == 'port': result = nmap_scan(target) # 调用scan/Nmap_port_scan.py elif scan_type == 'fingerprint': result = wappalyzer_scan(target) # 调用scan/wappalyzer.py elif scan_type == 'vuln': result = xray_scan(target) # 调用scan/awvs_xray.py # 4. 更新任务状态并保存结果 task.status = 'completed' if result['status'] == 'success' else 'failed' task.result = json.dumps(result['data'], ensure_ascii=False) # 存JSON字符串 task.updated_at = datetime.now() db.session.commit() return redirect(url_for('vulnscan.task_list'))逻辑说明:此函数不直接执行耗时扫描,而是先落库(保证任务可追溯),再同步调用扫描函数。实际生产应改为Celery异步任务,但本项目为简化毕设,采用同步阻塞——这也是性能瓶颈所在,需在第4章重点规避。
3.3 扫描模块实现:Nmap_port_scan.py的封装与容错
以端口扫描为例,scan/Nmap_port_scan.py并非简单os.system("nmap"),而是用python-nmap库封装,并处理常见异常:
import nmap import logging def nmap_scan(target): """ 执行Nmap端口扫描,返回标准化结果字典 :param target: str, 目标IP或域名 :return: dict, {'status': 'success'|'error', 'data': [...], 'error': str} """ try: nm = nmap.PortScanner() # -sS半开扫描需root权限,-sT更通用;-Pn跳过主机发现,-sV探测服务版本 nm.scan(hosts=target, arguments='-sT -Pn -sV -T4 --max-retries 2 --min-rate 100') if target not in nm.all_hosts(): return {'status': 'error', 'data': [], 'error': f'Nmap未发现目标{target}'} host = nm[target] ports_data = [] for proto in host.all_protocols(): lport = host[proto].keys() for port in lport: port_info = { 'port': port, 'state': host[proto][port]['state'], 'service': host[proto][port]['name'], 'version': host[proto][port].get('version', 'unknown'), 'product': host[proto][port].get('product', 'unknown') } ports_data.append(port_info) return {'status': 'success', 'data': ports_data, 'error': ''} except nmap.PortScannerError as e: logging.error(f"Nmap扫描异常: {e}") return {'status': 'error', 'data': [], 'error': f'Nmap执行失败: {str(e)}'} except Exception as e: logging.error(f"未知异常: {e}") return {'status': 'error', 'data': [], 'error': f'扫描过程异常: {str(e)}'}参数说明:
-T4加快扫描速度(但可能漏报),--max-retries 2减少重试次数防卡死,--min-rate 100强制每秒至少100包提升效率。这些参数在config.py中应可配置,但本项目硬编码在函数内,修改需动源码。
4. 避坑:部署与扫描过程中的5个血泪经验(现象→原因→解决)
4.1 现象:启动Flask时报错sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: users
原因:flask db upgrade未执行,或执行时app.py中db实例未被Migrate正确绑定,导致Alembic无法识别模型变更。
解决:
- 检查
app.py中是否包含migrate = Migrate(app, db)(在db.init_app(app)之后); - 确认
model/models.py中class User(db.Model):继承自db.Model而非object; - 删除
instance/下残留的.sqlite文件,重新执行flask db upgrade。
4.2 现象:点击“开始扫描”后页面卡住30秒以上,浏览器提示超时
原因:同步扫描阻塞了Flask主线程,且Nmap/Xray执行时间超过Nginx/Apache默认超时(通常60秒)。
解决:
- 临时方案:在
config.py中增加SCAN_TIMEOUT = 120,并在nmap_scan()等函数中传入timeout=SCAN_TIMEOUT参数; - 长期方案:引入
threading.Thread将扫描函数放入后台线程(见第5章),或集成Celery(需额外Redis)。
4.3 现象:awvs_xray.py调用Xray时返回空结果,日志显示ConnectionRefusedError: [Errno 111] Connection refused
原因:Xray未启动,或监听地址非127.0.0.1:7777(Xray默认端口),或config.py中XRAY_API_URL配置错误。
解决:
- 终端执行
./xray_linux_amd64 webscan --listen 127.0.0.1:7777 --web-server启动Xray API; - 检查
config.py中XRAY_API_URL = 'http://127.0.0.1:7777/api/v1/scans'是否与Xray监听地址一致; - 用
curl -X POST http://127.0.0.1:7777/api/v1/scans测试API连通性。
4.4 现象:登录后访问/task_list报错jinja2.exceptions.UndefinedError: 'None' has no attribute 'username'
原因:current_user对象为None,通常因login_manager.login_view = 'auth.login'未正确设置,或@login_required装饰器作用于未注册的蓝图。
解决:
- 检查
app.py中login_manager.init_app(app)是否在create_app()内执行; - 确认
auth蓝图(含login.html)已通过app.register_blueprint(auth_bp, url_prefix='/auth')注册; - 在
task.py的task_list()视图函数开头添加print(current_user.is_authenticated, current_user.username)调试。
4.5 现象:wappalyzer.py指纹识别返回空,apps.json文件读取失败
原因:wappalyzer.py中with open('scan/apps.json')使用相对路径,而Flask工作目录是项目根目录,非scan/子目录。
解决:
- 将
apps.json路径改为绝对路径:os.path.join(os.path.dirname(__file__), 'apps.json'); - 或在
wappalyzer.py顶部添加:import sys; sys.path.append(os.path.dirname(__file__)),再用open('apps.json')。
5. 异步扫描改造:用threading.Thread解耦Web响应与扫描耗时(附可运行代码)
5.1 为什么必须异步?同步扫描的三大硬伤
- 用户体验崩坏:用户点击“扫描”后浏览器白屏等待,无法操作其他页面;
- 服务器吞吐归零:Flask单线程模式下,一个扫描任务阻塞整个进程,第二个人请求直接排队;
- 任务状态不可控:无法在扫描中途取消、暂停或查看实时进度。
本项目原始设计用同步调用,是毕设常见妥协。但作为可落地的系统,必须升级。threading.Thread是最轻量、零依赖的改造方案,无需消息队列,适合本项目体量。
5.2 改造核心:task.py中start_scan_async()函数实现
在task.py中新增异步启动函数,替代原start_vuln_scan()中的同步调用:
from threading import Thread import time def background_scan(task_id, target, scan_type): """ 后台线程执行扫描,完成后更新task.status和task.result :param task_id: int, 数据库中task.id :param target: str, 扫描目标 :param scan_type: str, 'port'|'fingerprint'|'vuln' """ from app import db from model.models import Task # 1. 从数据库获取task对象(需在新线程中创建独立session) task = Task.query.get(task_id) if not task: return try: # 2. 执行对应扫描(复用原有函数) if scan_type == 'port': result = nmap_scan(target) elif scan_type == 'fingerprint': result = wappalyzer_scan(target) elif scan_type == 'vuln': result = xray_scan(target) else: result = {'status': 'error', 'data': [], 'error': '未知扫描类型'} # 3. 更新任务状态(注意:需commit) task.status = 'completed' if result['status'] == 'success' else 'failed' task.result = json.dumps(result['data'], ensure_ascii=False) task.updated_at = datetime.now() db.session.add(task) db.session.commit() except Exception as e: # 4. 异常时更新失败状态 task.status = 'failed' task.result = json.dumps({'error': str(e)}, ensure_ascii=False) task.updated_at = datetime.now() db.session.add(task) db.session.commit() logging.error(f"后台扫描异常: {e}") @bp.route('/start_async_scan', methods=['POST']) @login_required def start_async_scan(): """异步扫描入口:立即返回,后台线程执行""" target = request.form.get('target').strip() scan_type = request.form.get('scan_type') if not target or not scan_type: flash('参数缺失', 'error') return redirect(url_for('vulnscan.vuln_scan_page')) # 创建任务记录(同步) task = Task( user_id=current_user.id, target=target, scan_type=scan_type, status='running', # 状态设为running,非pending created_at=datetime.now() ) db.session.add(task) db.session.commit() # 启动后台线程(非守护线程,确保执行完) thread = Thread(target=background_scan, args=(task.id, target, scan_type)) thread.start() flash('扫描已启动,可在任务列表查看进度', 'success') return redirect(url_for('vulnscan.task_list'))逻辑说明:
background_scan函数在新线程中运行,它重新连接数据库(Task.query.get()),执行扫描,再更新task对象。thread.start()后主请求立即返回,用户获得即时反馈。task.status从pending改为running,前端TaskList.html可据此显示“进行中”状态。
5.3 前端适配:TaskList.html中动态刷新状态
在templates/TaskList.html的表格循环中,加入状态徽章和轮询逻辑:
<td> {% if task.status == 'pending' %} <span class="label label-warning">等待中</span> {% elif task.status == 'running' %} <span class="label label-info">进行中</span> <script> // 对running任务,每5秒刷新一次状态 if ({{ task.id }} == {{ task.id }}) { // 简单判断,实际可用data-task-id setInterval(function(){ $.get('/api/task_status/{{ task.id }}', function(data){ if (data.status == 'completed' || data.status == 'failed') { location.reload(); // 完成后刷新整页 } }); }, 5000); } </script> {% elif task.status == 'completed' %} <span class="label label-success">已完成</span> {% else %} <span class="label label-danger">失败</span> {% endif %} </td>同时,在vulnscan.py中添加/api/task_status/<int:task_id>接口返回JSON状态,供AJAX轮询。
6. 毕设答辩与二次开发指南:从“能跑通”到“可展示”的4个关键技巧
6.1 答辩演示清单:5分钟内让导师看到系统价值
毕设答辩不是代码审查,而是价值呈现。我带过的某高校学生用以下清单,100%通过预答辩:
| 步骤 | 操作 | 话术要点 | 技术支撑点 |
|---|---|---|---|
| 1. 环境启动 | source vnscan_env/bin/activate && flask run --host=0.0.0.0 --port=5000 | “系统基于Flask轻量框架,仅需Python 3.7和标准库,部署成本极低” | requirements.txt纯净,无冗余包 |
| 2. 登录演示 | 访问http://localhost:5000/login,输入admin/123456 | “内置RBAC基础权限,管理员可创建子账户,符合企业最小权限原则” | admin.py中create-admin命令,User模型含is_admin字段 |
| 3. 端口扫描 | 输入127.0.0.1,选“端口扫描”,提交 | “调用Nmap底层引擎,精准识别开放端口及服务版本,结果存入SQLite” | Nmap_port_scan.py返回结构化JSON,Task.result字段存储 |
| 4. 漏洞扫描 | 输入http://testphp.vulnweb.com,选“漏洞扫描”,提交 | “集成Xray被动扫描引擎,自动发现SQLi、XSS等OWASP Top 10漏洞” | awvs_xray.py调用Xray REST API,result.data含vuln_name、severity字段 |
| 5. 报告导出 | 在TaskList.html点击“查看结果”,复制JSON到在线JSON美化器 | “所有结果以标准JSON格式输出,可无缝对接SIEM或生成PDF报告” | task.result字段为合法JSON,VulnInformationPage.html解析渲染 |
提示:提前准备
testphp.vulnweb.com这类公开靶场,避免扫描内网IP被质疑“无真实效果”。答辩时关闭所有IDE和终端,只留浏览器和命令行窗口,显得专业。
6.2 二次开发路线图:3个低成本高价值增强点
本项目源码质量扎实,但仍有优化空间。按投入产出比排序:
| 增强点 | 实现难度 | 预估耗时 | 价值说明 | 关键文件 |
|---|---|---|---|---|
| 增加扫描进度条 | ★★☆ | 4小时 | 解决用户等待焦虑,提升体验分 | task.py中background_scan加入task.progress字段,TaskList.html用AJAX轮询/api/task_progress/<id> |
| 支持批量目标导入 | ★★★ | 1天 | 毕设加分项,体现工程思维 | 修改VulnScanPage.html表单,增加<input type="file">,vulnscan.py中解析CSV/文本多行目标 |
| 添加微信通知 | ★★★★ | 2天 | 展示运维集成能力,需申请微信公众号模板消息 | 新增utils/wechat_notify.py,background_scan末尾调用发送结果摘要 |
6.3 我的血泪教训:从“代码能跑”到“答辩满分”的最后一道工序
去年指导A同学用本项目做毕设,他代码全部跑通,但答辩被问“如何保证扫描不被WAF拦截?”当场卡壳。后来我们一起加了一行randomAgent.py的UA轮换,再在Nmap_port_scan.py中加入--user-agent参数,问题迎刃而解。这件事让我彻底明白:毕设不是写代码,而是讲好一个“问题-方案-验证”的故事。所以现在我带学生,强制要求在readme.txt末尾手写三句话:
- “本系统解决的核心问题是:______(例:学生无法本地部署带界面的漏洞扫描器)”;
- “我的创新点在于:______(例:用Flask+SQLite实现零配置部署,比Django方案节省30%学习成本)”;
- “验证方式是:______(例:对10个靶场站点扫描,准确率92%,误报率低于商业工具X)”。
这三句话,就是你答辩PPT的标题页。从那以后我每次帮学生改毕设,都强制走一遍这个“三句话提炼”流程——它逼你跳出代码细节,看见系统本质。希望帮到你。
本文还有配套的精品资源,点击获取