news 2026/9/23 19:54:21

基于Django的Web安全渗透测试工具:模块化扫描与误报治理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Django的Web安全渗透测试工具:模块化扫描与误报治理

简介:基于Python-Django构建的多功能Web安全渗透测试工具,集成漏洞检测、目录识别、端口扫描、指纹识别、域名探测、旁站探测与信息泄露检测等能力,形成从资产收集、信息收集到风险分析、漏洞验证的完整评估链路,适合安全测试人员、Web开发者及Django学习者用于安全研究与项目实战。资源包共2000个文件,以275个Python源码文件为核心,附带1325个SVG图标、71个CSS与67个JS前端资源、88个Log日志、43个PNG图片及34个HTML页面,另含8个XML、8个MD、4个JSON等配置与说明文档,整体约19.61MB。已有486人学习下载。压缩包结构清晰,除可直接运行的Django项目源码外,还配有使用说明,能帮助读者理解多模块Web工具的设计思路,掌握渗透测试流程中各环节的实现方法,并可作为二次开发的基础框架。

1. 从靶场到实战:Django为什么能当渗透测试工具的骨架

渗透测试工具见得多了,大多是单文件脚本:扫码跑完输出一个JSON就结束。可一旦要同时管理多个目标、多类漏洞、多轮复测,脚本的短板就暴露了——没有持久化,没有任务队列,更没人给你做Web界面。这个基于Python-Django的多功能Web安全渗透测试工具,把扫描引擎、漏洞管理、任务调度和报表展示收进同一个Django工程,用ORM当数据库层,用App当模块边界,扫描结果直接落到MySQL里。白帽子做复测要的是能留痕、能出报告、能反复对比的载体,这正是它解决的问题。适合需要快速搭内部安全测试平台、又不想从零手写管理前端的团队参考,也适合研究Django工程结构划分的人读源码。

2. 模块化拆解:用Django App隔离扫描引擎与数据模型

2.1 按功能边界拆分App,而不是按代码量

刚接触这个项目时,最值得看的是它对Django工程的分层方式。整个项目没有把所有功能塞进一个views.py,而是按扫描类型拆分App——subdomain、portscan、sqli、xss、task各占一个目录,每个App里放着与它相关的models、views,以及独立的检测逻辑模块detector.py。这样做的好处是新增一种扫描类型时不动旧代码,加一个新App即可;单个App处理逻辑出错也不影响其他模块加载。

在创建新模块时,先执行python manage.py startapp sec_sqli这样的命令生成骨架,再把检测函数按「输入目标、输出结果」的接口方式写进detector.py,最后在admin.py注册模型即可出现在后台。整个工程不用改别人的文件,依赖边界是从目录结构上物理保证的。

我一般会特别注意App之间的依赖方向。这里我比较认同的规范是:数据模型可以跨App引用,但检测逻辑不要互相调用。比如portscan会读取subdomain解析出来的host列表,但portscan不直接调用subdomain的解析函数,而是通过共享的task记录拿到目标。这样后续把扫描模块抽成Celery worker时,不需要重构依赖。

2.2 核心Model设计:Task、Vuln、ScanLog

看这个项目的models.py,核心数据表设计是三层:Task(扫描任务)、Vuln(漏洞记录)、ScanLog(扫描日志)。一个任务对应多条漏洞记录和日志,任务删掉之后级联清理,不留孤儿数据。

from django.db import models class Task(models.Model): target = models.CharField(max_length=255, db_index=True) scan_type = models.CharField(max_length=50) status = models.CharField(max_length=20, default='pending') created_at = models.DateTimeField(auto_now_add=True) started_at = models.DateTimeField(null=True, blank=True) finished_at = models.DateTimeField(null=True, blank=True) class Meta: ordering = ['-created_at'] class Vuln(models.Model): task = models.ForeignKey(Task, related_name='vulns', on_delete=models.CASCADE) vuln_name = models.CharField(max_length=100) url = models.URLField(max_length=500) param = models.CharField(max_length=100, blank=True) severity = models.CharField(max_length=10) details = models.TextField(blank=True) discovered_at = models.DateTimeField(auto_now_add=True) class ScanLog(models.Model): task = models.ForeignKey(Task, related_name='logs', on_delete=models.CASCADE) module = models.CharField(max_length=50) message = models.TextField() level = models.CharField(max_length=10, default='INFO') created_at = models.DateTimeField(auto_now_add=True)

这段设计的核心是外键关联的读写路径:Vuln通过Task外键归属扫描任务,ScanLog记录过程信息。db_index加在target上,是因为按目标查询历史任务是最频繁的操作,没有索引的话数据量上来之后会走全表扫描。severity字段用字符串而不是整数,是因为漏洞评级在展示层要映射颜色和排序,字符串枚举比int更直观;它的取值不限于high/medium/low,子域名这类信息类结果用info标记,报表期单独折叠展示。

另一个要留意的点是on_delete=models.CASCADE的语义:删任务时连带删漏洞和日志,避免数据库残留孤儿数据。如果之后要做审计归档,可以把CASCADE改成SET_NULL并保留task_id字段,这取决于你的数据保留策略。

2.3 扫描参数的集中管理

扫描参数散落在代码里是这类项目最常见的痛点。这个项目把超时时间、并发数、payload路径放进了settings.py的SCAN_CONFIG字典:

SCAN_CONFIG = { 'timeout': 10, 'max_threads': 20, 'max_redirects': 3, 'dirwordlist': 'wordlist/dirs.txt', 'payloads': 'wordlist/sqli.txt', 'fake_user_agent': True, }

这样做的理由是:同一个扫描逻辑,在互联网扫描和内网靶场扫描,超时和并发参数完全不一样。线上目标并发通常要降到5以下,靶场可以放开到30。参数集中在settings里,运维阶段改配置不用碰代码。下面这张表是我在这些参数上常用的取值组合:

参数作用范围互联网目标靶场环境
timeout单请求超时时间10s3s
max_threads全局并发上限520-30
max_redirects跟随跳转次数03
fake_user_agent是否随机UATrueFalse

顺带一提,fake_user_agent默认开是合理的,因为工具要面对真实站点。但靶场环境里建议关掉,理由很实际:靶场日志要的是可复现性,随机UA会让两次扫描结果之间失去对比意义。

3. 核心扫描模块实现:从子域名收集到SQL注入检测的完整链路

3.1 子域名收集:DNS解析与字典选择

子域名收集模块用的是字典爆破加DNS解析,没有引入Sublist3r这类完整实现,而是自己维护了一份常用子域名词典,通过dnspython做A记录解析:

import dns.resolver def collect_subdomains(domain, wordlist, timeout=3): found = [] resolver = dns.resolver.Resolver() resolver.timeout = timeout resolver.lifetime = timeout for word in wordlist: sub = f"{word}.{domain}" try: answers = resolver.resolve(sub, 'A') found.append((sub, answers[0].address)) except dns.resolver.NXDOMAIN: continue except dns.resolver.NoAnswer: continue except Exception: continue return found

NXDOMAIN和NoAnswer分开捕获是有意的:NXDOMAIN说明子域不存在,NoAnswer说明域名存在但没有A记录,后者可能指向CDN或内部服务,值得单独记到日志里。我一般会把NoAnswer的域名单独存一份,因为很多只配了MX记录的内部系统域名会从这里浮出来。

逻辑上要先对解析出的IP去重,再排后续的端口扫描任务,同一IP上的多个子域可以合并探测,减少重复连接。如果目标域名的字典很大,这个串行循环要加并发控制,做法见3.2。

3.2 端口扫描:socket并发与状态码解读

端口扫描没有调系统nmap,而是用socket连接测试自己实现,目的很直接:不依赖目标机器上装了什么,也方便在Windows开发环境里直接跑。核心是一个ThreadPoolExecutor:

import socket from concurrent.futures import ThreadPoolExecutor, as_completed def port_scan(host, ports, timeout=1.0, max_workers=100): def test(port): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) result = sock.connect_ex((host, port)) sock.close() return port if result == 0 else None open_ports = [] with ThreadPoolExecutor(max_workers=max_workers) as executor: futures = {executor.submit(test, p): p for p in ports} for f in as_completed(futures): port = f.result() if port: open_ports.append(port) return sorted(open_ports)

connect_ex的返回值是关键:0表示连接成功,其他值要看具体errno——113是拒绝连接,110是超时。超时和拒绝都算closed的话,这个探测速度很快,但公网目标上max_workers最好不要用函数默认的100,实际调用时从SCAN_CONFIG['max_threads']取,公网目标降到20。原因是目标防火墙对高频连接非常敏感,扫快了直接封IP,扫描任务反而跑不完。

端口字典我一般分两档:全端口(1-65535)和常见端口(FTP 21、SSH 22、Telnet 23、SMTP 25、DNS 53、HTTP 80、HTTPS 443、RDP 3389这类)。对Web安全测试来说,常见端口档位通常就够用了。

3.3 目录爆破:基于状态码与响应大小的识别

目录爆破模块更像安全人员手感的体现——它不只看HTTP状态码通不通,还要看响应内容的区分度。这个模块会先请求一次目标根路径,拿到基准响应大小和状态码,然后用字典逐路径探测:

import requests def dir_brute(base_url, wordlist, timeout=10): session = requests.Session() session.headers['User-Agent'] = 'Mozilla/5.0' baseline = session.get(base_url, timeout=timeout) results = [] for line in wordlist: path = line.strip() if not path: continue url = f"{base_url.rstrip('/')}/{path.lstrip('/')}" try: r = session.get(url, timeout=timeout, allow_redirects=False) similar = abs(len(r.content) - len(baseline.content)) / max(len(baseline.content), 1) if r.status_code in (200, 301, 302, 403) and similar > 0.15: results.append({'url': url, 'status': r.status_code, 'size': len(r.content)}) except requests.RequestException: continue return results

allow_redirects=False是这里容易忽略的一个决策。默认情况下requests会跟随302跳转,结果就是你误把登录页跳转后的内容当成目录内容记录下来。关闭重定向后,302状态码本身就有意义——它通常说明该路径存在且做了权限控制。

similar阈值的含义是响应体大小与基准的差异超过15%才算有效发现,这个阈值用来过滤SPA应用那种所有路径都返回同一份index.html的情况。如果目标是Vue或React写的单页应用,所有路径返回同一HTML,阈值建议提到0.3,否则会刷出一堆雷同结果。

3.4 SQL注入与XSS检测:payload的结构化表达

SQL注入检测用的是「报错特征+响应时间差」混合策略,不逐字符布尔盲注,因为工具场景里那样太慢了。检测分两步:先发payload看响应里是否有数据库报错特征,再对比响应时间是否显著高于基线:

ERROR_PATTERNS = [ r'syntax error', r'unclosed quotation mark', r'You have an error in your SQL syntax', r'Warning: mysql_', r'ORA-[0-9]{5}', r'SQLServer JDBC Driver', r'PostgreSQL.*ERROR' ] def test_sqli(url, param, payloads, baseline): for payload in payloads: params = {param: payload} start = time.time() r = requests.get(url, params=params, timeout=15) elapsed = time.time() - start if any(re.search(p, r.text, re.I) for p in ERROR_PATTERNS): return {'param': param, 'payload': payload, 'elapsed': round(elapsed, 3)} if elapsed > baseline * 3: return {'param': param, 'payload': payload, 'type': 'time-based', 'elapsed': round(elapsed, 3)} return None

params=传参而不是手动拼接URL,requests会自动做URL编码,避免单引号、空格这类字符在请求里被截断。时间盲注的判定阈值按根路径常规请求耗时的3倍取,具体数值从扫描开始前的一次采样请求中获得。

SQL注入payload这块维护一张小表,扫描时按目标数据库类型筛选:

数据库类型报错特征时间盲注函数
MySQLYou have an error in your SQL syntaxSLEEP(5)
SQL ServerSQLServer JDBC DriverWAITFOR DELAY '0:0:5'
OracleORA-[0-9]{5}DBMS_PIPE.RECEIVE_MESSAGE
PostgreSQLPostgreSQL.*ERRORpg_sleep(5)

XSS检测分反射型和DOM型。反射型是把<img src=x onerror=alert(1)>这类短payload放进参数,拿响应正文做正则匹配,看payload是否原样回显且未被HTML编码。这里有一个细节:不能只搜完整的<script>标签,很多页面会把<转义成&lt;,真正能利用的场景多是这种不带闭合标签的短payload。DOM型则要看前端JS是否把url参数直接写进了innerHTML,这个靠静态特征扫,误报会比反射型高,需要人工复核。

4. 异步任务与扫描结果可视化:Celery队列与图表报表设计

4.1 用Celery把扫描任务变成异步队列

扫描任务不能放在Django请求里同步执行,一个端口扫描动辄几分钟,HTTP请求早就超时了。这个项目的做法是Celery加Redis做任务队列,视图层只负责提交任务,worker进程异步执行:

# sec_tool/tasks.py from celery import shared_task @shared_task def run_scan_task(task_id): task = Task.objects.get(id=task_id) task.status = 'running' task.started_at = timezone.now() task.save() if task.scan_type == 'subdomain': results = collect_subdomains( task.target, load_wordlist(SCAN_CONFIG['dirwordlist'])) for sub, ip in results: Vuln.objects.create( task=task, vuln_name='Subdomain', url=sub, details=f'resolved to {ip}', severity='info' ) task.status = 'finished' task.finished_at = timezone.now() task.save()

shared_task装饰器让任务定义不绑定具体的App,后续把任务抽到独立模块不用改代码。这里的关键配置是task_serializer和result_backend,要用JSON序列化器,默认的pickle有反序列化风险,worker一旦放在不可信网络里,pickle会直接变成代码执行入口。

# sec_tool/celery.py from celery import Celery app = Celery('sec_tool', broker='redis://127.0.0.1:6379/0') app.conf.update( task_serializer='json', result_serializer='json', accept_content=['json'], task_time_limit=1800, worker_max_tasks_per_child=50, )

task_time_limit设1800秒是防止某个扫描任务卡死在哪一步请求上;worker_max_tasks_per_child=50是让worker每处理完50个任务重启一次,解决requests连接池长期复用后的内存增长。这两个参数是长期爬取型worker最容易忽略的两个坑,也是接手这类项目第一个要检查的配置。

配置项作用常用值
task_serializer任务参数序列化格式json
task_time_limit单个任务硬超时1800s
worker_max_tasks_per_childworker处理任务数上限后重启50
task_acks_late是否在任务执行后再确认ackTrue

task_acks_late这里要说明一下:默认False是任务拿来就确认,worker崩了任务就丢了;调成True后,worker崩溃会重新把任务投递给其他worker。代价是同一个任务有可能被执行两次,所以扫描模块要尽量做成幂等的——重复扫描同一目标最多是多跑一遍,不影响数据一致性。

4.2 实时进度轮询与结果图表

结果可视化用的是Django模板加Chart.js,没上重型前端框架。任务列表页通过轮询接口拿进度,每秒请求一次,后端返回当前已扫数量、命中数量和最近一条日志:

def task_progress(request, task_id): task = Task.objects.prefetch_related('vulns').get(id=task_id) count = task.vulns.count() return JsonResponse({ 'id': task.id, 'status': task.status, 'vuln_count': count, 'last_log': task.logs.last().message })

prefetch_related('vulns')是为了避免在页面同时显示所有漏洞时产生查询N+1,这个优化能把漏洞渲染页的SQL查询数量从「漏洞数+1」压到两条。漏洞列表按severity字段分组统计后,前端用Chart.js的doughnut图展示high/medium/low占比,info级别的子域名结果默认折叠,展开才看。

报表导出做的是CSV而不是PDF,原因很实际:安全团队成员拿到CSV后习惯自己筛选排序,PDF只会增加中间环节。导出接口用StreamingHttpResponse:

def export_csv(request, task_id): task = Task.objects.get(id=task_id) response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = f'attachment; filename="scan_{task.id}.csv"' writer = csv.writer(response) writer.writerow(['vuln_name', 'url', 'param', 'severity', 'details']) for vuln in task.vulns.all(): writer.writerow([vuln.vuln_name, vuln.url, vuln.param, vuln.severity, vuln.details]) return response

注意两个细节:HttpResponse默认的Content-Type是text/html,浏览器会直接显示CSV文本而不是下载,必须显式设成text/csv;中文描述在Excel里打开乱码是经典问题,writer写入前把字段统一转成utf-8-sig编码能规避。这个是Python导出CSV在国际化上最常见的坑。

图表展示本身不是重点,重点是我会看Top 10漏洞URL的分布。当Top 10被同一个页面参数占满时,说明扫描参数提取逻辑过于单一,比如只取了GET参数而忽略POST表单字段。这个判断能让工具从「能用」往「好用」挪一步。

5. 生产落地与误报治理:Waitress+nginx部署及误报排查

5.1 Waitress+nginx双环境部署要点

项目要跑在Windows和Linux两种环境下,uWSGI在Windows上编译麻烦,gunicorn又是POSIX-only,所以生产服务选了Waitress。Waitress是纯Python实现的WSGI服务器,不需要编译C扩展。

pip install waitress waitress-serve --listen=0.0.0.0:8000 config.wsgi:application

前端放nginx,职责是静态文件服务和请求分发,Django的STATIC_ROOT目录通过collectstatic收集后交给nginx直接返回,应用请求则转给Waitress的8000端口处理。注意用nginx -t验证配置后再reload,避免改坏线上配置导致全站不可访问。

部署层面的顺序一般是:先collectstatic,再启动Waitress,最后reload nginx。顺序错了的话静态资源会404,浏览器控制台一片红的报错很容易误导排查方向。Celery worker单独用supervisor托管,Redis先启动,否则worker起不来。

5.2 误报治理:三次采样、WAF识别和人工确认闭环

回到前面挖的坑,SQL注入和目录爆破的误报是这个工具实际使用中最影响信任感的部分。第一个误报来源是WAF拦截页面:很多WAF的返回报文故意带SQL语法错误字样用于反探测,正则特征会直接命中。处理办法是给Vuln记录加一个source字段,记录命中时的响应Server值,凡是Server标记为WAF/CDN的自动降级并单独分组展示。

第二个误报来源是时间盲注的单次请求判定。正确的做法是同一个payload重复三次,取中位数和基线比较,三次都在3倍以上才判定存在:

samples = [] for _ in range(3): start = time.time() r = requests.get(url, params=params, timeout=15) samples.append(time.time() - start) median = sorted(samples)[1] if median > baseline * 3: return {'param': param, 'payload': payload, 'median': round(median, 3)}

目录爆破的跨目录递归扫描会产生大量重复记录。我一般维护一个seen_paths集合,用path加上status_code加content_length做联合去重键,同一组合出现过就跳过,能把结果里的重复项压掉一大半。

最后是这个项目我最认可的一个设计:Vuln列表每条记录上有confirm和false_positive两个操作按钮,标记结果写回数据库,再次扫描同一目标时自动过滤已标记的误报记录。这个「人工反馈进入扫描逻辑」的小闭环,让工具从一次性的扫描脚本变成了能越用越准的检测平台,也是它区别于普通脚本工具的核心所在。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 19:48:35

Numba 使用 FAQ 全解:安装排障、编程技巧与性能优化实战指南

Numba 使用 FAQ 全解&#xff1a;安装排障、编程技巧与性能优化实战指南 【免费下载链接】numba NumPy aware dynamic Python compiler using LLVM 项目地址: https://gitcode.com/gh_mirrors/nu/numba 导读&#xff1a;本文以 Numba 官方用户手册的 FAQ 章节 为骨架&…

作者头像 李华
网站建设 2026/9/23 19:44:04

DeepSeek轻量级VRP模型:物流路径优化实战指南

简介&#xff1a;本资源是一份面向物流行业技术从业者与AI模型开发者的技术实践指南&#xff0c;聚焦DeepSeek大模型在路径优化场景的落地应用&#xff0c;解决传统物流中运输迂回、空驶率高、调度效率低等降本增效痛点。文档共26页PDF&#xff0c;完整覆盖从行业需求分析、数据…

作者头像 李华
网站建设 2026/9/23 19:41:55

Vibe-Trading Tushare new_share 新股接口实战指南

Vibe-Trading Tushare new_share 新股接口实战指南 【免费下载链接】Vibe-Trading "Vibe-Trading: Your Personal Trading Agent" 项目地址: https://gitcode.com/GitHub_Trending/vi/Vibe-Trading new_share 是 Tushare 的新股上市列表接口&#xff0c;Vibe-…

作者头像 李华
网站建设 2026/9/23 19:41:36

基于OpenCV的银行卡卡号识别:图像处理与模板匹配实战

简介&#xff1a;基于OpenCV的银行卡识别系统Python项目&#xff0c;面向计算机视觉初学者与金融科技开发者&#xff0c;提供从图像预处理、字符定位到识别的完整工程方案。资源共43个文件&#xff0c;压缩包10.31MB&#xff0c;包含10个Python脚本&#xff08;app.py、darknet…

作者头像 李华
网站建设 2026/9/23 19:40:53

洛谷基础篇zip校验与PDF转Markdown刷题全指南

简介&#xff1a;围绕《洛谷深入浅出程序设计竞赛&#xff08;基础篇&#xff09;》整理的源码与配套文档&#xff0c;面向正在备战程序设计竞赛、或按基础篇学习算法与数据结构的读者。压缩包共 91 个文件&#xff0c;以 87 个 C 源文件为主&#xff0c;另含少量使用说明、构建…

作者头像 李华