构建全栈Web安全扫描器:从JS分析到漏洞检测的自动化实践
1. 项目概述:为什么我们需要一个“全栈”Web安全扫描器?
在Web安全评估的日常工作中,我们常常面临一个尴尬的局面:工具链过于碎片化。一个项目下来,你可能需要打开Burp Suite抓包,用gau或waybackurls收集历史URL,用subfinder找子域名,用katana或crawley爬页面,再用nuclei或自定义脚本去测试漏洞。这还没算上专门分析JS文件找API端点、密钥,或者解析Swagger/OpenAPI文档的工具。流程繁琐不说,数据在不同工具间流转、去重、整合本身就是个体力活,还容易遗漏关键信息。
这个项目标题所指向的,正是一个试图整合这些核心能力的“瑞士军刀”式Web安全扫描工具。它瞄准的不是某个单一环节,而是从信息收集到漏洞验证的完整链路。JS敏感信息收集和API端点提取,针对的是现代前后端分离应用和SPA(单页应用)中,大量业务逻辑和接口隐藏在JavaScript文件里的现状。API文档解析则是对付那些规范化的接口,直接从/api-docs或/swagger.json里“抄作业”。页面爬取是传统但永恒的基础。子域名发现是扩大攻击面的起点。漏洞测试是核心目的。而WAF检测与绕过以及JS代码分析,则是为了提升扫描的穿透力和深度。
简单说,它想做的,就是让你输入一个域名,然后尽可能地自动化完成从“发现目标”到“找到可能漏洞”的整个过程,并且在这个过程中,特别强化了对现代Web应用技术栈(尤其是JavaScript和API)的适配能力。这对于安全工程师、渗透测试人员,甚至是开发人员进行自检,都有很高的实用价值。
2. 核心模块深度解析与设计思路
一个工具的功能列表只是表象,其背后的设计思路和实现权衡才是关键。下面我们来拆解这个“全栈”扫描器的各个核心模块,看看它们分别要解决什么问题,以及可能的实现路径。
2.1 JS敏感信息收集与API端点提取:从静态文件到动态线索
现代Web应用,特别是React、Vue、Angular构建的应用,其业务逻辑、配置信息乃至API端点,都打包在了前端JS文件里。传统的爬虫只能看到HTML,却错过了这座“数据富矿”。
核心原理:这个过程本质上是静态代码分析。工具需要下载所有关联的JS文件(包括<script>标签引入的、动态加载的),然后对JS代码进行语法分析(如使用正则表达式、AST抽象语法树解析)来寻找特定模式。
敏感信息收集:寻找硬编码的密钥、令牌、内部路径等。
- 目标模式:高熵字符串(如
AKIA...AWS密钥)、apiKey、secret、password、token、access_key等变量名后的赋值,以及https://或http://开头的URL。 - 实现思路:
- 正则匹配:快速但可能误报。例如,匹配
/[A-Za-z0-9+/]{40,}/找可能的长Base64串,匹配/[\w-]{20,}\.[\w-]{20,}\.[\w-]{20,}/找JWT令牌。 - AST分析:更精准。使用
esprima或@babel/parser等库将JS代码解析成AST,然后遍历节点,查找VariableDeclarator、AssignmentExpression等,分析其标识符(Identifier)和字面量(Literal)值。这能有效区分是变量名包含“secret”还是其值真是密钥。
- 正则匹配:快速但可能误报。例如,匹配
- 实操要点:必须结合上下文过滤。一个变量叫
apiSecret,但其值可能是“YOUR_API_SECRET_HERE”这样的占位符。需要加入熵值计算、关键字黑名单(如example,placeholder)和格式验证(如AWS密钥格式、GitHub令牌格式)来降低误报。
- 目标模式:高熵字符串(如
API端点提取:从JS代码中挖掘出后端接口地址。
- 目标模式:
fetch(‘/api/user’)、axios.get(‘/api/data’)、$.ajax({url: ‘...’}),以及字符串拼接形式的URL(如baseURL + ‘/endpoint’)。 - 实现思路:
- 字符串提取:用正则匹配所有引号内的字符串,然后过滤出看起来像URL路径的(包含
/api/、/v1/、以特定后缀如.json结尾等)。 - AST深度分析:这是更优解。通过AST找到所有的
CallExpression(函数调用),检查其callee.name是否为fetch、axios等,然后从其arguments中提取URL节点。对于拼接的URL,需要跟踪变量定义,进行简单的数据流分析。
- 字符串提取:用正则匹配所有引号内的字符串,然后过滤出看起来像URL路径的(包含
- 注意事项:提取到的端点可能是相对路径(如
/api/login),需要与当前页面URL进行拼接,生成绝对URL。同时,要处理JS中常见的动态路径(如模板字符串/api/${resource}/list),这类端点需要结合其他信息(如从其他静态文件或响应中发现的资源名列表)进行“爆破”或占位符替换。
- 目标模式:
2.2 API文档解析:直击规范化的接口蓝图
如果目标系统使用了Swagger(OpenAPI)或类似的API文档框架,那么我们就中大奖了。这些文档(通常是/swagger.json、/openapi.json或/api-docs)以结构化的JSON/YAML格式,完整描述了所有接口的路径、方法、参数、甚至示例值。
- 核心价值:无需猜测和爬取,直接获得权威的、完整的接口清单。这不仅是信息收集,更是高质量的漏洞测试输入。
- 实现流程:
- 发现文档:通过常见路径字典进行探测(如
/api-docs,/swagger-ui.html,/v2/api-docs等)。 - 获取与解析:下载文档文件,使用
swagger-parser或openapi-spec-validator等库进行解析和规范化。 - 端点生成:遍历解析后的规范对象,对于每个
path下的每个method(如get,post),生成一个标准的请求模板。 - 参数填充:提取
parameters和requestBody中定义的参数名、类型、示例(example)或模式(schema)。这是关键优势所在,工具可以智能地生成有效的测试载荷,而不是乱填一气。
- 发现文档:通过常见路径字典进行探测(如
- 经验之谈:解析时要注意处理
$ref引用(指向其他部分的定义)。生成的测试用例要特别注意区分query、header、path、cookie和body参数,并按照API文档要求的格式(如application/json、multipart/form-data)组装请求。对于标记为required: true的参数,应优先且必须填充。
2.3 页面爬取与子域名发现:构建完整的攻击面地图
这两者是传统信息收集的基石,但在这个集成工具中,需要与上述高级模块联动。
页面爬取:
- 目标:不只是抓取
<a href>链接,更要能执行JavaScript,渲染SPA,从而发现动态加载的内容和触发的API调用。 - 实现选择:
- 传统爬虫:基于
GET请求和HTML解析,速度快,资源消耗低,但无法处理JS。 - 无头浏览器:使用Puppeteer(Chrome)或Playwright,能完整渲染页面,执行JS,模拟点击,但速度慢,资源开销大。
- 传统爬虫:基于
- 设计权衡:一个成熟的扫描器通常会采用混合策略。先使用快速爬虫抓取所有静态链接和基础页面。然后,对重要页面(如登录页、主应用页)或疑似SPA的页面,启动无头浏览器进行深度爬取。从无头浏览器的网络请求日志中,可以直接提取出API端点和静态资源URL,这本身就是一种高效的“JS端点提取”方法。
- 避坑指南:无头浏览器爬虫要处理好登录状态(Cookie、Session)、避免陷入爬虫陷阱(如日历控件)、设置合理的超时和并发控制。同时,要配置好忽略规则,避免爬取到注销、删除等危险链接。
- 目标:不只是抓取
子域名发现:
- 数据源:综合利用多种来源才能最大化结果。
- 被动收集:查询DNS历史记录数据库(如SecurityTrails, Censys)、证书透明度日志(CT Logs,使用
crt.sh接口)、搜索引擎(如Google,site:*.example.com)、以及像VirusTotal、AlienVault OTX这样的威胁情报平台。这些方式无需直接与目标交互,隐蔽性好。 - 主动枚举:使用字典爆破(如
subdomains-top1million.txt)和排列组合(如基于已知子域名生成dev-,test-,staging-等变体)。DNS爆破(如使用massdns)是主动发现的核心。
- 被动收集:查询DNS历史记录数据库(如SecurityTrails, Censys)、证书透明度日志(CT Logs,使用
- 整合与去重:工具需要集成多个上述数据源的API或本地查询,将结果合并、去重,并尝试进行DNS解析验证(A/AAAA/CNAME记录),过滤出真实存在的子域名。发现的每个新子域名,都应自动加入到后续页面爬取和漏洞测试的队列中,形成闭环。
- 数据源:综合利用多种来源才能最大化结果。
2.4 漏洞测试与WAF交互:从探测到绕过
这是扫描器的“矛尖”。它利用前面收集的所有信息(URL、参数、端点)作为输入,进行安全漏洞的探测。
漏洞测试引擎:
- 模式:通常采用基于模板的检测方式(类似Nuclei)。每个漏洞对应一个YAML模板,模板中定义了:
- 请求:如何构造HTTP请求(方法、路径、头部、载荷)。
- 匹配器:如何从响应(状态码、头部、正文)中判断漏洞是否存在(使用正则、关键词、状态码等)。
- 载荷插入:工具需要智能地将测试载荷插入到请求的合适位置——URL参数、POST数据、HTTP头、Cookie、JSON路径等。对于从API文档解析来的端点,要能根据参数类型(字符串、整数)生成类型正确的畸形数据。
- 测试策略:不能盲目乱打。需要根据目标特点调整:
- 速率限制:避免触发目标系统的速率限制或被封IP。
- 智能检测:先发送一些无害请求探测目标的技术栈(如
Server头、特定Cookie、HTML特征),再选择针对该技术栈的漏洞模板进行测试(例如,针对ThinkPHP的模板,针对Spring的模板)。 - 结果去重:同一个漏洞点可能被多个模板或不同载荷触发,需要合并相似结果。
- 模式:通常采用基于模板的检测方式(类似Nuclei)。每个漏洞对应一个YAML模板,模板中定义了:
WAF检测与绕过:
- 检测先行:在开始激进测试前,先判断目标是否有WAF(如Cloudflare, AWS WAF, ModSecurity)。检测方法包括:
- 发送一个明显的恶意载荷(如
<script>alert(1)</script>或1 AND 1=1),观察响应。 - 检查HTTP响应头中是否有WAF标识(如
Server: cloudflare,X-Frame-Options等)。 - 检查错误页面是否包含WAF特有信息(如
403 Forbidden页面的样式)。
- 发送一个明显的恶意载荷(如
- 绕过技术集成:如果检测到WAF,扫描器应能自动或手动启用绕过策略。这些策略不是魔法,而是对请求的细微变形:
- 编码混淆:对载荷进行URL编码、双重编码、Unicode编码、HTML实体编码。
- 大小写变换/随机化:
UNION SELECT->UnIoN SeLeCt。 - 空白符替换:用
/**/、%0a、%0d代替空格。 - 注释插入:在SQL注入载荷中插入
/**/注释分隔符。 - 参数污染:提交多个同名参数(如
id=1&id=2),WAF和后台应用解析可能不一致。 - 非常规请求方式:将
POST参数放到GET查询字符串中,或者放到JSONbody中,反之亦然。
- 实现考量:工具不应默认使用所有绕过技术狂轰滥炸,这会产生大量无效请求。更好的设计是:提供一个“WAF绕过模式”选项,或者当检测到WAF拦截时,自动切换到一组经过挑选的、针对该类型WAF已知弱点的变形规则进行重试。
- 检测先行:在开始激进测试前,先判断目标是否有WAF(如Cloudflare, AWS WAF, ModSecurity)。检测方法包括:
2.5 JS代码分析:超越字符串匹配的深度挖掘
这是对“JS敏感信息收集”的深化。除了用正则和简单AST找字符串,更深度的分析能发现更隐蔽的问题。
- 数据流分析:跟踪敏感数据(如
document.cookie,localStorage中的令牌)在代码中的传递路径。它是否被发送到了第三方域名?是否被存入了不安全的全局变量? - 依赖分析:分析JS文件中引入的第三方库(通过
import、require或直接引用)。这些库的版本是否已知存在漏洞(可通过对接CVE数据库或npm audit结果)?是否引入了不安全的配置? - 源代码映射解析:如果生产环境的JS文件附带了
.map文件(Source Map),工具可以尝试下载并解析它,将压缩混淆后的代码映射回原始源代码。这在代码审计和寻找硬编码秘密时价值连城,因为原始代码中的变量名和逻辑清晰可读。 - 实操难点:深度JS分析非常消耗计算资源,且对混淆代码(如Webpack打包、主动混淆工具处理过的代码)效果有限。因此,在自动化扫描工具中,这一功能通常作为可选的高级模块或后续手动分析阶段使用,而非对每个JS文件都进行全量深度分析。
3. 系统架构与工作流设计
将这么多功能塞进一个工具,良好的架构设计是保证其可用性和性能的关键。一个典型的模块化设计可能如下:
输入(一个根域名) | v [协调器/引擎] | (并行/流水线) |-> [子域名发现模块] -> 发现子域名列表 |-> [页面爬取模块] -> 发现URL、链接、静态资源(JS/CSS) |-> [JS分析模块] -> 从JS中提取端点和敏感信息 |-> [API文档探测模块] -> 解析Swagger等文档获取端点 | v [数据聚合与去重中心] | (合并所有来源的URL/端点,去重,规范化) | v [目标队列] | v [漏洞测试引擎] <-> [WAF检测与绕过模块] | (从队列取目标,加载模板,发送测试请求,判断结果) | v [结果存储与报告生成]关键设计决策:
- 并发与速率控制:所有主动扫描模块(爬虫、子域名爆破、漏洞测试)都必须有全局的并发控制和速率限制配置,防止对目标造成拒绝服务(DoS)或触发防护机制。
- 任务管道与依赖:有些任务有依赖关系。例如,最好先进行轻量子域名发现,再对发现的子域名进行爬取和JS分析。漏洞测试应在信息收集基本完成后进行。协调器需要管理这种依赖关系。
- 资源隔离:无头浏览器爬虫资源消耗大,应与轻量级的HTTP请求模块(用于漏洞测试、API探测)隔离,可能运行在不同的进程或容器中。
- 配置与扩展性:每个模块都应可通过配置文件调整参数(如爬虫深度、超时时间、字典路径、漏洞模板目录)。漏洞测试引擎应支持用户自定义模板(YAML格式),这是工具保持生命力的核心。
4. 实战配置与操作指南
假设我们有一个这样的工具,我们将其命名为WebRecon(仅为示例)。下面是一个从安装到运行一次完整扫描的模拟流程。
4.1 环境准备与安装
WebRecon可能是一个Go或Python编写的工具,我们需要先准备环境。
# 假设是Go项目 git clone https://github.com/example/webrecon.git cd webrecon go build -o webrecon main.go # 或者假设是Python项目 pip install webrecon依赖项检查:
- 无头浏览器:如果包含高级爬虫,需要安装Chromium或Chrome,并确保
chromedriver版本匹配。 - 字典文件:子域名爆破、路径爆破需要字典。工具应内置基础字典,但允许用户指定自定义字典路径。
- API密钥:为了使用SecurityTrails、Censys等被动收集源,需要在配置文件(如
config.yaml)中配置API密钥。
4.2 配置文件详解
一个强大的工具离不开灵活的配置。以下是一个示例config.yaml的核心部分:
# webrecon_config.yaml target: "example.com" # 主目标域名 # 子域名发现配置 subdomain: enabled: true wordlist: "/path/to/subdomains.txt" brute-force: true recursive: false # 是否对发现的子域名进行子域名爆破 passive-sources: # 被动源配置 securitytrails: api-key: "YOUR_KEY" virustotal: api-key: "YOUR_KEY" crt-sh: true # 使用公共CT日志,无需key # 爬虫配置 crawler: enabled: true depth: 3 # 爬取深度 max-pages: 1000 # 最大页面数限制 js-render: true # 是否启用无头浏览器渲染JS headless-timeout: 30 # 无头浏览器页面超时(秒) exclude-extensions: [".pdf", ".jpg", ".png", ".css"] # 排除的文件扩展名 custom-headers: # 自定义请求头,可用于绕过基础认证或设置UA User-Agent: "WebRecon/1.0" # JS分析配置 js-analyzer: enabled: true extract-endpoints: true extract-secrets: true secret-patterns: ["api_key", "secret", "token", "password"] # 自定义要查找的密钥模式 # API文档解析 api-parser: enabled: true probe-paths: ["/api-docs", "/swagger-ui.html", "/openapi.json", "/v2/api-docs"] # 漏洞扫描配置 scanner: enabled: true templates-path: "./templates" # Nuclei兼容模板目录 rate-limit: 150 # 每秒请求数限制 threads: 50 # 并发线程数 waf-detection: true waf-bypass: false # 默认关闭,谨慎开启 # 输出配置 output: format: "json" # 支持 json, html, md directory: "./results/example.com" verbose: false # 是否输出详细日志注意:
waf-bypass选项默认应关闭。开启后,对于每个被WAF拦截的请求,工具会尝试多种绕过技术重试,这将极大增加请求数量和扫描时间,并可能产生大量日志告警。仅在授权测试且明确需要时开启。
4.3 运行一次完整扫描
配置好后,运行扫描就很简单了:
./webrecon -c webrecon_config.yaml # 或者 python -m webrecon -c webrecon_config.yaml工具会按照配置依次执行各模块。你可以在终端看到实时日志,了解当前进度(如“正在被动收集子域名”、“正在爬取 https://example.com”、“正在测试SQL注入模板”)。
关键操作技巧:
- 分阶段运行:对于大型目标,可以先只运行信息收集阶段(禁用
scanner.enabled),审查收集到的URL和端点,确认范围无误后,再针对性地运行漏洞扫描。# 第一步:只收集信息 ./webrecon -c config.yaml --no-scan # 第二步:分析收集到的 targets.txt 后,进行扫描 ./webrecon -c config.yaml --target-list ./results/example.com/urls.txt - 限制范围:使用
--scope或配置中的in-scope正则表达式,严格控制扫描边界,避免爬取到无关的第三方域名或注销链接。 - 使用代理:通过
--proxy http://127.0.0.1:8080将所有流量导向Burp Suite等代理,方便你实时查看、修改每一个请求,进行手动测试或调试工具行为。
4.4 结果解读与报告
扫描结束后,结果会输出到指定的目录(如./results/example.com/)。
urls_all.txt: 所有发现的URL。endpoints_from_js.json: 从JS中提取的API端点。secrets_potential.txt: 发现的潜在敏感信息(需人工复核)。subdomains_resolved.txt: 解析成功的子域名。vulnerabilities.json: 发现的漏洞详情,通常包括漏洞类型、受影响URL、请求/响应信息、严重等级等。report.html: 生成的HTML总结报告。
报告分析重点:
- 误报剔除:自动化工具的漏洞报告必然存在误报。特别是基于关键字的XSS或SQL注入检测。需要手动验证:
- 该参数是否真的在服务端被解析?
- 返回的“证据”是否是页面静态内容的一部分?
- 尝试使用更确定的Payload进行手动验证。
- 敏感信息复核:工具报告的“密钥”可能是误报(如变量名)。需要逐一检查其上下文,确认是否是真实有效的凭证。
- 风险关联:将发现的子域名、暴露的API端点、潜在的漏洞结合起来看。例如,一个在
api.test.example.com子域上发现的未授权访问漏洞,其风险远高于主站一个无关紧要的反射型XSS。
5. 常见问题、排查与进阶技巧
即使工具设计得再完善,在实际使用中也会遇到各种问题。以下是一些典型场景及处理思路。
5.1 扫描被中断或目标无响应
- 症状:大量请求超时,扫描进程卡住,或目标网站返回5xx错误。
- 排查:
- 检查网络:
ping或curl目标,确认基础连通性。 - 降低负载:大幅调低配置文件中的
rate-limit(如降到10)和threads(如降到5)。可能是触发了目标的速率限制或CC防护。 - 检查WAF:手动访问目标,查看是否被跳转到验证码页面或返回了特定的拦截页面(如Cloudflare的“Checking your browser”)。如果确认是WAF,启用
waf-detection,并考虑是否使用waf-bypass(需谨慎)。 - 调整超时:增加
timeout配置项,给目标更长的响应时间。
- 检查网络:
- 根本解决:与目标系统管理员沟通(在授权测试中),了解其防护策略,协商扫描窗口期或添加扫描器IP到白名单。
5.2 爬虫无法获取到核心内容(SPA应用)
- 症状:爬虫收集到的页面很少,全是基础的HTML骨架,看不到动态加载的数据和功能。
- 排查与解决:
- 确认JS渲染已开启:检查配置中
crawler.js-render是否为true。 - 增加等待时间:SPA加载数据可能需要时间,增加
headless-timeout(如从30秒增加到60秒)。 - 模拟交互:高级爬虫可以配置“交互脚本”,在页面加载后自动执行点击(如“加载更多”按钮)、滚动等操作。检查工具是否支持此类配置。
- 直接分析网络请求:如果爬虫效果不佳,可以手动用浏览器打开目标,在开发者工具的Network面板中查看XHR/Fetch请求,直接将这些API端点添加到工具的起始URL列表中。
- 确认JS渲染已开启:检查配置中
5.3 漏洞测试误报率极高
- 症状:报告了大量SQL注入或XSS漏洞,但手动验证发现都是误报。
- 排查:
- 审查匹配规则:查看触发漏洞的模板的
matchers部分。它可能只是匹配了响应中包含你的Payload字符串。一个健壮的匹配器应该能排除掉Payload被原样反射回响应体的情况(例如,检查响应体是否包含<script>alert且上下文不是<textarea>)。 - 检查参数位置:工具可能对所有的请求参数(包括Cookie、Header)都进行了测试。但有些参数服务端根本不处理。可以尝试在配置中排除某些静态参数(如
utm_source)或特定Header的测试。 - 使用更精确的模板:社区维护的模板库质量参差不齐。优先使用官方或信誉良好的源(如ProjectDiscovery的Nuclei模板库)。对于自定义模板,编写时要尽可能严谨。
- 审查匹配规则:查看触发漏洞的模板的
- 最佳实践:将漏洞扫描分为两个阶段。第一阶段使用“快速检测”模板,筛选出潜在风险点。第二阶段,对第一阶段的结果进行人工复核或使用更复杂、交互式的验证Payload进行二次扫描。
5.4 工具消耗资源过多(CPU/内存)
- 症状:扫描过程中机器变卡,内存占用飙升。
- 原因:无头浏览器是资源消耗大户。每个标签页都是一个独立的Chrome进程。
- 优化:
- 限制并发:降低无头浏览器的并发实例数(在配置中寻找
max-browsers或crawler.concurrency)。 - 分而治之:不要一次性扫描一个巨大的目标。将目标按功能模块或子域名拆分,分批扫描。
- 调整爬取深度和范围:限制
crawler.depth和crawler.max-pages。 - 使用资源限制:在Docker容器中运行工具,并限制其CPU和内存使用量。
- 考虑分布式:对于超大型目标,需要设计分布式扫描架构,将任务分发到多个节点执行。
- 限制并发:降低无头浏览器的并发实例数(在配置中寻找
5.5 进阶技巧与自定义扩展
- 自定义漏洞模板:这是提升工具威力的关键。学习工具的模板语法(通常是YAML),针对你常遇到的技术栈(如某特定CMS、框架)编写检测规则。例如,检测某OA系统的默认弱口令,或某中间件配置不当的未授权访问。
# 示例:一个简单的敏感文件泄露检测模板 id: sensitive-file-exposure info: name: "Backup or Config File Exposure" severity: "medium" requests: - method: GET path: - "{{BaseURL}}/.git/config" - "{{BaseURL}}/web.config.bak" - "{{BaseURL}}/wp-config.php~" matchers: - type: status status: - 200 - 与其他工具联动:将
WebRecon作为信息收集引擎,把发现的独特URL、端点导出,导入到Burp Suite、ZAP或自定义的Fuzzing工具中进行更深入的手动或自动化测试。 - 持续监控与差分扫描:对重要资产定期(如每周)运行扫描,并将结果与上一次进行对比(
diff),快速发现新增的子域名、暴露的API端点或新引入的漏洞。这可以集成到CI/CD流程中,作为上线前的一道安全关卡。
工具的价值最终取决于使用它的人。一个集成了从信息收集到漏洞验证的“全栈”Web安全扫描器,能极大提升测试效率,但它无法替代安全工程师的判断力。它生成的是“线索”和“潜在问题”,真正的风险评估、漏洞验证和利用,依然需要专业的分析和手动测试来完成。把这个工具当作你的数字雷达和侦察兵,它能帮你描绘出完整的攻击面地图,并标出那些需要你亲自前往勘察的“可疑地点”。
