news 2026/10/24 16:35:28

基于Python与Django的自动化Web漏洞扫描与安全检测系统实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Python与Django的自动化Web漏洞扫描与安全检测系统实战

简介:这是一套面向网络安全初学者与渗透测试人员的Web漏洞扫描系统源码,基于Python与Django开发,集成爬虫引擎与自定义规则库,可对静态页面和动态交互式Web应用进行深度安全扫描、漏洞发现与风险评估。资源包共202个文件,约1.16MB,以118个Python源码文件为核心,辅以20个HTML模板、9个JavaScript脚本、7个CSS样式及3个SQL文件构建前后端,另含字典文件、说明文档与数据库文件,结构完整、模块清晰。已有91人学习下载。读者可获得一套可直接部署运行的完整扫描工具,通过爬虫遍历页面链接模拟攻击行为,并借助可扩展的规则库定制扫描策略;系统提供任务管理、结果查看与评估报告生成界面,附带使用说明文档,便于理解扫描流程、规则配置与多层防护机制,适合用于课程设计、安全实验或二次开发参考。

1. 从一次被 SQL 注入打穿的登录接口说起

去年帮一个朋友看他的 Django 后台,登录接口被人用' or 1=1 --直接绕过去了,日志里连个像样的告警都没有。他问我:有没有办法在代码提交前就自动把这类问题扫出来?这就是「基于 Python 与 Django 构建的自动化 Web 应用漏洞扫描与安全检测系统」要解决的事——它不是又一个商业扫描器的替代品,而是一套你能读源码、能改规则、能塞进 CI 的自建评估工具。核心由三块拼起来:一个爬虫引擎负责把站点的 URL、表单、参数摸清楚,一个检测调度层把请求打出去并比对响应,一个自定义规则库让你把业务特有的坑写成可复用的检查项。适合谁?手里有 Django 或其他 Web 项目、想在内网做持续安全自检、又不放心把流量交给外部平台的团队。下面我按自己搭过的一版讲,能跑通、能改、能落地。

2. 爬虫引擎怎么把站点结构摸干净

2.1 为什么不用现成的爬虫框架直接上

很多人第一反应是scrapy或requests-html一把梭。我试过,翻车点在于:漏洞扫描要的不是「抓内容」,而是「抓可注入的入口」。一个页面里真正有价值的不是正文,是<form>的 action、method、每个 input 的 name,是 URL query 里的参数名,是Set-Cookie和自定义 header。通用爬虫把这些当噪音过滤掉了,你还得反过来再解析一遍。所以我的做法是自己写一个轻量爬虫,基于requests+BeautifulSoup,只提取「入口点」结构,输出统一的数据模型。这样后面规则库拿到的就是干净的{url, method, params, cookies},不用再猜。

另一个原因是可控性。扫描器最怕爬虫失控,把人家站点爬崩。自己写就能精确控制并发数、请求间隔、最大深度,出问题也好定位。

2.2 入口点提取的核心代码

import requests from bs4 import BeautifulSoup from urllib.parse import urljoin, urlparse, parse_qs class EntryPoint: def __init__(self, url, method="GET", params=None, cookies=None): self.url = url self.method = method.upper() self.params = params or {} # 参数名 -> 默认值 self.cookies = cookies or {} def extract_entry_points(base_url, html, current_url): """从单个页面 HTML 中抽取表单和链接入口""" soup = BeautifulSoup(html, "html.parser") points = [] # 1. 表单:method、action、所有 input 的 name for form in soup.find_all("form"): action = form.get("action") or current_url method = form.get("method", "get") params = {} for inp in form.find_all(["input", "textarea", "select"]): name = inp.get("name") if name: params[name] = inp.get("value", "") points.append(EntryPoint(urljoin(base_url, action), method, params)) # 2. 带 query 的链接:把参数名抽出来 for a in soup.find_all("a", href=True): full = urljoin(base_url, a["href"]) parsed = urlparse(full) if parsed.query: qs = parse_qs(parsed.query) params = {k: v[0] for k, v in qs.items()} points.append(EntryPoint(full.split("?")[0], "GET", params)) return points

逻辑说明:extract_entry_points只做一件事——把页面里所有「能带参数请求」的地方结构化。表单部分保留 method 和全部 input name,链接部分只保留带 query 的,因为纯静态链接没有注入面。参数说明:base_url用于拼接相对路径,current_url是当前页地址,防止 action 为空时丢目标。params里存默认值,后面规则库做 fuzz 时会替换成 payload。

2.3 去重、限速与深度控制

爬虫最容易出的问题是重复请求同一个 URL 几百次。我用一个set存(url, method, frozenset(params.keys()))作为指纹去重,参数名集合相同就认为是同一个入口,不重复扫。限速用time.sleep加随机抖动,别用固定间隔,固定间隔反而容易被 WAF 识别成扫描器。深度控制上,我只爬同域、最多三层,外链一律不跟——扫描器跑去爬别人站点是事故。

import time, random from collections import deque def crawl(start_url, max_depth=3, delay=(0.5, 1.5)): seen = set() queue = deque([(start_url, 0)]) all_points = [] while queue: url, depth = queue.popleft() if depth > max_depth or url in seen: continue seen.add(url) try: resp = requests.get(url, timeout=8) except requests.RequestException: continue points = extract_entry_points(start_url, resp.text, url) for p in points: fp = (p.url, p.method, frozenset(p.params.keys())) if fp not in seen: seen.add(fp) all_points.append(p) # 只跟同域链接继续爬 for a in BeautifulSoup(resp.text, "html.parser").find_all("a", href=True): nxt = urljoin(start_url, a["href"]) if urlparse(nxt).netloc == urlparse(start_url).netloc: queue.append((nxt, depth + 1)) time.sleep(random.uniform(*delay)) return all_points

参数说明:max_depth=3是经验值,再深收益递减还容易触发风控;delay给一个区间让请求间隔随机化。seen同时承担 URL 去重和入口指纹去重,注意这里我把两者混在一个 set 里,实际项目里建议分开,否则 URL 和指纹可能碰撞。

3. 检测调度层:把 payload 打出去并判断结果

3.1 检测器的抽象设计

爬虫给你一堆入口点,接下来要决定「用什么 payload、怎么判断命中」。我的做法是每个漏洞类型写一个 Detector 类,统一接口check(entry_point) -> Finding or None。这样加新规则就是加一个类,不用动调度逻辑。常见类型先覆盖四类:SQL 注入、XSS、命令注入、敏感信息泄露。别一上来追求全覆盖,先把这四类做扎实,误报率压下去比数量重要。

class BaseDetector: name = "base" payloads = [] def check(self, ep): raise NotImplementedError class SQLiDetector(BaseDetector): name = "sqli" payloads = ["'", "\"", "' or '1'='1", "1' AND SLEEP(3)--"] def check(self, ep): for payload in self.payloads: data = {k: payload for k in ep.params} try: if ep.method == "POST": resp = requests.post(ep.url, data=data, timeout=10) else: resp = requests.get(ep.url, params=data, timeout=10) except requests.RequestException: continue # 基于报错特征 + 时间盲注双重判断 if any(sig in resp.text.lower() for sig in ["sql syntax", "mysql_fetch", "ora-", "sqlite"]): return {"type": self.name, "url": ep.url, "payload": payload, "evidence": "error-based"} return None

逻辑说明:check遍历 payload,把入口点的每个参数都替换成 payload 发一次。判断命中用报错特征匹配,这是最稳的初筛。参数说明:timeout=10给足时间,因为后面要加时间盲注;data和params分别对应 POST 和 GET。注意这里每个参数都单独替换成 payload,而不是全部一起替换,否则无法定位是哪个参数有问题。

3.2 时间盲注的判断要留后悔药

报错注入好判断,时间盲注才是玄学。SLEEP(3)打出去,响应慢了 3 秒,但你没法确定是数据库睡了还是网络抖了。我的做法是打两次:一次带SLEEP(3),一次带SLEEP(0),比较两次响应时间差,差值接近 3 秒才判定命中。这样能过滤掉大部分网络波动导致的误报。

import time def time_based_check(ep, payload_sleep, payload_normal): t0 = time.time() requests.get(ep.url, params={k: payload_sleep for k in ep.params}, timeout=15) t_sleep = time.time() - t0 t0 = time.time() requests.get(ep.url, params={k: payload_normal for k in ep.params}, timeout=15) t_normal = time.time() - t0 return (t_sleep - t_normal) > 2.5

参数说明:阈值2.5是留了余量,SLEEP(3)实际差值通常在 2.8 到 3.2 之间,网络好的时候 2.5 足够。如果目标站点本身响应就慢,这个阈值要往上调,否则全是误报。

3.3 调度与并发

调度层用concurrent.futures.ThreadPoolExecutor控制并发,别用进程池,IO 密集场景线程更合适。并发数我一般设 5 到 10,再高容易把目标打挂或者触发封禁。每个入口点跑完所有 detector,结果汇总成 Finding 列表。

from concurrent.futures import ThreadPoolExecutor, as_completed def run_scan(entry_points, detectors, workers=8): findings = [] def scan_one(ep): local = [] for det in detectors: result = det.check(ep) if result: local.append(result) return local with ThreadPoolExecutor(max_workers=workers) as pool: futures = {pool.submit(scan_one, ep): ep for ep in entry_points} for fut in as_completed(futures): findings.extend(fut.result()) return findings

参数说明:workers=8是内网扫描的稳妥值,公网目标建议降到 3 到 5。as_completed保证结果按完成顺序收集,不用等最慢的那个。

4. 自定义规则库:把业务特有的坑写成检查项

4.1 规则用 YAML 描述,别硬编码

检测逻辑写死在 Python 里,改一条规则要动代码、要重新部署,这在实战里很痛苦。我的做法是把「匹配什么、怎么匹配、命中后报什么」抽成 YAML 规则文件,Python 只负责加载和执行。这样安全同学不用懂代码也能加规则。

- id: sensitive-file-exposure name: 敏感文件泄露 severity: high match: type: path patterns: - "/.git/config" - "/.env" - "/backup.sql" judge: status_code: 200 body_contains: ["DB_PASSWORD", "SECRET_KEY"]

逻辑说明:match.type=path表示这条规则针对路径做探测,patterns是要尝试的路径列表。judge定义命中条件——状态码 200 且响应体包含敏感关键字。参数说明:severity用于结果分级,id全局唯一,方便后续做白名单和去重。

4.2 规则加载与执行引擎

import yaml class RuleEngine: def __init__(self, rule_path): with open(rule_path, encoding="utf-8") as f: self.rules = yaml.safe_load(f) def run(self, base_url): findings = [] for rule in self.rules: if rule["match"]["type"] != "path": continue for pattern in rule["match"]["patterns"]: url = base_url.rstrip("/") + pattern try: resp = requests.get(url, timeout=8) except requests.RequestException: continue judge = rule["judge"] if resp.status_code != judge.get("status_code", 200): continue if any(kw in resp.text for kw in judge.get("body_contains", [])): findings.append({ "rule_id": rule["id"], "severity": rule["severity"], "url": url, }) return findings

参数说明:rule_path指向 YAML 文件,base_url是扫描目标根地址。judge.status_code默认 200,因为敏感文件通常返回 200 而不是 404。body_contains是关键字列表,命中任意一个就算。注意这里没做并发,规则数量少的时候够用,规则上百条要改成线程池。

4.3 规则库怎么和爬虫结果联动

规则库有两种用法:一种是独立探测(比如上面扫敏感文件),另一种是挂在爬虫发现的入口点上做参数级检查。后者需要规则里声明match.type=param,然后由调度层把规则转成 payload 注入到入口点参数里。这样一套规则库既能扫固定路径,又能扫动态参数,覆盖面就上来了。

5. 避坑与排查:那些让我熬夜的翻车现场

5.1 扫描器把自己扫进了黑名单

现象:扫到一半目标站点全部返回 403,换 IP 也不行。原因:并发太高加固定间隔,被 WAF 识别成攻击流量。解决:并发降到 3,请求间隔改成随机 1 到 3 秒,并且给请求加上正常的User-Agent和Referer,别用默认的python-requests。

5.2 时间盲注误报一片

现象:报告里几十条 SQL 注入,人工一看全是误报。原因:目标站点本身响应波动大,SLEEP(3)的差值判断被网络抖动干扰。解决:改成双请求对比(带 sleep 和不带 sleep 各一次),阈值提到 2.5 秒以上,并且对同一入口点重复测两次,两次都命中才报。

5.3 爬虫陷入无限循环

现象:扫描任务跑了半小时没结束,日志里同一个 URL 反复出现。原因:页面里有?page=1这种自引用链接,深度控制没生效。解决:入口指纹去重加上 URL 规范化(去掉 fragment、排序 query 参数),并且对同一路径的 query 参数组合数量设上限,超过就跳过。

5.4 规则库 YAML 格式错误导致整个扫描挂掉

现象:加了一条新规则后扫描直接报错退出。原因:YAML 缩进用了 Tab,解析失败。解决:加载规则时用try/except包住,单条规则解析失败只跳过那条并打日志,不要让整个引擎崩掉。另外统一用空格缩进,编辑器里把 Tab 转空格打开。

5.5 POST 表单漏扫

现象:爬虫抓到了表单,但扫描结果里没有 POST 类型的漏洞。原因:extract_entry_points里表单 method 默认取了get,但有些表单没写 method 属性,实际是 POST。解决:method 为空时默认按 GET 处理没问题,但要额外检查表单是否有enctype或隐藏字段,有的话补一次 POST 尝试。

6. 把扫描器塞进 CI:一个 Django 项目的落地技巧

前面讲的都是扫描器本身,但真正让它产生价值的是「自动跑起来」。我一般把它做成 Django 的 management command,这样能直接复用项目的配置和数据库,不用额外维护一套环境。

# yourapp/management/commands/security_scan.py from django.core.management.base import BaseCommand from scanner.crawler import crawl from scanner.detectors import SQLiDetector, XSSDetector from scanner.rules import RuleEngine class Command(BaseCommand): help = "对指定目标执行安全扫描" def add_arguments(self, parser): parser.add_argument("target", type=str, help="扫描目标根 URL") parser.add_argument("--rules", type=str, default="rules/builtin.yaml") def handle(self, *args, **options): target = options["target"] points = crawl(target, max_depth=3) self.stdout.write(f"发现入口点 {len(points)} 个") detectors = [SQLiDetector(), XSSDetector()] findings = run_scan(points, detectors, workers=5) engine = RuleEngine(options["rules"]) findings.extend(engine.run(target)) for f in findings: self.stdout.write(self.style.WARNING( f"[{f.get('severity', 'unknown')}] {f.get('type', f.get('rule_id'))} @ {f['url']}" )) self.stdout.write(self.style.SUCCESS(f"扫描完成,共 {len(findings)} 条发现"))

逻辑说明:add_arguments让命令接受目标 URL 和规则文件路径,handle里串起爬虫、检测器、规则引擎三步。参数说明:--rules默认指向内置规则文件,CI 里可以传自定义规则路径。输出用 Django 的self.style上色,方便在 CI 日志里一眼看到高危项。

接进 CI 就是在流水线里加一步python manage.py security_scan http://staging.example.com --rules rules/ci.yaml,发现高危就exit 1阻断发布。这里有个血泪经验:别把扫描器指向生产环境,CI 里只扫 staging,生产环境用只读的被动检查。另外规则文件要跟着代码走版本控制,每次加规则都走 PR 评审,避免有人随手加一条误报规则把流水线搞挂。

验证扫描器本身有没有用,我的习惯是拿一个故意留了漏洞的靶场项目跑一遍,看能不能把已知的 SQL 注入、XSS、敏感文件全部报出来,同时误报控制在可接受范围。每次改完检测逻辑都重跑一遍靶场,这比看代码靠谱。这套东西我前后迭代了大半年,最大的体会是:扫描器的价值不在覆盖多少漏洞类型,而在误报率够低、规则够好改、能持续跑。希望帮到你。

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

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

AI Native团队研发范式重构:从开发环境到Agent落地的实践指南

1. 先想清楚&#xff1a;AI Native团队到底“Native”在哪 1.1 别把AI Native做成“AI传统开发” 我接手这个团队的技术底座改造时&#xff0c;团队负责人跟我说了一句话&#xff1a;“我们想成为AI Native团队&#xff0c;但不知道从哪一步真正开始。”这句话我听了太多遍。绝…

作者头像 李华
网站建设 2026/10/24 16:35:00

游戏对象与资源管理:ECS架构与Addressable系统实战指南

1. 项目概述&#xff1a;为什么“游戏对象与资源管理”是引擎架构的生死线你写过一百个GameObject&#xff0c;却未必真正理解它在内存里怎么呼吸&#xff1b;你拖拽过上千次Prefab&#xff0c;但可能从没想过AssetDatabase背后那场持续数毫秒的资源争夺战。这期我们不聊渲染管…

作者头像 李华
网站建设 2026/10/22 16:33:46

DeepSeek Harness v0.2 桌面AI工作流实战搭建指南

1. 项目概述&#xff1a;这不是一个“安装软件”的教程&#xff0c;而是一次桌面AI工作流的实战重建 DeepSeek Harness v0.2 桌面端上手——这个标题里藏着三个被多数人忽略的关键信号&#xff1a;“Harness”不是客户端&#xff0c;是调度中枢&#xff1b;“v0.2”意味着它尚在…

作者头像 李华
网站建设 2026/10/23 20:53:47

隔离内网AI Agent实战:从0搭建离线部署最小闭环

1. 写在前面&#xff1a;为什么要在隔离内网里跑 Agent年初我接手了一个数据合规要求极高的项目&#xff0c;客户网络环境是物理隔离的内网&#xff0c;业务系统之间走区隔好的专网&#xff0c;终端侧基本不暴露公网出口。在这种环境里&#xff0c;大家最自然的第一反应是“先别…

作者头像 李华
网站建设 2026/10/18 17:34:17

AI 应用底座选型与部署:QuickBlue 实战避坑指南

最近好几个技术群里都在聊 QuickBlue&#xff0c;聊着聊着就绕回到一个老问题上&#xff1a;大模型能力越来越强&#xff0c;为什么企业内部真正跑起来的 AI 应用还是那么少&#xff1f;我自己的答案是&#xff0c;多数团队缺的不是模型&#xff0c;而是一个能把模型、数据、权…

作者头像 李华
网站建设 2026/10/19 3:31:11

游戏引擎基础架构拆解:从主循环到多线程与数据驱动

1. 开场&#xff1a;别一上来就啃渲染管线在决定写这个系列的时候&#xff0c;我一直在想第一篇文章应该从哪里切入。很多新手拿到游戏引擎源码或者想自行搭建一套引擎时&#xff0c;第一反应往往是扑向渲染、物理、UI这些看得见摸得着的系统&#xff0c;结果读了一圈源码之后脑…

作者头像 李华