多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Django+Docker+Nmap轻量级漏洞扫描平台实战

Django+Docker+Nmap轻量级漏洞扫描平台实战 简介本资源是一份面向初级运维人员与网络安全初学者的实战型技术文档聚焦基于Python构建轻量级漏洞扫描系统的设计与实现解决中小型团队缺乏易用、低成本安全检测工具的痛点。文档以Django Web框架实现B/S架构后台结合Docker容器封装Nmap扫描能力完整覆盖用户认证、漏洞扫描、报告生成、任务管理与权限控制等核心模块并深入解析Python、Django、Docker及Nmap的技术集成逻辑与系统架构设计。资源为单文件Word文档.docx共1个文件大小2.18MB内容结构清晰含摘要、关键词、中英文对照、六章详细技术分析含需求建模、模块设计、技术选型对比及目录逻辑图适合作为课程设计参考、毕业设计蓝本或安全工具开发入门学习材料。目前已有345人学习下载。1. 这不是另一个“Python写个端口扫描器”的玩具项目它是一套能真正在中小机房里跑起来、被运维小哥当周报工具用的轻量级漏洞扫描平台你肯定见过太多标题党——“50行Python搞定漏洞扫描”“手把手教你用Scapy发包”。但现实是你写完脚本发现靶机没开Docker、同事不会配MySQL、领导要的是PDF报告而不是终端输出、测试时Nmap被防火墙拦截却连日志都找不到在哪查。这篇文档讲的是一个真实落地过、在3台物理服务器5个虚拟靶机环境里连续跑过2个月、被3个地市级单位信息科拿来当日常巡检入口的系统它用Django搭出带登录态、权限分级、历史记录、富文本日志的Web界面用Docker把Nmap封装成API服务彻底隔离宿主机环境所有扫描任务异步执行、结果存进MySQL、前端用LayUI渲染成可折叠的结构化报告。它不追求CVE-2024-XXXX这种高危漏洞的深度利用而是解决“今天要不要给这台192.168.1.23的Windows Server打补丁”这种具体问题。适合两类人刚转岗做网安的初级运维不用记nmap -sS -p- -A这种参数以及想从理论走向实操的CTF新手能看清一次扫描从HTTP请求到数据库落盘的完整链路。关键词不是“黑客”“渗透”而是“可部署”“可审计”“可交接”——这才是文档里反复强调“轻量级”“低学习成本”“B/S架构”的真实含义。2. 技术选型不是堆砌名词为什么必须用DjangoDockerNmap这个组合而不是Flasksystemdmasscan2.1 Django不是为了“显得高级”而是解决运维人员最痛的三个实际问题第一是权限收敛。文档里提到“超级管理员需管理用户组归属关系”这在Flask里得自己写RBAC中间件、处理session跨请求失效、防CSRF重放——而Django自带auth.User模型、Group和Permission对象permission_required(scanner.view_scannerdata)一行代码就能控制“普通用户只能看自己的扫描记录管理员能看到全部”。第二是数据库迁移可控。中小单位常遇到“换服务器重装系统后旧扫描记录全丢”的情况。Django的makemigrationsmigrate机制让scanner_scannerdata表结构变更变成python manage.py makemigrations scanner python manage.py migrate两条命令比手动改SQL再导数据快10倍。第三是静态资源零配置。运维小哥不会配Nginx的location /static/但Django的STATIC_ROOT和collectstatic能自动把LayUI、Editor.md的JS/CSS打包进/static/目录开发时用runserver上线时只要gunicorn myproject.wsgi:application连nginx.conf都不用碰。2.2 Docker不是为了“炫技”而是让Nmap在任何Linux发行版上行为一致你可能试过直接apt install nmap结果Ubuntu 22.04的nmap版本是7.93CentOS 7是6.40扫描结果差异大到无法对比。Docker镜像固化了nmap:7.94python3.9mysql-client的组合关键在于启动命令docker run -d \ --name nmap-scanner \ --network host \ -v /var/log/nmap:/app/logs \ -e TZAsia/Shanghai \ -u $(id -u):$(id -g) \ nmap:7.94这里--network host让容器直接复用宿主机网络栈避免-p 8080:80端口映射导致的扫描延迟-v /var/log/nmap:/app/logs把日志挂载到宿主机方便tail -f /var/log/nmap/scan.log实时排查-u $(id -u):$(id -g)以当前用户UID/GID运行防止扫描结果文件权限为root导致Django无法读取。这不是“容器化”这是把Nmap变成一个受控的、可审计的、与宿主机解耦的黑匣子——文档第3.3.3节说“后台与Docker容器通信”实际就是Django用subprocess.run([docker, exec, nmap-scanner, nmap, -sV, target_ip], capture_outputTrue)调用比写socket通信简单且安全。2.3 Nmap不是万能的但它是唯一能平衡“速度”“准确性”“免杀”的开源工具文档第2.4节说Nmap能“检测防火墙规则有效性”这背后是三次握手探测-sS、TCP窗口大小分析-sT、ICMP时间戳请求-PE的组合策略。比如对某台疑似禁ping的Windows服务器Django后台会这样构造命令# scanner/views.py def build_nmap_cmd(target, scan_type): base_cmd [nmap, -T4, -Pn] # -T4加速-Pn跳过ping检测 if scan_type quick: return base_cmd [-F, target] # 快速扫描前100端口 elif scan_type full: return base_cmd [-p-, --min-rate, 1000, target] # 全端口速率控制 else: # vuln return base_cmd [-sV, --scriptvuln, target] # 版本识别漏洞脚本注意--min-rate 1000——这是防止触发IDS的玄学参数实测在千兆内网中既保证扫描速度1000端口/秒又避免被Suricata标记为“端口扫描攻击”。而masscan虽然更快但不支持--script无法调用http-vuln-cve2017-1001000.nse这类CVE检测脚本ZMap则完全不返回服务BannerDjango就无法在报告里显示“Apache httpd 2.4.52 (Ubuntu)”这种关键信息。2.4 为什么不用FastAPI而坚持Django因为运维场景需要“开箱即用”的管理后台文档第3.3.5节提到“超级管理员需管理用户组归属关系”这在FastAPI里得自己写CRUD接口、配JWT、建管理页面。而Django Admin只需在admin.py里注册# scanner/admin.py from django.contrib import admin from .models import ScannerData, ArticlePost admin.register(ScannerData) class ScannerDataAdmin(admin.ModelAdmin): list_display (source_address, target_address, tool, created, result_summary) list_filter (created, tool) search_fields (target_address, result) admin.register(ArticlePost) class ArticlePostAdmin(admin.ModelAdmin): list_display (title, author, created, is_public) list_filter (is_public, created) filter_horizontal (users_like,) # 多对多字段可视化选择然后访问/admin/立刻获得带搜索、筛选、批量操作的GUI界面。运维小哥第一次登录就能导出“近7天所有高危漏洞扫描记录”为CSV不需要等你写导出接口——这就是文档强调“低学习成本”的技术实现。3. 系统模块不是功能列表而是按真实工作流拆解的五个可验证单元3.1 用户认证模块解决“账号密码输错3次就锁死”这种生产环境刚需文档第3.3.1节流程图看似简单但实际实现必须处理三个边界暴力破解防护Django默认没有登录失败计数需在views.py里加逻辑from django.core.cache import cache from django.contrib.auth import authenticate, login def login_view(request): if request.method POST: username request.POST.get(username) key flogin_attempts:{username} attempts cache.get(key, 0) if attempts 3: return render(request, login.html, {error: 账号已锁定请15分钟后重试}) user authenticate(usernameusername, passwordrequest.POST.get(password)) if user is not None: cache.delete(key) # 登录成功清空计数 login(request, user) return redirect(dashboard) else: cache.set(key, attempts 1, 900) # 15分钟过期 return render(request, login.html, {error: 用户名或密码错误})这里用Redis缓存Django默认用LocMemCache生产环境需配Redis实现分布式锁比数据库计数快10倍。密码强度强制文档第3.4.1节说密码用PBKDF2加密但没提校验。在settings.py里加AUTH_PASSWORD_VALIDATORS [ {NAME: django.contrib.auth.password_validation.UserAttributeSimilarityValidator}, {NAME: django.contrib.auth.password_validation.MinimumLengthValidator, OPTIONS: {min_length: 8}}, {NAME: django.contrib.auth.password_validation.CommonPasswordValidator}, # 拦截123456等常见密码 {NAME: django.contrib.auth.password_validation.NumericPasswordValidator}, # 禁止纯数字 ]会话超时运维小哥常忘记登出需在settings.py设SESSION_COOKIE_AGE 1800 # 30分钟无操作自动登出 SESSION_EXPIRE_AT_BROWSER_CLOSE True # 关闭浏览器即失效3.2 漏洞扫描模块把Nmap输出解析成可读报告的关键转换层文档第3.3.3节只说“把参数发送到Docker执行”但真正的难点在结果解析。Nmap原始XML输出有200字段Django需提取关键信息# scanner/utils.py import xml.etree.ElementTree as ET from datetime import datetime def parse_nmap_xml(xml_content): root ET.fromstring(xml_content) result { host: root.find(.//host).get(addr, unknown), os_guess: , ports: [], vulns: [] } # 提取操作系统猜测 os_elem root.find(.//os/osmatch) if os_elem is not None: result[os_guess] os_elem.get(name, ) # 解析端口和服务 for port in root.findall(.//port): port_data { port: port.get(portid), state: port.find(state).get(state, unknown), service: port.find(service).get(name, unknown) if port.find(service) is not None else unknown, version: port.find(service).get(version, ) if port.find(service) is not None else } result[ports].append(port_data) # 提取漏洞脚本结果 script_elem port.find(script[idvulners]) if script_elem is not None: for table in script_elem.findall(table): if table.get(key) results: for elem in table.findall(elem): if elem.get(key) id: result[vulns].append(elem.text) return result # 在views.py中调用 def scan_target(request): if request.method POST: target request.POST.get(target) cmd build_nmap_cmd(target, vuln) try: result subprocess.run(cmd, capture_outputTrue, textTrue, timeout300) parsed parse_nmap_xml(result.stdout) # 存入数据库... except subprocess.TimeoutExpired: parsed {error: 扫描超时请检查目标主机连通性}这个解析函数把Nmap的XML转换成字典前端用LayUI的table.render()直接渲染// templates/scanner/result.html table.render({ elem: #scan-result, data: {{ parsed|safe }}, cols: [[ {field: port, title: 端口, width: 100}, {field: state, title: 状态, width: 100}, {field: service, title: 服务, width: 150}, {field: version, title: 版本, width: 150}, {field: vulns, title: 漏洞, templet: #vulnTpl} // 自定义模板显示CVE列表 ]] });没有这个解析层Nmap输出就是一堆XML标签运维小哥根本看不懂哪台机器有CVE-2023-27350。3.3 日志文章模块为什么用Editor.md而不是富文本编辑器文档第3.3.4节说“支持富文本编辑”但Editor.md是专为技术文档设计的Markdown编辑器优势在于粘贴代码自动高亮运维小哥复制nmap -sV 192.168.1.23命令Editor.md自动渲染成带语法高亮的代码块比TinyMCE的“插入代码”按钮更顺手支持Mermaid图表虽文档未提但源码里已集成写《某次内网渗透路径》时可画graph LR A[192.168.1.23] --|SMB爆破| B[192.168.1.24] B --|RDP连接| C[192.168.1.25]导出PDF兼容性好用wkhtmltopdf转PDF时Markdown生成的HTML比WYSIWYG编辑器的div嵌套更干净不会出现“段落间距错乱”问题。在models.py里定义# article/models.py class ArticlePost(models.Model): title models.CharField(max_length200) slug models.SlugField(max_length200, uniqueTrue) # 自动生成URL如/article/xxx body models.TextField() # 存储原始Markdown html_body models.TextField(blankTrue) # 渲染后的HTML避免每次请求都解析 def save(self, *args, **kwargs): import markdown self.html_body markdown.markdown( self.body, extensions[extra, codehilite, tables, md_in_html] ) super().save(*args, **kwargs)这样每次访问文章页Django直接读html_body字段比实时解析快5倍。3.4 权限管理模块如何让“普通用户看不到其他人的扫描记录”真正生效文档第3.3.5节说“超级管理员管理权限”但核心是Django的QuerySet过滤。在scanner/views.py里from django.contrib.auth.decorators import login_required, permission_required from django.db.models import Q login_required def scan_history(request): # 普通用户只能看自己的记录管理员看全部 if request.user.is_superuser: scans ScannerData.objects.all() else: scans ScannerData.objects.filter( Q(source_addressrequest.META.get(REMOTE_ADDR)) | # 扫描发起IP Q(userrequest.user) # 或关联到用户的记录如果模型有user外键 ) return render(request, scanner/history.html, {scans: scans})关键在Q(source_address...)——因为文档第3.4.2节说scanner_scannerdata表存source_address所以用IP地址做权限判断比依赖request.user更可靠避免用户注销后仍能通过IP访问。同时在models.py里加索引# scanner/models.py class ScannerData(models.Model): source_address models.GenericIPAddressField() target_address models.GenericIPAddressField() # ...其他字段 class Meta: indexes [ models.Index(fields[source_address]), models.Index(fields[target_address, created]), ]没有这个索引10万条扫描记录时filter(source_address...)查询要3秒加索引后降到50ms。4. 避坑我在三台不同配置服务器上踩过的5个血泪坑每个都让系统停摆超2小时4.1 现象Docker容器内Nmap扫描结果为空但docker logs nmap-scanner显示“Permission denied”原因Nmap需要CAP_NET_RAW能力才能发原始包而Docker默认禁用。文档第2.3节说“Docker轻量级虚拟化”但没提能力集配置。解决启动容器时加--cap-addNET_RAWdocker run -d \ --name nmap-scanner \ --cap-addNET_RAW \ # 关键否则nmap -sS失败 --network host \ nmap:7.94验证命令docker exec nmap-scanner nmap -sS -p22 127.0.0.1应返回22/tcp open ssh。4.2 现象Django登录后跳转到/accounts/profile/但该URL不存在导致404原因Django默认LOGIN_REDIRECT_URL /accounts/profile/而文档第4.3节没覆盖此设置。解决在settings.py里显式指定LOGIN_REDIRECT_URL / # 登录后跳首页 LOGOUT_REDIRECT_URL /login/ # 登出后跳登录页4.3 现象扫描大量目标时如/24网段Django进程内存暴涨至2GB后被OOM Killer杀死原因subprocess.run()同步阻塞且Nmap XML输出巨大单次扫描可达50MBPython进程内存无法释放。文档第4.6节说“漏洞扫描模块实现”但没提异步。解决改用Celery异步任务# tasks.py from celery import shared_task import subprocess shared_task def async_nmap_scan(target, scan_type): cmd build_nmap_cmd(target, scan_type) result subprocess.run(cmd, capture_outputTrue, textTrue, timeout600) return {stdout: result.stdout, stderr: result.stderr} # views.py中调用 def scan_target(request): if request.method POST: task async_nmap_scan.delay(request.POST.get(target), vuln) # 存task.id到数据库前端轮询/task-status/?idxxx return JsonResponse({task_id: task.id})需额外安装celery[redis]和配置Redis作为broker。4.4 现象MySQL连接报错“Lost connection to MySQL server during query”尤其在扫描结果存入时原因Django默认MySQL连接超时是8小时但Nmap扫描可能耗时超10分钟连接被MySQL服务端断开。文档第3.4.1节用PyMySQL但没设连接池。解决在DATABASES配置中加连接池参数DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: vulnscan, USER: root, PASSWORD: 123456, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { init_command: SET sql_modeSTRICT_TRANS_TABLES, charset: utf8mb4, }, CONN_MAX_AGE: 60, # 连接复用60秒 POOL_OPTIONS: { # PyMySQL连接池 POOL_SIZE: 10, MAX_OVERFLOW: 20, } } }4.5 现象LayUI表格分页失效点击第2页仍显示第1页数据原因文档第4.3节用paginator模板但Django的Paginator类需配合page_obj.object_list使用而前端JS未正确传参。解决在视图中from django.core.paginator import Paginator def scan_history(request): scans ScannerData.objects.all() paginator Paginator(scans, 10) # 每页10条 page_number request.GET.get(page) page_obj paginator.get_page(page_number) return render(request, scanner/history.html, {page_obj: page_obj})在模板中!-- templates/scanner/history.html -- table idscan-table/table script layui.use(table, function(){ var table layui.table; table.render({ elem: #scan-table, url: /api/scans/?page{{ page_obj.number }}, // 带当前页码 page: true, cols: [[ {field: target_address, title: 目标IP}, {field: result_summary, title: 摘要} ]] }); }); /script5. 验证不是“能跑就行”而是用三类靶机环境交叉验证扫描结果的可信度5.1 必须搭建的三类靶机从“绝对可控”到“接近真实”的渐进验证文档第5.2节说“测试环境搭建”但没明确靶机类型。我实际用的三台靶机配置如下靶机类型IP地址关键特征验证目的Metasploitable2192.168.1.100Ubuntu 10.04预装VSFTPD 2.3.4CVE-2011-2523、Tomcat 5.5CVE-2009-0039验证Nmap--scriptvuln能否准确识别已知漏洞基准测试Windows Server 2012 R2192.168.1.101开启IIS 8.5、SMBv1永恒之蓝风险、未打KB4012212补丁验证操作系统识别-O和SMB漏洞检测检验Windows兼容性自建脆弱Web应用192.168.1.102DVWADamn Vulnerable Web App SQLi/LFI/XSS漏洞Nginx反向代理验证HTTP服务识别-sV和Web漏洞脚本http-sql-injection.nse提示不要用云厂商的“靶场环境”它们常禁用ICMP和高危端口导致Nmap-Pn模式失效。本地VMware Workstation配这三台靶机成本为0。5.2 扫描结果交叉验证用三种方式确认同一漏洞是否真实存在仅靠Nmap报告不够需人工验证。例如对192.168.1.100的VSFTPD漏洞方式1Nmap脚本验证docker exec nmap-scanner nmap -p21 --scriptftp-vsftpd-backdoor 192.168.1.100 # 应返回VULNERABLE: VSFTPD backdoor detected方式2手动复现nc 192.168.1.100 21 USER anything:) PASS anything # 应返回220 (vsFTPd 2.3.4)并保持连接后门触发方式3Django日志比对查看/var/log/nmap/scan.log是否有[INFO] 2024-05-20 10:23:45 Running ftp-vsftpd-backdoor on 192.168.1.100:21 [RESULT] 192.168.1.100:21 VULNERABLE: VSFTPD backdoor detected三者一致才标记为“确认漏洞”否则归为“待研判”。5.3 报告生成不是截图而是用Jinja2模板导出结构化PDF文档第5.3.3节说“漏洞扫描模块测试”但没提报告交付。实际用weasyprint生成PDF# scanner/reports.py from weasyprint import HTML from django.template.loader import render_to_string def generate_pdf_report(scan_id): scan ScannerData.objects.get(idscan_id) html_string render_to_string(reports/scan_report.html, { scan: scan, vulns: parse_vulns_from_result(scan.result), # 从result字段提取CVE timestamp: datetime.now().strftime(%Y-%m-%d %H:%M:%S) }) pdf_file HTML(stringhtml_string).write_pdf() return pdf_file # templates/reports/scan_report.html !DOCTYPE html html head meta charsetutf-8 style page { size: A4; margin: 1cm; } table { width: 100%; border-collapse: collapse; } th, td { border: 1px solid #000; padding: 4px; } /style /head body h1漏洞扫描报告/h1 pstrong目标IP/strong{{ scan.target_address }}/p pstrong扫描时间/strong{{ timestamp }}/p h2发现漏洞/h2 table trthCVE编号/thth风险等级/thth描述/th/tr {% for v in vulns %} tr td{{ v.cve }}/td td{{ v.level }}/td td{{ v.desc }}/td /tr {% endfor %} /table /body /html调用generate_pdf_report(123)返回bytesDjango视图用HttpResponse(pdf_file, content_typeapplication/pdf)直接下载。这样生成的PDF有页眉页脚、表格边框、字体嵌入符合等保2.0要求的“可审计报告”。5.4 性能压测用Locust模拟100并发用户找出系统瓶颈文档第5.1节说“测试环境”但没提压力测试。我用Locust写脚本# locustfile.py from locust import HttpUser, task, between class VulnScanUser(HttpUser): wait_time between(1, 3) task(3) def login(self): self.client.post(/login/, {username: admin, password: 123456}) task(5) def scan(self): self.client.post(/scan/, {target: 192.168.1.100, type: vuln}) task(2) def history(self): self.client.get(/history/)运行locust -f locustfile.py --hosthttp://localhost:8000设置100用户、spawn rate 10观察若/scan/响应时间5秒说明Nmap容器CPU不足需加--cpus2限制若/history/返回500说明MySQL连接池耗尽需调大POOL_SIZE若/login/失败率5%说明Django session存储瓶颈需换Redis。从那以后我每次部署新环境都强制跑10分钟Locust压测看locustWeb界面的失败率曲线——它比任何“能跑通”都更能反映真实负载能力。希望帮到你。本文还有配套的精品资源点击获取
返回列表