简介:本资源是一套基于Python与Django框架实现的漏洞扫描系统完整源码,面向网络安全初学者、毕业设计学生及Web安全开发实践者,旨在提供可运行、可调试、可二次开发的实战型安全工具项目。系统涵盖扫描引擎、结果分析、Web管理界面与数据库持久化模块,覆盖端口探测、服务识别、常见漏洞检测等核心功能,适合作为课程设计、毕设选题或安全工具开发入门参考。压缩包共376个文件,含26个核心Python源码(.py)、53个前端交互脚本(.js)、75个UI动效资源(.gif)、26个样式文件(.css)、15个模板(.tmpl)及多种字体、图标与配置文件,整体82.58MB;其中可见bootstrap.css、layui.css、font-awesome.css等表明采用主流前端框架构建响应式管理后台,nikto相关配置文件暗示集成经典扫描能力。目前已有403人学习下载,读者可直接部署运行、理解MVT架构下安全模块的协同逻辑,并基于现有目录结构(如db_*系列字典文件、扫描插件目录)快速扩展漏洞规则与目标类型。
1. 这不是另一个“Django+前端”Demo,而是一个可落地的漏洞扫描任务调度中枢
你见过太多用 Django 写的「用户注册登录」或「图书管理系统」——但真正把 Django 当作安全工具调度平台来用的项目极少。这个源码包(基于python+Django的漏洞扫描系统源码数据库.zip)的核心价值,不在于它调用了 Nikto 或 Bootstrap,而在于它把「扫描任务生命周期管理」变成了 Web 可控流程:从目标录入、参数配置、并发执行、结果入库,到 HTML 报告生成与权限隔离,全部跑在 Django 的 ORM、Admin、中间件和异步任务框架之上。它适合两类人:一是需要交付可演示、可审计、带后台管理的毕业设计的学生(关键词明确指向“毕业设计 Python django”),二是想快速搭建内部红队资产探测入口的安全工程师——它不替代专业扫描器,而是让扫描行为本身变得可追踪、可复现、可回溯。项目结构里混入了nikto.conf、bootstrap.css、layui.css等文件,说明它已集成轻量级前端交互与 Nikto 命令行扫描能力,且数据库层已预置模型(.zip后缀强调含完整数据库结构),不是空架子。
2. Django 模型设计与 Nikto 扫描引擎的耦合逻辑
漏洞扫描系统的数据流起点是「目标定义」,终点是「结构化报告」。Django 的优势在于用 Python 类声明式地定义这两端,并通过 QuerySet 实现原子化操作。本项目中,核心模型并非泛泛的ScanTask和Vulnerability,而是围绕Nikto 扫描上下文深度建模的实体。
2.1 数据库模型:为什么必须拆解ScanTarget与ScanConfig?
直接把所有参数塞进一个ScanTask模型会导致配置复用困难、历史对比失效。本项目采用分离式设计:
# models.py from django.db import models from django.contrib.auth.models import User class ScanTarget(models.Model): ip = models.GenericIPAddressField() hostname = models.CharField(max_length=255, blank=True) port = models.PositiveIntegerField(default=80) protocol = models.CharField(max_length=10, choices=[('http', 'HTTP'), ('https', 'HTTPS')]) description = models.TextField(blank=True) def __str__(self): return f"{self.ip}:{self.port} ({self.protocol})" class ScanConfig(models.Model): name = models.CharField(max_length=100, unique=True) nikto_args = models.TextField(help_text="Nikto 命令行参数,如 '-Tuning 9 -timeout 30'") max_threads = models.PositiveSmallIntegerField(default=3) timeout_seconds = models.PositiveIntegerField(default=120) created_at = models.DateTimeField(auto_now_add=True) class ScanTask(models.Model): STATUS_CHOICES = [ ('pending', '等待中'), ('running', '运行中'), ('completed', '已完成'), ('failed', '失败'), ] target = models.ForeignKey(ScanTarget, on_delete=models.CASCADE) config = models.ForeignKey(ScanConfig, on_delete=models.PROTECT) user = models.ForeignKey(User, on_delete=models.CASCADE) status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending') start_time = models.DateTimeField(null=True, blank=True) end_time = models.DateTimeField(null=True, blank=True) result_file = models.FileField(upload_to='scan_results/', null=True, blank=True) # 关键字段:原始 Nikto XML 输出路径(供后续解析) nikto_xml_path = models.CharField(max_length=500, blank=True)提示:
ScanConfig.nikto_args字段存储的是纯参数字符串(如-Tuning 9 -timeout 30 -Format xml),而非完整命令。这保证了 Django 层只负责调度,不硬编码执行逻辑,便于后期替换为 Nmap 或 custom Python scanner。
2.2 Nikto 集成:如何安全调用外部命令并捕获结构化输出?
Django 默认禁止os.system(),且subprocess.run()在 Web 请求中阻塞主线程。本项目采用subprocess.Popen+multiprocessing组合,在独立进程中执行 Nikto,并重定向 XML 输出:
# utils/scan_executor.py import subprocess import os import tempfile from django.conf import settings def execute_nikto_scan(target: ScanTarget, config: ScanConfig) -> dict: """ 执行 Nikto 扫描并返回结构化结果元数据 返回示例: {'xml_path': '/tmp/nikto_abc123.xml', 'exit_code': 0, 'stderr': ''} """ # 构建 Nikto 命令(显式指定 -Format xml 以确保结构化输出) cmd = [ 'nikto', '-host', target.ip, '-port', str(target.port), '-ssl' if target.protocol == 'https' else '', '-Format', 'xml', '-output', '/dev/stdout', # 先输出到 stdout,再重定向到文件 ] # 合并配置参数(过滤空字符串) cmd.extend([arg for arg in config.nikto_args.split() if arg]) # 创建临时 XML 文件(避免并发写入冲突) with tempfile.NamedTemporaryFile( suffix='.xml', delete=False, dir=settings.SCAN_RESULT_ROOT ) as tmp: xml_path = tmp.name try: # 使用 Popen 异步执行,超时由 config.timeout_seconds 控制 proc = subprocess.Popen( cmd, stdout=open(xml_path, 'wb'), stderr=subprocess.PIPE, cwd=settings.BASE_DIR, timeout=config.timeout_seconds ) _, stderr = proc.communicate() return { 'xml_path': xml_path, 'exit_code': proc.returncode, 'stderr': stderr.decode('utf-8') if stderr else '' } except subprocess.TimeoutExpired: return {'xml_path': '', 'exit_code': -1, 'stderr': 'Scan timeout'} except FileNotFoundError: return {'xml_path': '', 'exit_code': -2, 'stderr': 'Nikto not found in PATH'}注意:
settings.SCAN_RESULT_ROOT必须在settings.py中显式配置(如os.path.join(BASE_DIR, 'scan_results')),且该目录需有 Web 服务器写权限。-output /dev/stdout是关键——Nikto 的-output参数若指定文件,会覆盖已有内容;而重定向 stdout 到文件更可控。
2.3 扫描任务状态机:从 pending 到 completed 的精确控制
Django Admin 无法直接触发耗时扫描,因此需结合django-q或原生threading实现异步。本项目采用轻量级线程池(避免 Celery 依赖):
# tasks.py import threading from .models import ScanTask, ScanTarget, ScanConfig from .utils.scan_executor import execute_nikto_scan # 全局线程池(生产环境应改用 django-q 或 Celery) SCAN_THREADS = {} MAX_CONCURRENT = 5 def start_scan_task(task_id: int): task = ScanTask.objects.get(id=task_id) task.status = 'running' task.start_time = timezone.now() task.save() # 执行扫描 result = execute_nikto_scan(task.target, task.config) # 更新任务状态 task.status = 'completed' if result['exit_code'] == 0 else 'failed' task.end_time = timezone.now() if result['xml_path']: task.nikto_xml_path = result['xml_path'] task.save() # 解析 XML 并存入数据库(见 3.2 节) if task.status == 'completed': parse_nikto_xml(task) def trigger_scan(task_id: int): """供 views.py 调用的入口函数""" if len(SCAN_THREADS) >= MAX_CONCURRENT: raise RuntimeError("Max concurrent scans reached") t = threading.Thread(target=start_scan_task, args=(task_id,)) t.start() SCAN_THREADS[task_id] = t逻辑说明:
trigger_scan()不阻塞 HTTP 请求,立即返回。start_scan_task()在新线程中执行完整流程,包括调用execute_nikto_scan()、更新ScanTask.status、调用parse_nikto_xml()。SCAN_THREADS字典用于简单并发控制,实际部署建议替换为 Redis 锁或 django-q 的async_task()。
3. Nikto XML 解析与 Django Admin 漏洞可视化
Nikto 的 XML 输出是标准格式,但直接展示<scandetails>树对用户不友好。本项目将 XML 解析为 Django 模型实例,并暴露给 Admin 界面进行筛选、导出和关联分析。
3.1 XML 解析器:提取 IP、端口、漏洞 ID、风险等级、描述
Nikto XML 结构固定,根节点为<niktoscan>,每个<item>包含一个发现项。关键字段映射如下:
| XML 路径 | Django 模型字段 | 说明 |
|---|---|---|
//item/@id | vuln_id | Nikto 内部漏洞编号(如10001) |
//item/@method | http_method | HTTP 方法(GET/POST) |
//item/@uri | uri | 受影响路径 |
//item/@details | description | 文本描述 |
//item/@severity | severity | 数值(0=info, 3=high) |
# utils/xml_parser.py import xml.etree.ElementTree as ET from .models import NiktoFinding def parse_nikto_xml(scan_task: ScanTask): """ 解析 Nikto XML 输出,存入 NiktoFinding 模型 """ try: tree = ET.parse(scan_task.nikto_xml_path) root = tree.getroot() for item in root.findall('.//item'): # 提取属性 vuln_id = item.get('id', '') method = item.get('method', 'GET') uri = item.get('uri', '/') severity = int(item.get('severity', '0')) details = item.get('details', '').strip() # 创建记录 NiktoFinding.objects.create( scan_task=scan_task, vuln_id=vuln_id, http_method=method, uri=uri, severity=severity, description=details, # 从 target 衍生的辅助字段 target_ip=scan_task.target.ip, target_port=scan_task.target.port ) except Exception as e: # 记录错误但不中断主流程 print(f"XML parse error for task {scan_task.id}: {e}")参数说明:
NiktoFinding是独立模型(非ScanTask的 ForeignKey),支持按vuln_id、severity、target_ip多维度聚合查询。severity映射为整数便于排序(0=Informational, 1=Low, 2=Medium, 3=High),Admin 中可直接用list_filter。
3.2 Django Admin 定制:让安全人员一眼看懂风险分布
默认 Admin 仅显示列表,需添加统计卡片和导出功能:
# admin.py from django.contrib import admin from django.urls import reverse from django.utils.html import format_html from .models import ScanTask, NiktoFinding, ScanTarget @admin.register(NiktoFinding) class NiktoFindingAdmin(admin.ModelAdmin): list_display = ('scan_task_link', 'vuln_id', 'severity_badge', 'uri', 'description_short') list_filter = ('severity', 'scan_task__target__ip', 'scan_task__config__name') search_fields = ('uri', 'description', 'vuln_id') actions = ['export_as_csv'] def scan_task_link(self, obj): url = reverse('admin:myapp_scantask_change', args=[obj.scan_task.id]) return format_html('<a href="{}">Task #{}</a>', url, obj.scan_task.id) scan_task_link.short_description = '扫描任务' def severity_badge(self, obj): colors = {0: 'gray', 1: 'blue', 2: 'orange', 3: 'red'} return format_html( '<span style="background:{};color:white;padding:2px 6px;border-radius:4px;">{}</span>', colors.get(obj.severity, 'gray'), ['INFO', 'LOW', 'MEDIUM', 'HIGH'][obj.severity] ) severity_badge.short_description = '风险等级' def description_short(self, obj): return obj.description[:50] + '...' if len(obj.description) > 50 else obj.description description_short.short_description = '描述' def export_as_csv(self, request, queryset): # 导出 CSV 的具体实现(略,需用 django-admin-csv-exporter 或自定义 response) pass export_as_csv.short_description = "导出选中项为 CSV"关键技巧:
severity_badge()方法用内联 CSS 渲染彩色标签,比纯文本更直观;list_filter中嵌套scan_task__target__ip实现跨表筛选,无需额外视图;search_fields包含vuln_id,方便按 Nikto 编号查 CVE 关联项。
3.3 数据库迁移与初始数据:makemigrations前必须确认的三件事
本项目 ZIP 包含预置数据库(可能是 SQLite.db文件),但直接复制会丢失 Django 的 migration history。正确做法是:
清空
migrations/目录下除__init__.py外的所有文件
(避免与现有 migration 冲突)运行
python manage.py makemigrations --empty myapp创建空迁移# 生成 0001_initial.py,手动编辑其中的 operations from django.db import migrations class Migration(migrations.Migration): initial = True dependencies = [] operations = [ # 复制 models.py 中的 CreateModel 操作(Django 会自动生成) ]执行
python manage.py migrate --fake-initial
此命令告诉 Django:“这些表已存在,跳过创建,只记录 migration 已应用”。
验证命令:
python manage.py showmigrations应显示[X] 0001_initial;python manage.py dbshell进入 SQLite 后执行.tables,应看到myapp_scantask、myapp_niktofinding等表名(前缀取决于 APP 名)。
4. 前端界面集成与 Bootstrap/Layui 混合渲染策略
项目文件列表中出现bootstrap.css、layui.css、font-awesome.css,说明前端未采用单一框架,而是分层使用:Bootstrap 布局 + Layui 表单组件 + Font Awesome 图标。这种混合在毕业设计中很常见——既保证快速开发,又规避了全框架学习成本。
4.1 模板继承结构:base.html 如何协调多 CSS 源?
base.html是整个 UI 的骨架,需解决 CSS 加载顺序冲突:
<!-- templates/base.html --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>{% block title %}漏洞扫描平台{% endblock %}</title> <!-- 1. Bootstrap CSS(基础栅格、按钮) --> <link rel="stylesheet" href="{% static 'css/bootstrap.css' %}"> <!-- 2. Font Awesome(图标) --> <link rel="stylesheet" href="{% static 'css/font-awesome.css' %}"> <!-- 3. Layui CSS(表单、弹窗) --> <link rel="stylesheet" href="{% static 'css/layui.css' %}"> <!-- 4. 自定义样式(覆盖优先级最高) --> <link rel="stylesheet" href="{% static 'css/style.css' %}"> </head> <body> <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <a class="navbar-brand" href="{% url 'home' %}">漏洞扫描平台</a> <!-- ... --> </nav> <div class="container mt-4"> {% block content %}{% endblock %} </div> <!-- JS 加载顺序同样重要 --> <script src="{% static 'js/jquery.min.js' %}"></script> <script src="{% static 'js/bootstrap.bundle.min.js' %}"></script> <script src="{% static 'js/layui.js' %}"></script> <script> // 初始化 Layui 模块 layui.use(['form', 'layer'], function(){ var form = layui.form; var layer = layui.layer; }); </script> {% block extra_js %}{% endblock %} </body> </html>注意:
layui.js必须在jquery.min.js之后加载(Layui 依赖 jQuery),且bootstrap.bundle.min.js包含 Popper.js,不能被 Layui 的layer替换。style.css放最后,确保自定义样式能覆盖 Bootstrap/Layui 默认值。
4.2 扫描任务提交页:Layui 表单 + Django 表单验证双保险
Django Form 提供服务端验证,Layui 提供客户端即时反馈:
<!-- templates/scan/create.html --> {% extends 'base.html' %} {% load static %} {% block content %} <div class="row"> <div class="col-md-8 offset-md-2"> <h3>新建扫描任务</h3> <form id="scanForm" method="post"> {% csrf_token %} <div class="layui-form-item"> <label class="layui-form-label">目标 IP</label> <div class="layui-input-block"> {{ form.target_ip }} <span class="text-danger">{{ form.target_ip.errors }}</span> </div> </div> <div class="layui-form-item"> <label class="layui-form-label">端口</label> <div class="layui-input-inline"> {{ form.port }} </div> <div class="layui-form-mid">协议</div> <div class="layui-input-inline"> {{ form.protocol }} </div> </div> <div class="layui-form-item"> <label class="layui-form-label">扫描配置</label> <div class="layui-input-block"> {{ form.config }} </div> </div> <div class="layui-form-item"> <div class="layui-input-block"> <button class="layui-btn" lay-submit lay-filter="scanForm">立即扫描</button> </div> </div> </form> </div> </div> {% endblock %} {% block extra_js %} <script> layui.use(['form', 'layer'], function(){ var form = layui.form; var layer = layui.layer; // 监听表单提交 form.on('submit(scanForm)', function(data){ $.ajax({ url: '{% url "scan_create" %}', type: 'POST', data: data.field, success: function(res){ layer.msg('任务已提交,正在排队执行', {icon: 1}); setTimeout(function(){ location.href='{% url "scan_list" %}' }, 1500); }, error: function(xhr){ layer.msg('提交失败:' + xhr.responseJSON.error, {icon: 2}); } }); return false; }); }); </script> {% endblock %}逻辑说明:
{{ form.target_ip }}渲染 Django Form 字段,保留服务端验证逻辑(如 IP 格式校验);Layui 的lay-submit触发 AJAX 提交,避免页面刷新;layer.msg()提供用户友好的反馈,比原生 alert 更专业。
4.3 扫描结果页:Bootstrap 表格 + 动态 Severity Badge
结果页需展示NiktoFinding列表,并按 severity 分色:
<!-- templates/scan/result_detail.html --> <div class="table-responsive"> <table class="table table-hover"> <thead class="thead-light"> <tr> <th>风险等级</th> <th>漏洞 ID</th> <th>URI</th> <th>描述</th> <th>HTTP 方法</th> </tr> </thead> <tbody> {% for finding in findings %} <tr> <td> {% if finding.severity == 3 %} <span class="badge badge-danger">HIGH</span> {% elif finding.severity == 2 %} <span class="badge badge-warning">MEDIUM</span> {% elif finding.severity == 1 %} <span class="badge badge-info">LOW</span> {% else %} <span class="badge badge-secondary">INFO</span> {% endif %} </td> <td>{{ finding.vuln_id }}</td> <td><code>{{ finding.uri }}</code></td> <td>{{ finding.description|truncatewords:10 }}</td> <td><span class="badge badge-primary">{{ finding.http_method }}</span></td> </tr> {% endfor %} </tbody> </table> </div>技巧:Bootstrap 4 的
badge类(badge-danger/badge-warning)与 severity 数值直接对应,无需额外 CSS;truncatewords:10过滤长描述,保持表格紧凑;<code>标签包裹 URI,提升可读性。
5. 生产部署避坑指南:Nikto 权限、静态文件、数据库连接
毕业设计常卡在最后一步:本地能跑,部署后报错。以下是三个高频故障点的精准解法。
5.1 Nikto 执行权限:为什么Permission denied不是 Django 的锅?
错误现象:OSError: [Errno 13] Permission denied: 'nikto'
根本原因:Web 服务器(如 Apache/Nginx)以低权限用户(www-data)运行,而 Nikto 未加入该用户的 PATH 或无执行权限。
解决方案:
- 查找 Nikto 安装路径:
which nikto(通常为/usr/bin/nikto) - 赋予执行权限:
sudo chmod +x /usr/bin/nikto - 在 Django 设置中硬编码路径(避免依赖 PATH):
# settings.py NIKTO_BINARY_PATH = '/usr/bin/nikto' # 替换为实际路径# utils/scan_executor.py cmd = [settings.NIKTO_BINARY_PATH] + [...] # 替换 'nikto' 为全路径
验证命令:
sudo -u www-data /usr/bin/nikto -h 127.0.0.1 -port 80 -Format xml -output /dev/null应静默成功。
5.2 静态文件收集:collectstatic后 CSS 仍 404 的三重检查
错误现象:GET /static/css/bootstrap.css HTTP/1.1" 404
检查清单:
| 检查项 | 命令/配置 | 说明 |
|---|---|---|
STATIC_URL是否以/结尾 | print(settings.STATIC_URL) | 必须为'/static/',否则{% static %}生成错误路径 |
STATIC_ROOT是否可写 | `ls -ld $(python manage.py showmigrations --settings=myproject.settings | head -1)` |
| Web 服务器是否 alias 静态路径 | Nginx 配置中location /static/ { alias /path/to/static/; } | Django 开发服务器自动处理,生产环境必须由 Nginx/Apache 托管 |
调试命令:
# 1. 检查 collectstatic 是否真复制了文件 ls -l $(python -c "import myproject.settings; print(myproject.settings.STATIC_ROOT)")/css/ # 2. 检查 Django 是否识别静态文件 python manage.py findstatic css/bootstrap.css # 3. 检查 Nginx 配置语法 sudo nginx -t5.3 SQLite 并发瓶颈:为什么多用户扫描会卡死?
SQLite 默认不支持高并发写入,ScanTask状态更新和NiktoFinding插入同时发生时,极易触发database is locked错误。
临时解法(毕业设计适用):
在settings.py中启用 WAL 模式并增加超时:
# settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', 'OPTIONS': { 'timeout': 20, # 等待锁释放最长 20 秒 } } } # 在 db.sqlite3 所在目录执行(一次即可) sqlite3 db.sqlite3 "PRAGMA journal_mode=WAL;"长期方案(推荐):
替换为 PostgreSQL:
# Ubuntu 安装 sudo apt install postgresql postgresql-contrib sudo -u postgres psql -c "CREATE DATABASE vulnscan;" sudo -u postgres psql -c "CREATE USER vulnuser WITH PASSWORD 'yourpass';" sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE vulnscan TO vulnuser;"# settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'vulnscan', 'USER': 'vulnuser', 'PASSWORD': 'yourpass', 'HOST': 'localhost', 'PORT': '5432', } }验证 PostgreSQL 连接:
python manage.py dbshell应进入psql交互界面;python manage.py migrate不报错即成功。
本文还有配套的精品资源,点击获取