Python Web漏洞扫描系统:从信息搜集到漏洞检测的完整实现
2026/9/11 21:48:39 网站建设 项目流程

简介:Web安全防护的起点是发现风险,渗透测试与漏洞扫描成为企业自查和攻防演练的核心手段。一个完整的扫描流程往往从信息搜集开始,涵盖子域名枚举、端口探测、指纹识别与目录爆破,这些侦察动作决定了后续漏洞检测的精准度。在技术实现上,基于Python生态的Flask框架可快速构建Web管理界面,配合Celery异步任务队列处理耗时扫描任务,结合SQLAlchemy与SQLite完成结果存储,形成一套可复用的工程化方案。针对SQL注入、XSS、目录泄露等经典漏洞,插件化检测机制能灵活扩展,并生成结构化报告。这类系统适用于小规模资产自查、CTF训练和安全教学,在真实业务中也能作为半自动化的漏洞发现工具,其架构设计与实现思路为深入学习安全自动化提供了有效参考。

1. 项目背景与整体设计思路

1.1 为什么选这个题目,以及它解决的痛点

Web漏洞扫描系统这类题目在毕业设计里算是常青树了,原因很简单:它的技术栈清晰,涉及面广但不失控,既能体现Python编程能力、Flask Web开发能力,又能展示对网络安全基础知识的掌握程度,还特别容易把“工作量”做出来。选题阶段我就确定了一个原则:不能只做一个“能跑起来”的CRUD系统,而是要有真实的安全测试逻辑在里面,这样答辩才有东西可讲。

这个系统的核心目标可以拆成两条线:一是信息搜集,也就是扫描前的资产发现和情报收集;二是漏洞扫描,也就是对目标Web应用发起检测,找出SQL注入、XSS、目录泄露等常见风险点。两条线都围绕一个基于Flask的Web界面展开,用户输入目标域名或URL,系统自动执行一系列扫描任务,最后生成可查看、可导出的报告。它的应用场景很像一个简化版的安全测试平台,适合小规模甲方自查、CTF入门训练,也适合毕业设计展示完整的工程能力。

1.2 技术选型背后的考量:Flask、Celery与SQLite的组合

技术选型这块,我按“毕业设计该有的成熟度”来定,而不是一味追求生产级方案。后端框架选Flask而不是Django,是因为这个系统的业务逻辑以API为主,模板渲染只占一小部分,Flask的轻量特性让代码结构更可控,单文件能起的服务对新手也友好。前端则用原生HTML、CSS加少量JavaScript配合动态渲染,没有引入Vue或React,因为扫描任务本身需要轮询结果,用AJAX就够了,引入重型前端框架反而增加答辩时被追问的风险。

任务调度是一个关键点。扫描任务不是瞬时操作,端口扫描可能几十秒,目录爆破可能几分钟,如果直接在Flask的请求处理函数里同步执行,请求会被阻塞,浏览器一直转圈不说,超时后任务状态也丢了。我的方案是用Celery做异步任务队列,Broker用Redis。扫描任务提交后立即返回任务ID,前端通过轮询接口查询任务状态和结果。这套组合在真实项目和毕业设计里都是非常稳妥的选择,也能体现出具备分布式任务处理的思维能力。

还有个细节是数据库选型。我用的是SQLite加SQLAlchemy ORM,数据库表不多,分别是任务表、目标表、漏洞结果表、子域名信息表、端口信息表。SQLite的优点是不需要单独配置数据库服务,答辩演示时不用折腾环境,直接跑起来就是完整系统。如果后续真要扩展,SQLAlchemy切到MySQL也就是改一行URI的事。

1.3 系统模块划分与整体架构

这个系统的模块划分遵循“数据流驱动”的原则。用户输入目标URL后,系统按以下路径工作:

  1. 信息搜集模块接收目标,进行子域名枚举、IP解析、端口存活检测、Web指纹识别、目录/文件爆破;
  2. 搜集结果存入数据库;
  3. 漏洞扫描模块基于搜集到的Web指纹和URL列表,对探测到的每个Web服务执行漏洞检测插件;
  4. 扫描结果汇总,生成报告。

架构上我分了三层:任务接入层(Flask路由和API)、业务逻辑层(扫描引擎、插件加载器、报告生成器)、数据存储层(数据库和文件缓存)。这里要特别强调一件事:扫描引擎和Web层一定要解耦。我第一版代码把扫描逻辑直接写在路由函数里面,结果一跑长任务Flask就堵死,后来才改成独立模块加Celery异步化。所以做这个项目的同学,架构设计千万别省,否则后期返工成本极高。

2. 信息搜集模块:扫描之前的侦察工作

2.1 子域名枚举与IP信息收集

信息搜集模块是整个系统数据的第一来源,如果这步做不好,后续漏洞扫描就是无源之水。子域名枚举我实现了两种方式:基于字典的暴力枚举和基于搜索引擎接口的被动收集。

字典暴力枚举很好理解,就是构造一个常见子域名词典(admin、test、oa、api、mail这类高频前缀),然后对每个候选域名发起DNS解析请求,解析成功则说明该子域名存在。这里要注意DNS请求的频率控制,如果纯单线程跑几百个域名,速度很慢且容易触发目标DNS服务器的速率限制。我用了Python的concurrent.futures.ThreadPoolExecutor,线程数控制在20,实测几百条记录的枚举在一两分钟内能完成。解析库用的是dnspython的resolver,设置超时2秒,遇到超时或NXDOMAIN直接跳过。

被动收集这块我调用了两个公开接口:一个是通过搜索引擎的site语法查询子域,另一个是通过证书透明度日志平台查询。后者效果好很多,因为CA签发的证书里经常会带上所有子域,拿回来正则提取即可。这部分代码量不大,但答辩时是个亮点,因为证书透明度是近几年行业里比较前沿的信息搜集思路。

IP信息收集相对简单:域名解析拿到IP后,用IP归属查询接口标记地理位置和运营商,再尝试判断该IP是否属于CDN节点。判断CDN有个土办法:对同一个域名用不同公共DNS服务器分别解析,如果返回多个不同IP且在不同网段,大概率是CDN。这个逻辑在真实扫描中很重要,因为如果目标挂在CDN后面,直接扫描源站IP才有意义,扫CDN节点容易误判。不过考虑到目标规模,我最终只做了提示,没有做太深。

2.2 端口探测与Web指纹识别

端口探测这步我用了自己实现的TCP Connect扫描器。原理非常直接:用socket模块去连接目标的每个端口,如果连接成功,说明端口是开放的。扫描范围是常用的前1000个端口,包括80、443、22、21、3306、6379、27017等。要注意的是,TCP Connect扫描的缺点是会留下完整的连接日志,容易被IDS发现,但这是一个教学和演示性质的项目,不需要那么隐蔽,反倒是完整握手的好处是结果可靠,不容易误判。

针对开放端口上的服务,我做了服务指纹识别:通过发送特定探测数据包,比对返回的banner信息判断服务类型和版本。比如连上MySQL 3306端口,服务端通常会返回版本号的欢迎信息;连上Nginx的80端口,HTTP响应头里的Server字段会直接暴露版本。正则匹配这部分我维护了一个指纹库,跑通一个加一个,目前大概有几十条常见规则。Web指纹识别则是通过HTTP响应头、HTML元信息、Cookie特征、特定路径文件是否可访问来综合判断站点用的什么CMS或框架,比如WordPress的wp-content路径、ThinkPHP的X-Powered-By头、Spring Boot的Whitelabel Error Page特征等。

指纹识别对后续漏洞扫描的意义非常大。知道目标跑的是Apache还是Nginx,是直接决定用哪个漏洞检测插件的关键信息,避免发一些驴唇不对马嘴的无效请求。所以我在扫描引擎设计里,指纹识别结果会作为漏洞插件的触发条件之一。

2.3 目录扫描与爬虫模块的实现细节

目录扫描用的是经典的字典爆破思路。提前准备一份常见的目录字典,包含admin目录、backup目录、.git泄露目录、配置文件路径(.env、config.php.bak)、上传目录等,然后逐个发HTTP请求,通过状态码判断目录是否存在。这里有个经验:状态码不等于一切。很多站点对不存在的路径会返回200加一个自定义的404页面,反而对存在的目录返回403(不允许列出)或302(需要登录跳转)。所以判断逻辑不能只看200,要把403、302也当作“目录可能存在”来处理,编码时我设置了一个状态码白名单记录逻辑,宁可产生少量误报,也不能漏掉真实的敏感路径。

爬虫模块承担的是URL收集工作。我从目标首页开始,用requests加BeautifulSoup解析页面中的a标签和form标签,提取链接并去重,遇到相对路径则拼接为绝对URL。爬取深度控制为2层,单页链接上限设为200条,避免爬到外网或跑成无底洞。再通过robots.txt解析允许爬取的路径,作为URL集合的补充。爬到的链接会统一交给漏洞扫描模块去做参数检测,这比单独扫描域名根路径覆盖面大得多,因为它能挖到实际带参数的请求,SQL注入和XSS往往就藏在这些参数里。

3. 漏洞扫描模块:核心检测逻辑与插件化设计

3.1 为什么用插件化架构:扩展性与可维护性

漏洞扫描模块我做了插件化设计,这是整个系统里最能体现工程能力的地方。每一个漏洞检测逻辑都是一个独立的Python类,统一继承自一个基类。基类定义了execute方法、目标URL属性、漏洞等级属性和结果上报方法,子类只需要实现自己的检测逻辑。扫描引擎加载插件时,使用Python的pkgutil遍历插件目录,自动发现并注册所有可用插件。

这样设计的好处很明显。第一,新增漏洞检测能力不需要修改原有代码,直接在插件目录放一个新文件就行,系统重启后自动加载;第二,单个插件出问题时不会影响其他插件的执行,引擎在调用插件时会捕获异常并记录日志;第三,答辩时可以这样描述“高内聚低耦合的设计模式”,非常加分。

插件分类方面,我按照漏洞类型分成了注入类、跨站类、信息泄露类、配置缺陷类和其它类五个包。每个插件实现了两个方法:check_target预处理检测目标是否适用于当前插件,execute执行具体检测。比如SQL注入插件会先检测URL中是否存在参数,没有参数就直接跳过,从而节省扫描时间。

3.2 四种经典漏洞的检测实现:SQL注入、XSS、目录泄露、敏感文件

SQL注入检测我采用了三种策略组合。第一是错误回显检测:构造单引号和双引号闭合字符,请求后检查响应中是否出现SQL语法错误特征,比如”MySQL”、”You have an error in your SQL syntax”、”sqlite”、”ORA-“等,一旦命中基本可以确认数据库类型和注入点位置。第二是布尔盲注检测:构造true和false两个条件请求(比如and 1=1与and 1=2),比较两个响应的页面长度和内容差异,如果结果差异稳定,就判定参数存在布尔注入。第三是时间盲注检测:构造sleep函数(MySQL环境),响应时间显著延长的则判定存在时间盲注。时间盲注的判定阈值设为5秒,实测要特别注意网络波动带来的误报,至少要对比三次,取平均值再做判断。

XSS检测的思路类似:将payload参数值注入到目标URL的参数中,请求后检查响应中是否原样回显payload内容。这里要区分反射型和存储型,扫描系统主要做反射型检测。payload集合我准备了多组,包括script标签、svg标签、img错误事件等,覆盖常见过滤绕过场景。检测回显时用的是模糊匹配,把<和>标签包裹的核心payload部分去掉两端的引号再匹配,因为很多站点会做引号转义。

目录遍历检测比较简单:在URL路径部分加上../的循环组合去请求,比如/etc/passwd、/windows/win.ini这类系统文件路径,如果响应内容里出现了root:或boot loader等标志性内容,则判定存在路径穿越漏洞。敏感文件泄露检测则是基于一份文件路径字典,逐个探测常见备份文件、版本控制目录、配置文件。

3.3 结果入库与扫描报告生成

漏洞检测的每个命中结果都会被写入漏洞结果表,记录目标URL、漏洞类型、漏洞等级、请求参数、响应摘要、检测时间和修复建议。漏洞等级我分了高、中、低三档:SQL注入和命令执行属于高危,XSS和目录遍历属于中危,Web指纹信息泄露和服务器组件版本过旧属于低危。

报告生成模块我做了两种格式:网页版和PDF版。网页版是一个只读的详情页面,展示扫描概览、漏洞列表和修复建议,适合在线查看;PDF版用模板渲染加HTML转PDF来实现,适合下载保存和答辩展示。报告里除了漏洞清单,还会生成统计图表,用ECharts绘制漏洞等级分布饼图和趋势图,这个视觉呈现对答辩演示是很大的加分项。

4. 实操过程:从环境搭建到完整扫描复现

4.1 环境准备与依赖安装

如果你要复现这个项目,我建议按以下环境来准备:Python版本用3.8以上,太低的版本对类型标注和异步支持不友好,太高(比如3.13)个别库可能还没适配。操作系统Windows或Linux都行,Linux下跑Celery更顺畅,Windows下注意Celery需要额外配置事件循环。核心依赖如下:

  • Flask 2.x:Web服务框架
  • Celery 5.x:异步任务队列
  • Redis:Celery的Broker和Backend
  • requests:HTTP请求
  • BeautifulSoup4:HTML解析
  • dnspython:DNS解析
  • SQLAlchemy:ORM

安装命令很简单:

pip install flask celery redis requests beautifulsoup4 dnspython sqlalchemy

Redis需要单独安装并启动,Windows下可以使用Memurai替代或者用Docker跑一个Redis容器,Linux下直接apt或yum安装。这里有个小坑:Celery 5.x的Broker URL配置如果格式不对,会出现连接拒绝的报错,检查一下redis://localhost:6379/0这样的写法是否和你本机Redis端口一致即可。

4.2 核心代码结构与关键配置说明

项目目录结构分为以下几个核心模块:

  • app.py:Flask应用入口,注册路由和API
  • tasks.py:Celery任务定义,扫描任务的入口函数
  • scanner/:扫描引擎包
    • collect/:信息搜集模块(子域名、端口、指纹、目录)
    • vuln/:漏洞检测插件包
    • engine.py:插件加载和调度逻辑
  • models.py:数据库模型定义
  • templates/:前端页面模板
  • reports/:生成报告的目录

Flask路由部分,我设计了五个主要接口。第一个是提交扫描任务,接收目标URL和扫描选项,返回任务ID;第二个是查询任务状态;第三个是查询任务结果;第四个是获取历史扫描记录;第五个是导出报告。这些都是REST风格接口,前端用AJAX轮询调用。

端口扫描模块的代码核心是socket连接:

def scan_port(host, port): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(1.0) result = sock.connect_ex((host, port)) sock.close() return result == 0

这个函数返回True表示端口开放。注意settimeout设成1秒,如果网络状况差可以放宽到2秒,但建议不要太久,因为大规模扫描时每个端口都等很久是灾难。

4.3 配置扫描任务与前端联动

提交扫描这个流程,我简单说一下实现逻辑。前端页面上有一个表单,用户填入目标URL,勾选需要执行的扫描类型(子域名、端口、目录、漏洞检测可以分别开关),点击提交后JavaScript用fetch发送POST请求到/api/scan接口。后端接口函数启动一个Celery异步任务,立即返回JSON格式的任务ID。

前端轮询部分的JavaScript代码大致是这样的:

async function checkStatus(taskId) { while (true) { const response = await fetch(`/api/task/${taskId}`); const data = await response.json(); updateProgress(data); if (data.status === 'SUCCESS' || data.status === 'FAILURE') { window.location.href = `/result/${taskId}`; break; } await new Promise(resolve => setTimeout(resolve, 2000)); } }

每两秒请求一次任务状态,通过Celery的result对象获取进度百分比和中间结果,前端用进度条展示。这一步是用户体验的关键,如果不用异步任务,用户面对一个卡死的页面会焦虑,答辩时观感也不好。

4.4 实测过程:一个本地测试站点的完整扫描

我在本机搭了一个基于DVWA的测试环境来验证整个系统。提交http://localhost:8080/DVWA作为目标后,系统流程如下:

信息搜集阶段:端口扫描发现8080端口开放,指纹识别判断为Apache/PHP架构,目录扫描发现DVWA的登录页和几个PHP文件。漏洞扫描阶段:引擎针对搜索功能页面和输入参数执行SQL注入检测,成功在参数id上触发时间盲注的延时特征;XSS检测在反射点上复现了script payload回显。整个过程大概跑了三分钟,结果页面显示高危漏洞1个、中危漏洞2个、低危信息若干。

实测下来,系统基本达到了设计预期。但同时也暴露出几个问题,比如对JS渲染的页面爬虫无能为力,目录扫描的字典还不够全,对WAF的识别能力有限。这些既是不足,也是后续可以继续优化的方向。

5. 常见问题与排查技巧实录

5.1 扫描任务排队不执行或卡住的排查方法

Celery任务提交后一直没有worker执行,这是新手最容易踩的坑。首先确认Celery worker有没有启动,启动命令是:

celery -A tasks worker --loglevel=info

启动后日志会显示ready状态。如果worker起来了但任务还是不进队列,检查Redis连接,使用redis-cli ping确认Redis进程正常。还有一种情况是tasks模块导入失败导致worker启动失败,但日志被刷过去了没注意到,建议用--loglevel=debug启动一次看看完整的导入堆栈。

还有一种更隐蔽的问题:Windows环境下Celery 5.x不支持在交互式命令行里用默认的prefork并发模式,需要指定--pool=solo参数,否则启动时直接报错。

5.2 扫描误报率太高?结果判定逻辑怎么调优

误报主要集中在布尔盲注和时间盲注上。布尔盲注的误报原因往往是把正常动态页面的内容差异误判成了注入差异。例如参数值改变会触发不同的SQL查询结果,两个响应页面本身就存在差异,跟注入无关。解决办法是在判定逻辑中加入基线请求,先请求一次不带任何注入payload的原始URL,记录基线指纹,再对比注入条件下的响应差异,只有基线一致、注入请求才出现差异的情况才判定为注入。

时间盲注的误报来源是网络抖动。我的改进方案是:对同一参数连续测试三次sleep请求,三次的平均耗时达标才认定存在注入,同时加入本地延迟基线校准,每次测试前先请求一个静态资源,测量网络往返延迟,再从总耗时中扣除。实际操作中这个措施能把时间盲注的误报率降低一半以上。

5.3 反爬与请求频率限制会不会影响扫描结果

很多目标站点有反爬策略,比如对单IP请求频率做限制,触发后返回403或验证码页面,这种情况下扫描器拿到的响应全是拦截页,指纹识别和漏洞检测都会失灵。对策是给扫描器加请求间隔配置,默认每次请求间隔0.2秒,匹配到验证码特征时自动退避到1秒。同时设置User-Agent池,每次请求随机选择一个,避免同一个UA高频出现。

但这里要特别提醒你:配置的请求频率越低,单次扫描耗时越长,一个端口扫描加目录爆破加漏洞检测的完整流程可能从几分钟拉长到半小时。所以扫描配置里务必提供“快速模式”和“全量模式”两档,毕业设计演示时用快速模式展示流程,自己做测试时用全量模式,这样体验更好。

6. 毕业设计答辩经验与后续扩展方向

6.1 演示时最容易出彩的三个环节

答辩演示这块我总结出几个经验。第一个要让评委看到真实的安全检测效果,提前准备一个有漏洞的测试站点(DVWA或自己写一个带SQL注入和XSS的简单PHP页面),现场扫描,让评委亲眼看到漏洞被识别出来的过程,而不是空口说“我们系统的检测率很高”。

第二个是展示报告的可视化。报告里的漏洞等级分布饼图和详细列表,配上实际的请求和响应摘要,能直观体现系统把完整的取证闭环做出来了。

第三个是聊架构设计。被问到“你的系统如何扩展新的漏洞检测能力”时,现场演示往插件目录里添加一个新文件,重启后系统自动识别并运行这个插件,这个从代码到演示的闭环会显得你真正理解了可扩展性的含义。

6.2 后续演进:从教学演示走向半自动化运维

这个系统如果想继续深入,有几个方向可以考虑。一是加入POC验证模块,检测到的疑似漏洞不直接判定,而是调用相应的验证脚本确认漏洞是否真实可利用,这需要额外维护一个POC库,依赖关系和管理会复杂一些,但能大幅降低误报率。二是引入WebSocket替换轮询机制,前端实时推送扫描进度,体验能再上一个台阶。三是支持批量目标和计划任务,对接企业资产管理系统,从手动输入目标演进到自动发现、定期扫描的SaaS服务架构。

还有一个比较实用的小改进是配置管理:把目标URL、扫描选项、字典路径等统一放到配置文件里,用Flask的配置系统加载,而不是散落在各模块的硬编码里,这样后续维护会省很多事。系统跑过几百次之后你会发现,维护成本最高的不是扫描逻辑,而是字典库和指纹库,这些数据资产的持续更新才是把这个项目做成产品的最关键一环。

6.3 常见问题速查表

我把开发过程中确认过的最实用的几条经验整理成了表格,方便你对照排查:

现象可能原因解决方案
任务提交后无响应Celery worker未启动执行celery -A tasks worker --loglevel=info
worker启动报错Windows下prefork模式不支持加--pool=solo参数
端口扫描结果全为空目标防火墙拦截TCP连接检查本机与目标网络连通性,确认端口实际状态
时间盲注误报网络延迟波动大增加基线延迟校准,单参数测试三次取均值
爬虫只拿到首页站点是JS渲染应用升级为无头浏览器渲染,或接受当前限制
Redis内存持续增长任务结果长时间保留设置result_expires参数,定期清理过期结果

总体来看,这个项目的核心价值不在于它检测了多少种漏洞,而在于它完整覆盖了从信息搜集、漏洞发现到报告生成的整个安全测试流程,技术栈选型合理、架构清晰、可迁移性高。无论你是拿它当作毕业设计,还是想在此基础上继续深挖安全自动化方向,这套骨架都能支撑住你的扩展需求。

最后分享一个我在实际开发中的体会:安全扫描器和普通业务系统最大的区别在于,扫描器的输出永远是不可信的,每个检测结果都必须能回溯到原始请求和响应,否则无法说服任何人。所以在开发过程中所有模块都带上了日志记录,每次请求响应都落盘,起初觉得麻烦,后来发现这是排查问题的救命稻草。做安全方向的工具,可审计性比性能更重要,这个理念希望你从一开始就放在心上。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询