
简介这份资源是面向初级运维人员与初级网络安全研究者的漏洞扫描系统设计与实现文档围绕Python、Django、Docker与Nmap等技术讲解如何构建一个低学习成本的B/S架构扫描平台帮助缺乏专业安全技能的用户开展基础网络安全检查。压缩包内共1个docx文件约2.18MB内容涵盖绪论、技术简介、系统分析与设计等章节涉及用户认证、信息管理、漏洞扫描、日志文章与权限管理等模块并配有摘要与目录便于按章节查阅。目前已有345人学习下载。读者可从中获取完整的系统设计思路、功能模块划分与集成方案理解Django快速开发与Docker轻量级虚拟化结合Nmap的实践路径适合作为课程设计、毕业设计或安全入门项目的参考材料。1. 从一份 docx 标题说起Python 漏洞扫描系统到底在扫什么很多人第一次看到「基于 Python 的漏洞扫描系统的设计与实现」这个标题脑子里浮现的是 Nessus、OpenVAS 那种界面复杂、插件成千上万的商业工具觉得自己用 Python 加 Django 搭一个纯属玩具。我一开始也这么想直到真正接手一个内部资产梳理的活儿几百台机器散落在几个网段端口开着什么、跑着什么服务、有没有明显的弱口令和过期组件全靠人工一台台登上去看根本不现实。这时候一个能定时跑、能出报告、能按资产维度归档的轻量扫描系统价值就出来了。这个标题拆开看其实是三件事扫描引擎负责发现Nmap 做端口和指纹识别、漏洞判定负责匹配把指纹和已知漏洞规则对上、系统平台负责管理Django 做资产、任务、报告和权限。它解决的不是「替代商业扫描器」而是「把扫描这件事工程化、可复现、可追溯」。适合谁适合手里有一堆内网资产要盘、又不想为每个小需求买授权的一线运维和安全同学也适合想拿一个完整项目练 Django Docker Nmap 组合的开发者。下面我按自己实际搭过的一版思路把选型、落地和踩坑讲清楚。2. 扫描引擎与平台选型为什么是 Nmap Django Docker 这套组合2.1 扫描能力为什么优先选 Nmap 而不是自己写 socket自己用 Python 的 socket 写端口扫描入门练手可以真上生产会立刻翻车。原因很直接TCP 连接扫描要处理超时、重传、并发、半开连接还要面对目标主机的防火墙策略和速率限制这些 Nmap 已经打磨了二十多年。Nmap 提供的不只是端口状态还有服务指纹-sV、操作系统识别-O、脚本引擎NSE能直接跑漏洞探测脚本这对一个扫描系统来说是现成的能力池。我一般把 Nmap 当成「扫描执行器」Python 只负责调度和解析。调用方式有两种一是subprocess直接调命令行二是用python-nmap库封装。前者灵活、可控参数多后者省去解析文本的麻烦。实际项目里我更倾向subprocess XML 输出-oX因为 XML 结构稳定解析起来比 grep 文本靠谱得多。# 一次典型的扫描快速端口 服务版本 输出 XML nmap -sS -sV -T4 --top-ports 1000 -oX /tmp/scan_result.xml 192.168.1.0/24这条命令里-sS是 SYN 半开扫描速度快且不易被应用层日志记录-sV做服务版本探测是后续漏洞匹配的关键-T4是时序模板内网可以用激进一点--top-ports 1000只扫常见端口全端口 65535 太慢除非有明确需求。输出 XML 给解析层用不要直接读 stdout。提示-sS需要 root 或 CAP_NET_RAW 权限容器里跑要注意加--cap-addNET_RAW否则会静默退化成-sT全连接扫描速度差一大截。2.2 Django 在这套系统里承担什么角色Django 不是扫描器它是「资产与任务的管理中枢」。一个扫描系统跑起来会产生大量结构化数据资产表、端口表、服务表、漏洞表、任务表、报告表。用 Django 的 ORM 建模配合 admin 后台几乎不用写多少前端就能把资产管理界面搭出来。热搜里常出现「django 创建 app」「django 项目实战新手」说明很多人卡在工程组织上。我的做法是按职责拆 appassets资产、网段、标签scanner扫描任务、调度、Nmap 调用封装vulns漏洞规则、匹配结果、风险等级reports报告生成与导出这样拆的好处是每个 app 的模型边界清晰迁移migration不会互相打架。Django 的manage.py startapp创建后记得在settings.py的INSTALLED_APPS里注册否则模型不会建表这是新手最常见的「表怎么没生成」问题。# scanner/models.py 任务模型的核心字段 class ScanTask(models.Model): target models.CharField(max_length64) # 目标网段或 IP ports models.CharField(max_length128, defaulttop1000) status models.CharField(max_length16, defaultpending) # pending/running/done/failed created_at models.DateTimeField(auto_now_addTrue) result_file models.CharField(max_length256, blankTrue) # XML 结果路径status字段是整个调度的心脏扫描是异步的任务提交后不能阻塞 Web 请求所以必须有状态机。result_file存路径而不是把 XML 塞进数据库是因为扫描结果动辄几 MB进库会让查询变慢。2.3 Docker 把 Nmap 和 Django 的依赖地狱一次性解决Nmap 的版本、NSE 脚本库、Python 依赖、数据库驱动这几样在不同机器上装一遍能折腾一下午。Docker 的价值在这里体现得最明显把扫描器和 Web 服务分别打包用docker compose编排。热搜里「docker 安装 mysql 失败」「docker desktop failed to start」这类问题本质多是环境没理顺而不是 Docker 本身难用。# docker-compose.yml 精简版 services: web: build: . ports: - 8000:8000 depends_on: - db db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: example MYSQL_DATABASE: vulnscan volumes: - db_data:/var/lib/mysql volumes: db_data:depends_on只保证启动顺序不保证 MySQL 已经能接受连接所以 Django 启动脚本里要加重试逻辑否则第一次migrate必然报连接拒绝。这是「docker 安装 mysql8.0 并使用」场景里最高频的坑。3. 从零跑通最小扫描闭环建表、调 Nmap、存结果3.1 环境准备与依赖安装的最小步骤先把 Python 环境和依赖固定下来避免版本漂移。我一般用requirements.txt锁版本Python 3.8 到 3.11 都能跑但 Nmap 的 Python 封装库对版本敏感建议固定。# 宿主机或容器内安装系统依赖 apt-get update apt-get install -y nmap # Python 依赖 pip install django mysqlclient python-nmapmysqlclient需要系统里有libmysqlclient-dev和编译工具否则pip install会报mysql_config not found。如果不想折腾编译可以换pymysql并在__init__.py里打补丁但性能和兼容性上mysqlclient更稳。Nmap 本体一定要在运行扫描的容器里装Web 容器和扫描容器如果是分开的扫描容器才需要 Nmap。3.2 用 subprocess 封装一次 Nmap 调用并解析 XML核心逻辑是拼命令、执行、读 XML、转成 Python 字典、写库。下面这段是我实际用过的简化版。import subprocess import xml.etree.ElementTree as ET def run_nmap(target, portstop1000): # -oX - 让 nmap 把 XML 输出到 stdout省去临时文件 cmd [nmap, -sS, -sV, -T4, -oX, -, target] if ports top1000: cmd.insert(4, --top-ports) cmd.insert(5, 1000) proc subprocess.run(cmd, capture_outputTrue, textTrue, timeout1800) if proc.returncode ! 0: raise RuntimeError(fnmap failed: {proc.stderr[:200]}) return parse_xml(proc.stdout) def parse_xml(xml_str): root ET.fromstring(xml_str) hosts [] for host in root.findall(host): addr host.find(address).get(addr) ports [] for port in host.findall(.//port): state port.find(state).get(state) service port.find(service) ports.append({ port: port.get(portid), state: state, service: service.get(name) if service is not None else , version: service.get(version) if service is not None else , }) hosts.append({ip: addr, ports: ports}) return hoststimeout1800是硬性保护防止某个网段卡死拖垮整个任务队列。-oX -把 XML 打到标准输出避免并发任务写同一个临时文件互相覆盖。解析时只取state为open的端口入库filtered和closed存了也没意义反而让报告变臃肿。service的name和version是后续漏洞匹配的输入比如识别出Apache httpd 2.4.49就能对上 CVE-2021-41773 这类路径穿越漏洞。3.3 把扫描结果落库并生成一份可读报告解析出来的字典要写进 Django 模型。这里有个性能点一个 C 段扫下来可能几百个 host、上千个端口逐条save()会慢得离谱用bulk_create批量插入。from django.db import transaction from assets.models import Asset, Port def save_scan_result(hosts): with transaction.atomic(): for h in hosts: asset, _ Asset.objects.get_or_create(iph[ip]) port_objs [ Port(assetasset, numberp[port], statep[state], servicep[service], versionp[version]) for p in h[ports] if p[state] open ] Port.objects.filter(assetasset).delete() # 先清旧数据再写 Port.objects.bulk_create(port_objs)transaction.atomic()保证一个资产要么全写成功要么全回滚避免扫到一半失败留下半截数据。先删后插是因为端口状态会变增量更新逻辑复杂且容易出错全量覆盖更简单可靠。报告生成可以用 Django 模板渲染 HTML再转 PDF或者直接导出 CSV 给运维同事别一上来就追求花哨的 PDF 排版。4. 漏洞匹配与任务调度让扫描系统真正「有结论」4.1 指纹到漏洞的匹配规则怎么设计扫描出端口和服务只是原料漏洞判定才是结论。最朴素也最可控的做法是维护一张规则表服务名 版本范围 → CVE 编号 风险等级 修复建议。不要一上来就接庞大的漏洞库先覆盖自己环境里最常见的几十条准确率比覆盖面重要。服务版本条件漏洞编号等级建议Apache httpd2.4.49CVE-2021-41773高升级到 2.4.51OpenSSH 8.0弱加密算法中禁用 ssh-dssMySQL5.7 未授权空口令高设置强密码Redis无密码未授权访问高绑定内网 requirepass匹配逻辑用 Python 写就是字符串和版本号比较版本比较别用字符串直接比2.4.9和2.4.10字符串比会出错用packaging.version或自己拆成元组。from packaging import version def match_vuln(service, ver): rules [ (httpd, 2.4.51, CVE-2021-41773, 高), (openssh, 8.0, 弱加密算法, 中), ] for name, cond, cve, level in rules: if name in service.lower(): op, target cond[0], cond[1:] if op and version.parse(ver) version.parse(target): return cve, level return None, Noneversion.parse能正确处理多段版本号比手写比较函数省心。规则表建议放数据库而不是硬编码方便运营同学自己加规则改规则不用重新发版。4.2 异步任务调度别让扫描阻塞 Web 请求扫描一个 C 段可能几分钟到几十分钟绝对不能放在 Django 的请求-响应周期里同步执行否则浏览器转圈转到超时。常见做法是引入 Celery Redis 做任务队列Django 只负责提交任务和查状态。# scanner/tasks.py from celery import shared_task from .models import ScanTask from .nmap_runner import run_nmap, save_scan_result shared_task def execute_scan(task_id): task ScanTask.objects.get(idtask_id) task.status running task.save() try: hosts run_nmap(task.target, task.ports) save_scan_result(hosts) task.status done except Exception as e: task.status failed task.result_file str(e)[:200] task.save()shared_task让任务可以被 Celery worker 消费Web 进程只调用execute_scan.delay(task_id)就立刻返回。状态字段让前端可以轮询进度。如果不想引入 Celery退而求其次可以用threading起后台线程但进程重启任务就丢了生产环境不推荐。热搜里「docker 安装 redis 主从」说明 Redis 在大家环境里很常见正好拿来当 broker。4.3 扫描频率与并发控制的实际参数并发不是越高越好。Nmap 本身有--min-rate和--max-rate控制发包速率系统层面还要限制同时运行的任务数否则把目标网络打挂或者把自己的出口带宽占满。我一般这样设单任务-T4同时运行任务数不超过 3扫描时间避开业务高峰。Celery 的worker_concurrency设成 2 到 4配合--max-tasks-per-child防止内存泄漏累积。注意对生产网段做全端口扫描前一定要拿到书面授权扫描行为在多数环境里会被 IDS 告警别让自己变成「被通报的那个人」。5. 避坑与排查这套系统最容易翻车的五个地方5.1 容器里 Nmap 报权限不足扫描结果全是 filtered现象容器内跑-sS扫描返回的端口状态全是filtered或者直接报You requested a scan type which requires root privileges。原因Docker 默认不给容器 NET_RAW 能力SYN 扫描发不了原始包。解决启动容器时加--cap-addNET_RAW --cap-addNET_ADMIN或者干脆用-sT全连接扫描代价是慢且容易被目标记录。5.2 Django migrate 报 MySQL 连接被拒现象docker compose up后 Web 容器启动就报django.db.utils.OperationalError: (2002, Cant connect to MySQL server)。原因depends_on只保证容器启动顺序MySQL 初始化要几十秒Django 启动太快。解决在启动脚本里加等待循环用mysqladmin ping或 Python 的 socket 探测直到数据库可连接再执行migrate。5.3 扫描结果里中文服务名乱码现象报告里服务描述出现乱码。原因Nmap XML 默认 UTF-8但某些系统 locale 不是 UTF-8subprocess读取时用了错误编码。解决subprocess.run(..., encodingutf-8, errorsreplace)显式指定编码别依赖系统默认。5.4 任务状态永远停在 running现象Celery 任务提交后状态不更新前端一直转圈。原因worker 进程崩了或者任务超时被 kill但except没覆盖到BaseException或者 worker 根本没启动。解决确认celery -A proj worker在跑给任务加soft_time_limit并在finally里兜底更新状态别让异常路径漏掉状态写回。5.5 规则匹配误报太多运维不信报告现象报告里一堆「高危」点进去发现版本号根本没匹配上或者服务名识别成了泛化名称。原因-sV的版本探测有时只能拿到服务名拿不到精确版本规则却按精确版本匹配。解决版本拿不到时降级为「疑似」而不是「确认」风险等级区分开规则里对服务名做模糊匹配但版本条件放宽宁可漏报不要误报报告可信度比数量重要。6. 进阶把扫描系统做成能长期维护的资产台账跑通最小闭环之后真正决定这套系统能不能活下去的是它能不能沉淀成资产台账而不是每次扫完就丢。我后来加的一个关键能力是「资产变更对比」每次扫描结果和上一次做 diff新增端口、消失服务、版本变化都单独列出来。这个功能比漏洞列表更受运维欢迎因为它直接告诉你「昨天到今天哪台机器变了」。实现上不难给Port表加个last_seen时间戳每次扫描更新超过 N 天没出现的端口标记为「已下线」而不是直接删。对比逻辑用集合运算def diff_ports(old_set, new_set): added new_set - old_set removed old_set - new_set return {added: added, removed: removed}old_set和new_set都是(port, service, version)元组集合集合运算天然去重且快。变更记录单独存一张表报告里按时间线展示运维一眼就能看出异常。另一个值得投入的点是扫描任务的「模板化」。别让用户每次手填网段和端口预置几套模板内网快速巡检top1000 常见漏洞脚本、Web 资产专项80/443/8080 http 相关 NSE、数据库专项3306/5432/6379/27017。模板存数据库任务创建时选模板即可降低使用门槛。验证这套系统有没有做对我的习惯是拿一台自己完全掌握的测试机故意开几个已知有问题的服务比如老版本 httpd、无密码 Redis看系统能不能准确识别并给出正确等级。如果连自己搭的靶机都测不准就别指望它在真实环境里靠谱。这个自测习惯帮我省了无数次在领导面前翻车的尴尬。最后说一句实在的这套东西的价值不在代码多优雅而在它能不能每周自动跑一次、结果能不能被人看懂、变更能不能被追踪。把这三件事做扎实比堆一百条漏洞规则都有用。希望帮到你。本文还有配套的精品资源点击获取