简介:这份资源是一篇面向初级运维人员与初级网络安全研究者的毕业设计类文档,围绕基于Python的漏洞扫描系统展开,重点解决中小型网络环境中安全检测门槛高、工具集成难的问题。文档以Django Web框架搭建B/S架构平台,借助Docker轻量级虚拟化封装Nmap扫描工具,涵盖用户认证、信息管理、漏洞扫描、日志文章与权限管理等模块设计,并配有需求分析、逻辑架构与功能模块划分的完整论述。资源包内共1个docx文件,约2.18MB,内容为系统设计与实现说明,结构上从绪论、技术简介到系统分析与设计逐章推进,便于读者理解整体方案与实现思路。目前已有345人学习下载,适合需要参考漏洞扫描系统设计、学习Django与Docker集成应用或撰写同类课题的读者使用。
1. 从一份 docx 标题说起:Python 漏洞扫描系统到底在扫什么
很多人第一次看到「基于 Python 的漏洞扫描系统」这个标题,脑子里浮现的是电影里那种黑底绿字、噼里啪啦滚 IP 的画面。真实情况要朴素得多:它本质是一个调度器,把 Nmap 这类扫描引擎的输出解析成结构化数据,再套上 Web 界面和数据库,让「扫了哪些主机、开了哪些端口、对应什么风险」变成可查询、可复现的记录。我在实际项目里最常被问的不是「怎么写扫描器」,而是「扫完一堆结果怎么存、怎么展示、怎么不把自己扫崩」。这篇笔记就按这个思路走:先讲清系统边界,再落到 Django 建表、Nmap 调用、Docker 打包,最后把踩过的坑摊开。适合有 Python 基础、想做一个能跑起来的安全工具原型的同学,也适合已经写过脚本但没做过完整 Web 系统的人。
2. 系统边界与选型:为什么是 Python + Django + Nmap + Docker
2.1 扫描引擎为什么选 Nmap 而不是自己写 socket
自己用 socket 写端口探测不是不行,但你要处理超时重传、半开连接、服务指纹识别、操作系统探测,这些 Nmap 已经做了二十多年。Nmap 是什么工具?一句话:网络发现和安全审计的事实标准。它支持-sV版本探测、-O系统识别、-sC默认脚本,输出格式有 XML、JSON、grepable 三种。对系统集成来说,XML 最稳,因为字段层级固定,解析不会因为版本升级就崩。
我一般这样分工:Nmap 负责「发现」,Python 负责「编排和解析」,Django 负责「存储和展示」。不要让 Django 的请求线程直接去跑 Nmap,因为一次全端口扫描可能几分钟到几十分钟,Web 请求会超时。正确做法是异步任务队列,或者至少用子进程加超时控制。
2.2 Django 在这里的角色不是「网站」而是「数据层」
Django 执行查询、删除对象这些操作,在漏洞扫描系统里对应的是:查询某次扫描任务的所有开放端口、删除过期扫描记录、按风险等级过滤结果。Django 的 ORM 让这些操作不用手写 SQL,迁移系统让表结构变更可追溯。新手常犯的错是把 Django 当成纯前端框架,其实它更适合做「带管理后台的数据中枢」。
选 Django 而不是 Flask 的理由很实际:自带 admin、自带 ORM、自带用户认证。漏洞扫描系统需要多用户、需要权限隔离、需要后台管理扫描任务,这些 Django 开箱即用。Flask 更轻,但你要自己拼这些组件,对新手不友好。
2.3 Docker 解决的是「Nmap 装不上」这个玄学问题
Windows 上装 Nmap 经常遇到权限问题,Linux 上不同发行版包名不一样,macOS 上又涉及 Homebrew 路径。Docker 把这些差异抹平:一个镜像里装好 Nmap 和 Python 依赖,换台机器docker compose up就能跑。Docker Desktop 安装教程网上很多,但要注意 Windows 家庭版需要 WSL2,虚拟化没开的话会报virtualisation support wasn't detected,这个后面避坑章节细说。
提示:Docker 镜像里跑 Nmap 需要
--cap-add=NET_RAW或--privileged,否则 SYN 扫描会失败。这是权限边界,不是配置错误。
3. 用 Django 搭最小可运行骨架:建表、建 App、跑迁移
3.1 创建项目和扫描专用 App
先确保 Python 环境干净。我习惯用 venv,不用全局 Python,避免 django 版本冲突。
# 创建虚拟环境并激活 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装 Django 和 Nmap 的 Python 封装 pip install django python-nmap # 创建项目 django-admin startproject vulnscan cd vulnscan # 创建扫描核心 App python manage.py startapp scanner逻辑说明:python-nmap是对 Nmap 命令行的封装,它调用系统安装的 nmap 可执行文件,所以镜像里必须同时装 nmap 和 python-nmap。参数上,startapp scanner生成的是标准 Django App 结构,后面所有模型和视图都放这里。
3.2 设计扫描结果的数据模型
漏洞扫描系统的核心表就三张:扫描任务、主机、端口/漏洞。不要一上来就设计十几张表,先跑通最小闭环。
# scanner/models.py from django.db import models class ScanTask(models.Model): target = models.CharField(max_length=255) # 目标 IP 或网段 created_at = models.DateTimeField(auto_now_add=True) status = models.CharField(max_length=20, default='pending') # pending/running/done/failed def __str__(self): return f"{self.target} - {self.status}" class Host(models.Model): task = models.ForeignKey(ScanTask, on_delete=models.CASCADE, related_name='hosts') ip = models.GenericIPAddressField() hostname = models.CharField(max_length=255, blank=True) os_match = models.CharField(max_length=255, blank=True) class Port(models.Model): host = models.ForeignKey(Host, on_delete=models.CASCADE, related_name='ports') port = models.IntegerField() protocol = models.CharField(max_length=10, default='tcp') state = models.CharField(max_length=20) # open/closed/filtered service = models.CharField(max_length=100, blank=True) version = models.CharField(max_length=255, blank=True)逻辑说明:on_delete=models.CASCADE表示删除任务时级联删除主机和端口,避免脏数据。related_name让反向查询写成task.hosts.all(),比默认的host_set可读。GenericIPAddressField比 CharField 多一层格式校验,存 IPv6 也不会崩。
参数说明:status字段用字符串而不是枚举,是为了迁移方便,后期要加状态不用改数据库。version字段留 255 长度,因为 Nmap 的版本字符串可能很长,比如Apache httpd 2.4.41 ((Ubuntu))。
3.3 迁移与后台注册
python manage.py makemigrations scanner python manage.py migrate python manage.py createsuperuser然后在scanner/admin.py里注册模型,这样 Django admin 就能直接看扫描结果:
from django.contrib import admin from .models import ScanTask, Host, Port admin.site.register(ScanTask) admin.site.register(Host) admin.site.register(Port)逻辑说明:Django admin 不是给最终用户用的,是给你调试用的。扫描任务跑完,进 admin 看数据对不对,比写查询语句快得多。等数据模型稳定了,再写面向用户的视图。
4. 调用 Nmap 并解析结果:从命令行到数据库的完整链路
4.1 用 python-nmap 执行扫描
# scanner/scan_engine.py import nmap def run_scan(target, ports='1-1024'): nm = nmap.PortScanner() # -sV 版本探测,-T4 加速,--open 只显示开放端口 nm.scan(hosts=target, ports=ports, arguments='-sV -T4 --open') results = [] for host in nm.all_hosts(): host_info = { 'ip': host, 'hostname': nm[host].hostname(), 'os_match': nm[host].get('osmatch', [{}])[0].get('name', '') if nm[host].get('osmatch') else '', 'ports': [] } for proto in nm[host].all_protocols(): for port in nm[host][proto].keys(): p = nm[host][proto][port] host_info['ports'].append({ 'port': port, 'protocol': proto, 'state': p['state'], 'service': p.get('name', ''), 'version': f"{p.get('product', '')} {p.get('version', '')}".strip() }) results.append(host_info) return results逻辑说明:nmap.PortScanner()每次实例化都会调用系统 nmap,所以不要在循环里反复创建。arguments里的-T4是速度模板,T0 最慢最隐蔽,T4 较快,T5 最快但可能丢包。内网扫描用 T4 够了,公网目标建议 T2 或 T3,避免触发防护。
参数说明:ports='1-1024'是常见端口范围,全端口是1-65535,但全端口扫描很慢,建议先扫常见端口,再对开放端口做深度探测。--open只返回开放端口,减少数据量。
4.2 把结果写进 Django 数据库
# scanner/tasks.py from .models import ScanTask, Host, Port from .scan_engine import run_scan def execute_scan(task_id): task = ScanTask.objects.get(id=task_id) task.status = 'running' task.save() try: results = run_scan(task.target) for r in results: host = Host.objects.create( task=task, ip=r['ip'], hostname=r['hostname'], os_match=r['os_match'] ) for p in r['ports']: Port.objects.create( host=host, port=p['port'], protocol=p['protocol'], state=p['state'], service=p['service'], version=p['version'] ) task.status = 'done' except Exception as e: task.status = 'failed' raise e finally: task.save()逻辑说明:try/except/finally保证无论扫描成功还是失败,任务状态都会更新。finally里的task.save()是后悔药,避免任务卡在 running 状态。实际项目里这个函数应该丢给 Celery 或 RQ 异步执行,不要直接在视图里调用。
参数说明:task_id用整数而不是对象传入,是为了兼容异步队列的序列化。如果直接用 Celery,任务函数参数必须是可 JSON 序列化的类型。
4.3 用 Docker 打包整个环境
# Dockerfile FROM python:3.11-slim # 安装 nmap,slim 镜像里没有 RUN apt-get update && apt-get install -y nmap && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]# docker-compose.yml version: '3.8' services: web: build: . ports: - "8000:8000" volumes: - .:/app cap_add: - NET_RAW - NET_ADMIN command: python manage.py runserver 0.0.0.0:8000逻辑说明:cap_add是必须的,Nmap 的 SYN 扫描需要原始套接字权限。不加的话 Nmap 会自动降级为 TCP connect 扫描,速度慢且结果略有差异。volumes挂载当前目录是为了开发时改代码不用重新 build。
参数说明:python:3.11-slim比alpine兼容性好,因为 alpine 用 musl libc,某些 Python 包编译会出问题。requirements.txt里至少要有django和python-nmap。
5. 避坑与排查:那些让我加班到凌晨的翻车现场
5.1 Nmap 报权限错误:You requested a scan type which requires root privileges
现象:容器里跑扫描,日志显示权限不足,扫描结果为空。 原因:Docker 默认没有 NET_RAW 能力,Nmap 无法创建原始套接字。 解决:在docker-compose.yml里加cap_add: [NET_RAW, NET_ADMIN],或者用--privileged启动。生产环境建议用 cap_add,不要用 privileged。
5.2 Django 迁移时报No module named 'scanner'
现象:makemigrations找不到 App。 原因:settings.py的INSTALLED_APPS里没加scanner。 解决:在INSTALLED_APPS列表末尾加'scanner',,注意逗号。改完再跑makemigrations。
5.3 Docker Desktop 启动失败:virtualisation support wasn't detected
现象:Windows 上装完 Docker Desktop,启动就报虚拟化未检测到。 原因:BIOS 里 Intel VT-x 或 AMD-V 没开,或者 Hyper-V 被其他虚拟化软件占用。 解决:进 BIOS 开虚拟化,Windows 功能里确认 Hyper-V 和「虚拟机平台」已启用。如果装了 VMware,先关掉再试。这个坑没有代码能救,只能改系统设置。
5.4 扫描结果重复写入
现象:同一个任务跑两次,数据库里出现两份主机记录。 原因:execute_scan没有做幂等处理,重复调用就重复插入。 解决:在写入前先Host.objects.filter(task=task).delete(),或者给Host加unique_together = ('task', 'ip')约束。我一般用后者,让数据库兜底。
5.5 扫描大网段时内存暴涨
现象:扫/24网段时 Python 进程内存飙升。 原因:nm.all_hosts()把所有结果一次性加载到内存。 解决:改用nm.scan(hosts=target, arguments='-sV -T4 --open -oX -')直接输出 XML 流,用xml.etree.ElementTree.iterparse逐条解析。或者把网段拆成/28分批扫描。
6. 进阶技巧:让扫描系统从「能跑」到「好用」
6.1 用 Django 的bulk_create把写入速度提上去
前面execute_scan里逐条Port.objects.create(),扫 1000 个端口就是 1000 次 INSERT。改成批量:
port_objs = [] for p in r['ports']: port_objs.append(Port( host=host, port=p['port'], protocol=p['protocol'], state=p['state'], service=p['service'], version=p['version'] )) Port.objects.bulk_create(port_objs, batch_size=500)batch_size=500是经验值,太大容易撞数据库的 SQL 长度限制,太小又没效果。实测 1000 条记录从 3 秒降到 0.2 秒。
6.2 用 Nmap 脚本做漏洞初筛
Nmap 自带vuln脚本类别,能查一些已知漏洞:
nmap --script vuln -sV 192.168.1.1在 Python 里对应arguments='-sV --script vuln'。注意这个脚本集比较慢,而且误报率不低,适合做初筛,不适合做最终结论。我一般把它和-sC默认脚本分开跑,避免一次扫描太久。
6.3 扫描任务的异步化:从线程到 Celery
Django 视图里直接调execute_scan会阻塞请求。最小改动是用threading.Thread:
import threading from .tasks import execute_scan def start_scan(request, task_id): t = threading.Thread(target=execute_scan, args=(task_id,)) t.start() return JsonResponse({'status': 'started'})但线程方案在 Django 开发服务器上还行,生产环境用 gunicorn 多 worker 时会失控。正经做法是 Celery + Redis,把execute_scan注册成@shared_task,视图里调execute_scan.delay(task_id)。Docker 里再加一个 worker 容器,和 web 容器共享代码卷。
6.4 验证扫描结果是否可信
扫完不要直接信。我习惯做三件事:第一,用nmap -p <port> <ip>单独验证某个端口,确认不是误报;第二,对比两次扫描结果,如果差异很大,可能是网络抖动或目标有防护;第三,把-sV的版本信息和目标服务的实际 banner 对比,比如curl -I http://<ip>:<port>,看 Server 头是否一致。
注意:扫描公网目标前确认你有授权。未授权扫描在多数地区是违规行为,内网测试也要走审批流程。
6.5 一个我常犯的错误
早期我总想把所有功能塞进一个 App,模型、视图、扫描逻辑全写在一起,结果改一处崩三处。后来强制自己拆:scanner/models.py只管数据,scanner/scan_engine.py只管调 Nmap,scanner/tasks.py只管编排。这样换扫描引擎时只改一个文件,Django 部分完全不动。这个习惯帮我省了很多返工时间,希望帮到你。
本文还有配套的精品资源,点击获取