
简介这是一份基于Django的多功能Web安全渗透测试系统毕业设计项目主要面向计算机相关专业的高校学生适用于毕业设计、课程设计、期末大作业以及Python Web安全方向的实战练习。项目经导师指导并获高分代码完整、可直接运行即使基础较弱也能按说明完成部署与演示。资源包共2000个文件以275个Python源码文件、1325个SVG图标、71个CSS、67个JS、34个HTML为主体同时包含SQLite数据库、GeoIP库、日志与字体等整体16.88MB前端采用Bootstrap和Xenon/Tabler管理模板目录结构清晰。当前已有46人学习下载属于人气逐步上升的优质毕业设计资源。除可运行的完整系统外另附使用说明与全部配套资料便于理解整体架构、模块划分、安全检测流程及Django项目配置方法是完成毕业设计或构建安全测试演示系统的高性价比参考。1. 基于Django的Web渗透测试系统课设选题为什么选它做毕业设计或课程设计时安全类选题最容易踩两个坑要么只做前端界面后台全是mock数据要么堆一堆工具脚本没有Web化管理界面答辩时讲不出工程化的东西。基于Django的多功能Web安全渗透测试系统恰好把两端都补上了——用Django做任务调度、结果展示和报告生成用底层模块实现子域枚举、端口扫描、SQL注入与XSS检测构成一条完整的“目标录入→扫描任务→漏洞入库→报告导出”链路。这套设计贴合企业安全测试平台类似极光扫描器或AWVS的最小可行实现既有安全领域的深度又有Django Web开发的工程量对找工作的项目经验也是一个能讲透的亮点。源码里附带使用说明和完整项目资料按步骤跑起来就能演示适合计算机相关专业的学生以及想上手安全开发方向的Python学习者。2. 系统架构与Django应用拆分2.1 MVT模式下的功能模块划分这个系统的业务流可以抽象成三块目标管理、扫描执行、结果展示。对应到Django的MVT架构里Model层承载扫描目标、任务记录、漏洞结果三类核心数据View层负责接收前端请求并调度扫描任务Template层用Bootstrap等前端资源搭建操作台界面。项目里能看到xenon.css、tabler.css、bootstrap.css这类样式文件说明前端采用的是现成的后台管理模板开发重心放在业务逻辑而不是审美调优这也是课设项目控制工期的常见策略。模块拆分我建议按Django app的维度来组织而不是把所有逻辑塞进一个app否则后续加功能会很难受project_name/ ├── manage.py ├── config/ # 项目配置settings、urls、wsgi ├── users/ # 用户认证模块 ├── targets/ # 扫描目标管理域名、IP录入 ├── scanner/ # 扫描引擎子域枚举、端口扫描、漏洞检测 ├── tasks/ # 异步任务调度Celery任务定义 └── reports/ # 报告生成PDF、HTML导出这样的结构下每个app承担单一职责scanner里只写扫描逻辑不写数据库操作tasks只做任务调度不写业务规则。后面做单元测试或替换扫描模块时影响面被限制在一个app内部这比把所有视图函数堆在views.py里要安全得多。2.2 核心数据模型ScanTask与VulnResult数据模型是整个系统能不能撑住“多功能”定位的关键。我的建议是至少设计四张核心表ScanTarget存目标信息ScanTask存每一次扫描的任务记录VulnResult存检测出的漏洞详情ScanReport存报告文件路径。下面是核心模型的参考实现# scanner/models.py from django.db import models from django.contrib.auth.models import User class ScanTarget(models.Model): 扫描目标 name models.CharField(max_length255, verbose_name目标名称) target_url models.URLField(max_length500, verbose_name目标URL) target_type models.CharField(max_length20, choices( (domain, 域名), (ip, IP地址), (url, 单条URL), ), defaultdomain, verbose_name目标类型) owner models.ForeignKey(User, on_deletemodels.CASCADE, nullTrue, blankTrue) created_at models.DateTimeField(auto_now_addTrue) description models.TextField(blankTrue, verbose_name备注) class Meta: db_table scan_target verbose_name 扫描目标 class ScanTask(models.Model): 扫描任务 STATUS_CHOICES ( (pending, 等待中), (running, 扫描中), (completed, 完成), (failed, 失败), ) target models.ForeignKey(ScanTarget, on_deletemodels.CASCADE, related_nametasks) task_type models.CharField(max_length50, verbose_name扫描类型, help_textsubdomain/port/sqlmap/xss) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultpending) progress models.IntegerField(default0, verbose_name进度百分比) created_at models.DateTimeField(auto_now_addTrue) finished_at models.DateTimeField(nullTrue, blankTrue) logs models.TextField(blankTrue, verbose_name扫描日志) class Meta: db_table scan_task ordering [-created_at] class VulnResult(models.Model): 漏洞结果 SEVERITY_CHOICES ( (high, 高危), (medium, 中危), (low, 低危), (info, 信息), ) task models.ForeignKey(ScanTask, on_deletemodels.CASCADE, related_namevulns) name models.CharField(max_length255, verbose_name漏洞名称) url models.URLField(max_length500, verbose_name漏洞URL) severity models.CharField(max_length20, choicesSEVERITY_CHOICES) description models.TextField(verbose_name漏洞描述) evidence models.TextField(blankTrue, verbose_name证据数据) suggested_fix models.TextField(blankTrue, verbose_name修复建议) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table vuln_result indexes [ models.Index(fields[severity], nameidx_vuln_severity), ]代码逻辑说明ScanTask里的task_type字段决定了这次任务跑哪个检测模块status字段配合后文要讲的Celery异步任务做状态流转。VulnResult独立成表的好处是一个扫描任务可以产生多条漏洞记录反向查询通过related_namevulns直接拿到不需要额外写复杂的连表查询。参数说明on_deletemodels.CASCADE表示删除目标时级联清理关联任务和漏洞避免脏数据堆积db_table显式指定表名便于后期SQL审计或者对接外部数据平台indexes在severity字段上建索引因为按漏洞等级筛选是报告页最频繁的操作。2.3 用户权限与操作审计渗透测试系统比普通业务系统更注重权限边界不能让任意注册用户随便扫描任意目标否则容易被滥用。这里采用两层控制第一层是Django自带的认证系统登录后才有操作入口第二层是数据隔离普通用户只能看到和删除自己创建的目标任务。# targets/views.py from django.contrib.auth.decorators import login_required from django.core.exceptions import PermissionDenied from django.shortcuts import get_object_or_404 login_required def target_detail(request, target_id): target get_object_or_404(ScanTarget, idtarget_id, ownerrequest.user) tasks target.tasks.all()[:10] return render(request, targets/detail.html, {target: target, tasks: tasks})这段视图通过ownerrequest.user做了查询过滤即使攻击者手动拼URL里的target_id也拿不到别人的数据。login_required装饰器保证未登录请求会重定向到登录页。这类权限校验逻辑在每个操作类视图中都要写不能省答辩时这也是一个很好的技术加分点可以主动讲“我做了越权数据访问防护”。3. 扫描引擎与漏洞检测模块实现3.1 子域枚举基于字典与DNS解析子域枚举是信息收集的第一步常见做法是借助字典文件拼接候选子域再通过DNS解析判断域名是否存在。Python里可以用socket.gethostbyname也可以用dnspython库做更精细的解析。下面是一个带并发控制的实现# scanner/subdomain.py import socket import concurrent.futures def load_subdomain_dict(path: str) - list: 加载子域名字典 with open(path, r, encodingutf-8) as f: return [line.strip() for line in f if line.strip() and not line.startswith(#)] def check_subdomain(domain: str, prefix: str) - tuple: 校验单个子域名是否存在 sub f{prefix}.{domain} try: ip socket.gethostbyname(sub) return (sub, ip, True) except socket.gaierror: return (sub, None, False) def run_subdomain_enum(domain: str, dict_path: str, max_workers: int 20) - list: 并发枚举子域 results [] prefixes load_subdomain_dict(dict_path) with concurrent.futures.ThreadPoolExecutor(max_workersmax_workers) as executor: future_map {executor.submit(check_subdomain, domain, p): p for p in prefixes} for future in concurrent.futures.as_completed(future_map): sub, ip, exists future.result() if exists: results.append({subdomain: sub, ip: ip}) return results逻辑说明ThreadPoolExecutor把字典里的每个前缀丢到线程池里并发解析max_workers控制并发数避免一次性发起太多DNS请求被目标服务器的防火墙封IP。as_completed方法会在任意一个future完成时立即返回结果而不是等待全部完成这样整体耗时约等于最慢的单次解析时间。参数说明max_workers20在公网DNS解析场景下是一个比较温和的并发值如果扫描的是内网环境可以降到510socket.gethostbyname收到gaierror异常说明域名不存在或DNS没有响应这种情况直接过滤掉。注意这里没有用subprocess调系统命令所有步骤都是纯Python这也方便后面接到Celery异步任务里。3.2 端口扫描TCP Connect与Banner识别端口扫描模块我会用TCP Connect模式而不是SYN半开扫描。原因很实际SYN扫描需要构造原始套接字在Windows上还要装Npcap部署成本高而TCP Connect只需要调用标准库socket虽然速度稍慢但对课设项目完全够用而且不容易被系统防火墙拦截。# scanner/portscan.py import socket from dataclasses import dataclass from typing import List dataclass class PortResult: port: int state: str service: str def tcp_connect_scan(host: str, port: int, timeout: float 1.0) - bool: TCP Connect扫描单个端口 sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) try: result sock.connect_ex((host, port)) return result 0 finally: sock.close() def scan_ports(host: str, ports: List[int], timeout: float 1.0) - List[PortResult]: results [] for port in ports: is_open tcp_connect_scan(host, port, timeout) if is_open: results.append(PortResult(portport, stateopen)) return results逻辑说明connect_ex比connect好用之处在于它不会抛异常而是返回错误码0表示连接成功其他值表示不同错误类型。扫描到开放端口后通常还需要做Banner识别抓取服务返回的版本信息这可以在上面代码的is_open分支里再发一次数据接收操作。端口列表可以从配置文件里读取一般默认扫描常用端口集合21,22,23,25,53,80,110,135,139,143,443,445,993,995,1433,1521,3306,3389,5432,6379,8000,8080,8443参数说明timeout控制单端口探测超时时间太短在普通网络环境下会把慢速服务误判为关闭太长会拖慢整体速度1.0秒是局域网和公网环境都比较稳妥的值如果想做快速扫描可以降到0.30.5秒但要接受误报率上升。3.3 SQL注入与XSS检测从请求构造到特征匹配SQL注入检测的常见思路是往目标URL的参数里注入payload然后等价的请求发送两次一次带payload一次不带通过响应体的差异判断是否存在注入。这个差异分析有两种落地方式一种是基于状态码与响应体长度的简单对比另一种是基于错误特征如SQL syntax、mysql_fetch的匹配。# scanner/sqli.py import requests PAYLOADS [ , OR 11, OR 11-- , ) OR (11--, UNION SELECT NULL-- , ] def request_with_timeout(url: str, params: dict, timeout: int 5) - requests.Response | None: 发送带超时的HTTP请求 try: resp requests.get(url, paramsparams, timeouttimeout, verifyFalse, headers{User-Agent: Mozilla/5.0}) return resp except requests.RequestException: return None def detect_sqli(url: str, param_name: str) - dict: 检测单个参数的SQL注入 # 先发正常请求 normal_params {param_name: 1} normal_resp request_with_timeout(url, normal_params) if not normal_resp: return {param: param_name, vulnerable: False, reason: request_failed} for payload in PAYLOADS: test_params {param_name: payload} resp request_with_timeout(url, test_params) if not resp: continue # 特征1响应体长度差异 20% 或 状态码变化 len_diff abs(len(resp.text) - len(normal_resp.text)) if len_diff len(normal_resp.text) * 0.2 or resp.status_code ! normal_resp.status_code: return {param: param_name, vulnerable: True, payload: payload} # 特征2数据库错误关键字 db_errors [SQL syntax, mysql_fetch, ORA-, PostgreSQL, sqlite] if any(err.lower() in resp.text.lower() for err in db_errors): return {param: param_name, vulnerable: True, payload: payload} return {param: param_name, vulnerable: False}逻辑说明这个检测器先以正常参数值建立基线然后逐个注入payload通过响应差异判断注入点。只做响应长度对比会有很多误报所以第二层加了数据库错误特征匹配只有命中错误关键字时才确认漏洞。verifyFalse关闭SSL证书校验是因为很多测试站点使用自签名证书验证会直接请求失败。参数说明PAYLOADS里每条payload针对不同的注入点类型第一行单引号用于触发数据库语法错误第3、4行是布尔盲注绕过用的timeout5控制单次请求的超时防止目标响应慢导致任务长时间卡住。检测接口在真实使用时要限定请求频率否则完全可能把目标站点打宕这也是安全测试工具的伦理边界。XSS检测的原理逻辑类似只是payload换成scriptalert(document.cookie)/script这类脚本标签然后在响应体中检查未经过滤的payload回显。这里不额外贴代码核心就是“提交payload → 检索响应体是否包含payload原文”。3.4 误报处理与漏洞确证自动扫描必然会遇到误报尤其是基于响应差异的检测方式。一个有效的处理策略是将扫描结果标记为“疑似漏洞”随后用独立的验证模块重新请求一次只有验证请求也满足漏洞特征才把状态改为“已确认”。验证时可以换一组语义相同但写法不同的payload比如URL编码后的%27%20OR%201%3D1--如果两种不同编码都能触发同样的异常就可以确信不是巧合。4. 任务调度、异步执行与报告导出4.1 Celery接入与任务状态流转Web安全扫描是典型的耗时任务一个子域枚举加端口扫描可能要跑几分钟如果直接在Django视图里同步执行浏览器请求会一直挂着可能触发超时。这里采用Celery Redis的异步任务方案视图只需创建一条扫描任务记录然后把真正的扫描工作交给Celery worker执行。# tasks/tasks.py from celery import shared_task from django.utils import timezone from scanner.subdomain import run_subdomain_enum from scanner.portscan import scan_ports from scanner.sqli import detect_sqli from scanner.models import ScanTask, VulnResult shared_task(bindTrue, max_retries3, default_retry_delay30) def execute_scan_task(self, task_id: int): 执行扫描任务 task_obj ScanTask.objects.get(idtask_id) task_obj.status running task_obj.save(update_fields[status]) try: target task_obj.target if task_obj.task_type subdomain: task_obj.logs 开始子域枚举\n task_obj.progress 30 task_obj.save(update_fields[logs, progress]) results run_subdomain_enum(target.target_url, dict/subdomain.txt) task_obj.logs f发现 {len(results)} 个子域: {results} task_obj.progress 100 elif task_obj.task_type portscan: task_obj.progress 10 task_obj.save(update_fields[progress]) ports [21, 22, 23, 25, 53, 80, 110, 135, 139, 143, 443, 445, 993, 995, 1433, 1521, 3306, 3389, 5432, 6379, 8080, 8443] open_ports scan_ports(target.target_url, ports) task_obj.logs f开放端口: {[p.port for p in open_ports]} task_obj.progress 100 task_obj.status completed task_obj.finished_at timezone.now() task_obj.save() except Exception as exc: # 重试3次每次间隔30秒 raise self.retry(excexc, countdown30)逻辑说明bindTrue让任务函数能访问它自己实例self这样才能调用self.retry()实现失败重试。update_fields是Django ORM的一个优化点只更新指定字段能减少生成UPDATE语句的字段数量对频繁状态变更很有效。参数说明max_retries3意味着最多执行4次第1次算原始执行之后重试3次default_retry_delay30是重试间隔秒数。异常发生时任务不会直接标记为failed而是先进入重试队列重试次数用尽后Celery会把任务标记为失败这时Django里的ScanTask.status才需要更新为failed。任务状态流转的对照关系如下Celery任务状态Django ScanTask状态说明PENDINGpending任务已创建入队RECEIVED / STARTEDrunningworker开始执行SUCCESScompleted正常完成RETRYrunning失败重试中FAILUREfailed重试次数耗尽这个表在答辩时画成流程图也很有说服力能直接体现你理解异步任务的生命周期。4.2 扫描报告的PDF导出报告模块是这类系统的门面也是容易被课设项目忽略的部分。基础的实现是把漏洞信息渲染成HTML模板再用第三方库转成PDF。比较省事的方案是weasyprint它能把带CSS样式的HTML转成PDF不需要额外安装LaTeX之类的重型依赖。# reports/generator.py from django.template.loader import render_to_string from weasyprint import HTML from pathlib import Path def generate_pdf_report(task_id: int, output_path: str): 根据扫描任务生成PDF报告 task ScanTask.objects.select_related(target).get(idtask_id) vulns task.vulns.all().order_by(-severity) html_content render_to_string(reports/report_template.html, { task: task, vulns: vulns, }) # 渲染HTML为PDF HTML(stringhtml_content).write_pdf(output_path) return output_path逻辑说明select_related(target)通过SQL的JOIN一次性把外键关联的目标数据查出来避免每访问一次task.target就多一次数据库查询这在生成报告时要遍历很多漏洞记录的场景下能显著减少SQL执行次数。render_to_string把Django模板渲染成纯HTML字符串交给weasyprint时注意模板里的静态资源路径要写成绝对路径否则PDF里会漏掉图片和样式。参数说明output_path建议用settings.MEDIA_ROOT/reports/{task_id}_{date}.pdf的格式这样在Django的MEDIA_URL下可以直接通过浏览器访问下载。报告模板里要展示漏洞等级分布可以在视图层用ORM聚合from django.db.models import Count severity_stats task.vulns.values(severity).annotate(countCount(id))这段代码返回的是按severity分组的统计列表例如[{severity: high, count: 3}, {severity: low, count: 1}]把它传进模板后可以用表格或柱状图展示不需要额外的图表库。4.3 前后端联动与轮询刷新前端页面需要实时展示扫描进度常见做法是前端每隔35秒轮询一次任务状态接口而不是用WebSocket——WebSocket在小项目里引入会增加部署复杂度。轮询接口返回JSON前端用setInterval刷新进度条# tasks/views.py from django.http import JsonResponse from scanner.models import ScanTask def task_progress_api(request, task_id: int): 任务进度查询API task ScanTask.objects.get(idtask_id, ownerrequest.user) data { status: task.status, progress: task.progress, logs: task.logs, } return JsonResponse(data)接口只返回三个字段前端拿到后更新页面上的进度条值和日志区文本。轮询间隔建议设置成3000毫秒太短会给服务器造成不必要的压力太长会导致进度条跳动感明显。这里没有用Django REST Framework纯手写JsonResponse就能完成需求少引一个依赖对课设项目来说更可控。5. 运行部署、使用说明与高频排错5.1 环境依赖清单系统的运行环境依赖集中在requirements.txt里核心依赖如下Django4.2.7 celery5.3.6 redis5.0.3 django-celery-beat2.6.0 requests2.31.0 weasyprint60.2 python-dotenv1.0.0 gunicorn21.2.0版本说明Django 4.2是当前LTS版本安全更新周期覆盖到2026年比5.x更稳当。Celery 5.3.x与Django 4.2的兼容性已经过广泛验证。weasyprint 60版本要求Python 3.9如果本机是Python 3.8需要降到52之前的版本。5.2 初始化与启动步骤拿到项目源码后的启动路径如下按顺序执行不要跳步# 1. 创建虚拟环境并安装依赖 python3 -m venv venv source venv/bin/activate # Windows: venv\Scripts\activate pip install -r requirements.txt # 2. 迁移数据库 python manage.py makemigrations users targets scanner tasks reports python manage.py migrate # 3. 创建超级管理员 python manage.py createsuperuser # 4. 启动Django服务与Celery python manage.py runserver 0.0.0.0:8000 # 新终端窗口启动Redis需提前安装Redis redis-server # 新终端窗口启动Celery worker celery -A config worker -l info逻辑说明makemigrations后面显式指定了app名称这是因为不同app里的模型有外键依赖分开迁移会避免生成损坏的迁移文件。Celery worker必须和Django进程使用同一个数据库和Redis实例worker启动后会自动接收队列里的扫描任务。-l info表示日志级别为info能看到任务接收和执行的实时状态。启动完成后浏览器访问http://127.0.0.1:8000进入系统首页先登录然后进入目标管理页面添加一个目标触发扫描任务后在任务列表里能看到状态从“等待中”变成“扫描中”再到“完成”。注意使用系统扫描的任何目标都必须经过目标所有者书面授权仅限本人拥有或授权测试的服务器这不是免责声明而是安全测试工作的基本职业规范。5.3 高频问题与修复对照表现象可能原因解决方式访问页面报TemplateDoesNotExist前端模板文件路径未注册检查settings.py的DIRS是否包含模板根目录必要时重启服务Celery任务一直卡在pendingRedis未启动或worker未启动redis-cli ping确认Redis存活celery -A config worker -l info看worker是否挂起扫描任务报ConnectionRefusedError目标不可达或端口被防火墙拦截先在本机telnet 目标IP 端口验证连通性PDF报告中文乱码weasyprint缺少中文字体apt install fonts-noto-cjk安装Noto CJK字体然后重新生成轮询接口返回403CSRF校验未通过在AJAX请求头加X-CSRFToken值从cookie中读取最隐蔽的一个坑是all.css等静态资源加载404检查项目根目录下static/文件夹是否存在以及settings.STATICFILES_DIRS路径是否指向正确。源码包里如果附带的是压缩后的CSS文件可以看到xenon.css、tabler.css这类命名要注意DEBUGFalse时Django不会自动提供静态文件服务必须执行python manage.py collectstatic收集到指定目录。这个问题在部署到宝塔或云服务器时特别常见每次更新CSS后没有重新collect浏览器缓存又加载旧文件排查很久才发现是静态文件问题。最后一招排错技巧在settings.py里开一个LOG_LEVELDEBUG的日志配置把SQL和请求日志打到logs/文件夹当扫描结果数量不对时打开日志看ORM实际执行的SQL语句比在代码里到处写print高效得多。另外建议在交付项目前把python manage.py check --deploy跑一遍它会自动检查settings里的安全隐患比如DEBUGTrue、ALLOWED_HOSTS为空、Cookie未设置Secure标记等。这个命令给出的警告项大多能在几分钟内修完但答辩时讲到“我做了生产环境安全加固”效果比多写几个功能模块还要加分。本文还有配套的精品资源点击获取