
简介这份资源是面向初级运维人员与网络安全研究者的漏洞扫描系统设计文档围绕Python、Django与Docker技术栈讲解如何构建一个低学习成本的B/S架构扫描平台帮助缺乏专业安全技能的用户快速开展漏洞检测与风险排查。压缩包内共1个docx文件约2.18MB内容涵盖绪论、技术简介、系统分析与设计等章节涉及用户认证、信息管理、漏洞扫描、日志文章与权限管理等模块并给出Nmap集成与报告生成思路。文档从课题背景、国内外漏洞危害现状切入逐步展开需求分析、逻辑架构与功能模块设计适合作为课程设计、毕业设计或安全入门实践的参考方案。目前已有345人学习下载读者可借此理解Django的MVT开发模式、Docker轻量虚拟化封装扫描工具的方法以及B/S架构下权限控制与任务管理的实现路径为后续动手搭建类似系统提供较完整的结构参照。1. 从一份 docx 标题说起Python 漏洞扫描系统到底该做成什么样很多人第一次看到「基于 Python 的漏洞扫描系统的设计与实现」这个标题脑子里浮现的是 nmap 套个壳、点一下按钮出一堆红字。真动手做过的人知道难点从来不在扫描本身而在扫描之前的目标管理、扫描之中的任务调度、扫描之后的漏洞归并与报告。nmap 负责端口和服务的探测Python 负责把探测结果变成可存储、可查询、可复现的数据Django 负责把这一切包装成一个能多人使用的 Web 系统Docker 负责让这套东西在别人机器上也能跑起来。这四件事凑齐才叫一个「系统」否则只是一个脚本。这篇文章面向的是想把这个标题真正落地的人可能是课程设计要交东西的学生也可能是想给内部资产做轻量巡检的运维。我会按「先能跑、再能存、再能看、最后能扩展」的顺序把选型理由、目录结构、关键代码、参数含义和踩过的坑讲清楚。读完你应该能自己搭出一套最小可用的版本并且知道哪些地方现在别碰、以后再说。2. 技术选型与最小骨架为什么是 Python Django Docker Nmap2.1 四个组件各自解决什么问题先把职责划清楚后面写代码才不会互相打架。nmap 是扫描引擎它输出的是 XML 或普通文本包含主机存活、开放端口、服务指纹、部分脚本探测结果。Python 是胶水层负责调用 nmap、解析结果、做并发控制、把数据写进数据库。Django 是展示层和任务管理层提供用户、资产、任务、漏洞、报告这几类模型以及对应的增删改查接口。Docker 解决的是环境一致性尤其是 nmap 在不同发行版上的版本差异和权限问题。选 Django 而不是 Flask 或 FastAPI理由很实际这个系统天然需要用户体系、后台管理、ORM 和模板渲染Django 自带 admin 能省掉大量重复劳动django-rabc 这类权限方案也成熟。选 Docker 而不是裸装是因为 nmap 需要原始套接字权限容器里用--cap-addNET_RAW或--network host就能解决比在宿主机上折腾 sudo 干净得多。2.2 项目目录结构一个能长期维护的结构比「能跑就行」重要得多。我一般会这样组织vulnscan/ ├── docker-compose.yml ├── Dockerfile ├── requirements.txt ├── manage.py ├── vulnscan/ # Django 配置 │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── scanner/ # 扫描核心 app │ ├── models.py │ ├── tasks.py │ ├── nmap_runner.py │ ├── parser.py │ └── views.py ├── assets/ # 资产与漏洞展示 app └── templates/scanner只负责「怎么扫、怎么解析」assets只负责「怎么展示、怎么导出」。两个 app 之间通过模型外键关联不要互相 import 视图函数否则后期改一处崩一片。2.3 用 Docker 把 nmap 和 Django 装进同一个环境Dockerfile 的关键不是装多少东西而是把 nmap 和 Python 依赖固定住FROM python:3.11-slim # nmap 必须装且版本要固定不同版本 XML 字段有差异 RUN apt-get update apt-get install -y --no-install-recommends \ 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]python:3.11-slim体积小--no-install-recommends避免拉进一堆无用包。nmap 不写版本号时默认装当前源里的版本生产环境建议锁死比如nmap7.94git20230807.3be01efb1-1否则换台机器解析逻辑可能对不上。docker-compose 里要处理权限和网络services: web: build: . ports: - 8000:8000 cap_add: - NET_RAW - NET_ADMIN volumes: - .:/app depends_on: - db db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: vulnscan volumes: - db_data:/var/lib/mysql volumes: db_data:NET_RAW是 nmap 做 SYN 扫描的前提NET_ADMIN在部分探测模式下需要。如果启动时报virtualization support not detected那是 Docker Desktop 在 Windows 上的 WSL2 后端没开和本项目无关去 BIOS 开虚拟化、在 Docker Desktop 设置里启用 WSL2 即可。数据库用 MySQL 8.0 是因为 Django 的 JSONField 在 MySQL 8 上支持完善存 nmap 原始结果很方便。3. 扫描引擎落地nmap 调用、结果解析与任务调度3.1 用 Python 调 nmap 的两种方式与参数选择调 nmap 有两条路subprocess 直接调命令行或者用 python-nmap 库。python-nmap 本质也是包 subprocess但把结果转成了字典省去自己解析 XML。我一般用 subprocess -oX -输出 XML 到标准输出再用xml.etree解析因为这样对字段的控制最细出问题也容易定位。import subprocess import xml.etree.ElementTree as ET def run_nmap(target: str, ports: str 1-1000, timing: str T4): target: 单个 IP 或 CIDR如 192.168.1.0/24 ports: 端口范围默认扫 1-1000全端口用 1-65535 timing: T0-T5T4 是速度和准确性的常用折中 cmd [ nmap, -sS, # SYN 半开扫描需要 NET_RAW -sV, # 服务版本探测 -Pn, # 跳过主机存活探测避免被防火墙拦 ICMP -T4, # 时序模板 -p, ports, -oX, -, # XML 输出到 stdout target, ] result subprocess.run( cmd, capture_outputTrue, textTrue, timeout1800 ) if result.returncode ! 0: raise RuntimeError(fnmap failed: {result.stderr}) return ET.fromstring(result.stdout)-sS比-sT快且隐蔽但必须有 root 或 NET_RAW 权限容器里已经给了。-Pn在扫描内网资产时几乎是必须的很多主机不响应 ping 但端口是开的。-T4是速度和稳定性的平衡点-T5在丢包网络里会大量误报。timeout1800是防止大网段扫描卡死实际项目里应该按目标数量动态计算。3.2 解析 XML 并落库模型设计与字段含义nmap 的 XML 结构是nmaprun下面挂host每个 host 有address、ports、hostscript。解析时要处理「主机存活但无开放端口」和「端口开放但服务识别失败」两种情况。from scanner.models import ScanTask, Host, Port def parse_and_save(root, task: ScanTask): for host_el in root.findall(host): addr_el host_el.find(address) if addr_el is None: continue ip addr_el.get(addr) host, _ Host.objects.get_or_create(tasktask, ipip) for port_el in host_el.findall(.//port): state_el port_el.find(state) if state_el is None or state_el.get(state) ! open: continue service_el port_el.find(service) Port.objects.update_or_create( hosthost, portint(port_el.get(portid)), defaults{ protocol: port_el.get(protocol), service: service_el.get(name) if service_el is not None else , product: service_el.get(product) if service_el is not None else , version: service_el.get(version) if service_el is not None else , }, )update_or_create保证重复扫描同一目标时不会产生重复端口记录。service字段存的是 nmap 的服务名product和version是-sV探测出来的具体软件和版本这两个字段是后续匹配 CVE 的关键。注意state只取openfiltered和closed不落库否则数据量会爆炸。3.3 任务调度别让扫描阻塞 Web 请求新手最容易犯的错是在视图函数里直接调run_nmap结果页面转圈几分钟然后超时。正确做法是把扫描任务丢给后台 worker。轻量方案用threading 数据库状态字段重一点用 Celery Redis。import threading from django.db import close_old_connections def start_scan_async(task_id: int): def _run(): close_old_connections() task ScanTask.objects.get(idtask_id) task.status running task.save() try: root run_nmap(task.target, task.ports) parse_and_save(root, task) task.status done except Exception as e: task.status failed task.error str(e) finally: task.save() close_old_connections() threading.Thread(target_run, daemonTrue).start()close_old_connections()是必须的Django 的数据库连接不能跨线程复用不加这行在并发扫描时会报MySQL server has gone away。daemonTrue保证主进程退出时线程不会挂住。这个方案适合单机小规模任务多了要换成 Celery否则线程数不可控。4. 避坑与排查那些让扫描系统翻车的细节4.1 扫描结果全是 filtered 或直接超时现象任务状态变成 done但主机列表是空的或者所有端口都是 filtered。原因通常是容器网络模式不对或者目标网段有防火墙。先确认容器用的是--network host还是默认 bridgebridge 模式下 nmap 看到的源 IP 是容器网段很多内网 ACL 会直接丢弃。解决扫描内网时用network_mode: host或者把容器放到目标同网段。另外检查-Pn是否加上没加的话目标不回 ICMP 就整个跳过。4.2 解析 XML 时报编码错误现象ET.fromstring抛ValueError: Unicode strings with encoding declaration are not supported。原因是 nmap 输出的 XML 第一行带?xml version1.0 encodingUTF-8?而fromstring收到的是 str 不是 bytes。解决把result.stdout改成result.stdout.encode(utf-8)再传进去或者用ET.XMLParser(encodingutf-8)。这个坑在 python-nmap 里被封装掉了自己解析就一定会遇到。4.3 Docker 里 nmap 报权限不足现象Failed to open device eth0或You requested a scan type which requires root privileges。原因是容器默认没有 NET_RAW capability。解决compose 里加cap_add: [NET_RAW, NET_ADMIN]或者临时用docker run --cap-addNET_RAW。不要用--privileged那等于把宿主机权限全交出去没必要。4.4 扫描大网段时内存暴涨现象扫/16网段时进程被 OOM kill。原因是subprocess.run把整个 XML 读进内存几万台主机的 XML 能到几百 MB。解决改用subprocess.Popen流式读取或者把大网段拆成/24分批扫描每批落库后释放。nmap 本身有--max-hostgroup和--min-parallelism可以控制并发但内存问题主要在 Python 侧。4.5 重复扫描产生重复漏洞记录现象同一个资产扫两次漏洞列表翻倍。原因是漏洞匹配逻辑没有做去重。解决在漏洞模型上加unique_together (host, port, vuln_id)或者用update_or_create按vuln_id更新。更稳妥的做法是每次扫描生成一个scan_id展示时只取最新一次的结果历史记录保留用于对比。5. 从能跑到好用漏洞匹配、报告导出与一个我常用的验证习惯系统能扫出端口和服务之后下一步是把它变成「漏洞扫描」而不是「端口扫描」。最轻量的做法是维护一张本地漏洞规则表字段包括service、product、version_range、cve_id、severity、description。扫描完成后用product和version去匹配命中就生成漏洞记录。规则表可以手工维护几十条高频的比如 OpenSSH 旧版本、Apache 特定版本、Redis 未授权。不要一上来就接 NVD 全量库几万条规则匹配性能很差而且误报率高。from packaging.version import parse as vparse def match_vulns(port): rules VulnRule.objects.filter(serviceport.service) hits [] for rule in rules: if rule.product and rule.product.lower() not in port.product.lower(): continue if rule.version_max and vparse(port.version) vparse(rule.version_max): continue hits.append(rule) return hitspackaging.version比字符串比较靠谱能正确处理1.10 1.9这种。version_max表示「小于等于该版本受影响」规则表里存边界值。匹配时先过滤 service再过滤 product最后比版本三层过滤能把候选规则从几万降到几条。报告导出用 Django 的模板渲染成 HTML再交给浏览器打印成 PDF比直接生成 PDF 省事得多。模板里按主机分组每个主机下列端口和漏洞严重级别用颜色区分。导出接口加一个?task_id参数只导出指定任务的结果。最后说一个我自己的验证习惯每次改完解析逻辑或匹配规则不要只看新扫描的结果一定要拿一份固定的 nmap XML 样本跑回归。样本里故意包含「主机存活无端口」「端口开放服务识别失败」「版本号带字母后缀」这几种边界情况。跑一遍看解析出的记录数和字段值是否和预期一致。这个习惯帮我挡掉过好几次「改了一行解析结果所有版本号都变成空」的事故。扫描系统这种东西数据一旦落库错了后面所有报告都是错的后悔药没得吃。希望帮到你。本文还有配套的精品资源点击获取