ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

免费浏览器工具URL审计实战:用Node.js追踪743个请求

免费浏览器工具URL审计实战:用Node.js追踪743个请求 过去在调研一批免费浏览器工具时我发现很多工具的“功能边界”并不清晰有的网页截图工具会在后台加载一串你根本没见过的域名有的在线压缩工具会把完整的资源链接上报到第三方统计平台。于是我对 8 个免费浏览器工具、743 个 URL 做了一次系统性的审计把每一个请求的域名、类型、来源都摊开来看。这篇文章会把完整的审计方案、Node.js 实现脚本、结果分类思路和常见陷阱整理成一套可以复用的教程。如果你也要对某个在线工具、浏览器扩展或自己的前端项目做“请求透明化”审计可以直接按下面的流程操作。1. 背景与核心概念1.1 什么是 URL 审计URL 审计指的是对浏览器工具在运行过程中产生的所有网络请求进行采集、分类、统计和分析从而搞清楚这个工具到底访问了哪些地址、为什么要访问、数据被发送到了哪里。举个例子你在网页上输入一串链接点击“解析”按钮后工具可能会发起下面的请求打开目标页面内容拉取页面里的图片、样式、脚本请求一个远程接口做内容识别把当前任务 ID 上报到工具自己的统计系统调用第三方广告或舆情服务。这里面有些请求是“功能必需”的有些则是“业务必需”的还有一部分可能是“完全没必要的”。URL 审计的目的就是把它们区分开。1.2 主动审计与被动审计常见的审计方式可以分为两类主动审计主动设计一份 URL 清单检查工具是否访问了清单之外的地址。 这种方法适合验证工具的“边界行为”例如判断工具是否只访问声明的域名。被动审计不预设规则先完整抓取所有请求再对结果做分类整理。 这种方法适合探索式分析能发现你之前完全想不到的第三方域名。在本文的实战中我采用的是“先被动采集再主动分类”的混合方式。先通过脚本控制工具访问一批测试 URL同时记录所有网络请求最后对提交结果做人工分析。1.3 为什么要审计浏览器工具的 URL有四个很实际的场景隐私合规很多免费工具依赖广告、用户画像和第三方统计来维持运营但用户有权知道自己上传的数据流向了哪里。审计结果可以作为隐私说明书的参考依据。安全边界如果工具请求了意料之外的域名或者把目标 URL 参数原封不动地发给第三方就可能存在数据外泄风险。性能优化工具加载了太多无用的第三方脚本会显著增加首屏耗时和任务执行时间。审计能帮你定位慢请求。技术选型在多个工具之间做对比时URL 级审计能给出一个比较客观的“行为黑盒”结果比单纯看功能列表更可信。2. 审计设计与数据范围2.1 审计对象8 个免费浏览器工具在开展审计前先明确一下“免费浏览器工具”的边界。我这里选了 8 类比较常见、不需要登录即可完成基础任务的中小型工具覆盖面尽量广才能反映不同场景下的请求行为类型典型功能审计关注点网页截图工具输入 URL生成网页截图是否打开原页面、图片传到哪个服务器在线格式转换工具上传或输入内容转换格式是否有上传行为以外的额外请求SEO 体检工具检测网站标题、关键词、响应头是否在服务端抓取目标页面性能检测工具分析页面加载速度请求目标页面时是否附加了统计参数链接解析工具展开短链接、提取链接信息短链接解析后目标地址是否泄露给第三方代码美化/压缩工具粘贴代码格式化或压缩代码内容是否被上传到远程接口网页爬虫工具输入 URL提取文本或元数据爬取结果是否经过中间服务内容安全检测工具检查网站是否被拦截或标记返回结果是否依赖黑名单 API这 8 类工具都属于通用、安全的浏览器工具类型不涉及任何需要特殊网络环境才能访问的服务。2.2 为什么是 743 个 URL743 不是刻意凑出来的数字。审计过程中我准备了 30 个不同类型的种子 URL比如静态博客、电商页、图片站、视频播放页、在线文档、空页面和 404 页面等。每个工具处理一个种子 URL 时平均会产生 20~30 个网络请求把这些请求去重后汇总在一起就得到了 743 个唯一 URL。这里要注意不同工具对同一个种子 URL 的处理逻辑差异很大。有的工具只请求一次目标页面就不再发请求有的工具会额外请求字体、广告、统计脚本甚至把截图上传到自己的对象存储中。所以最终得到的 URL 数量会远大于“工具数量 × 种子 URL 数量”。2.3 审计维度对每个 URL我记录了下面几个字段source_tool来自哪个工具source_url触发请求的种子 URLrequest_url被请求的完整 URLdomain提取出的域名resource_type资源类型如 document、script、image、xhr、font、mediais_third_party是否属于工具自身域名之外status_code返回状态码timestamp请求发生时间。有了这些字段后面做分类统计就会非常方便。3. 环境准备与版本说明3.1 运行环境本次审计脚本使用 Node.js 编写因为后续依赖的浏览器自动化工具链非常成熟而且对前端工程师来说零学习成本。操作系统macOS / Linux / Windows 均可Node.js建议使用 18 及以上版本包管理器npm 或 yarnIDEVisual Studio Code版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。3.2 项目结构建议按下面的目录组织项目url-audit/ ├── package.json ├── config/ │ └── tools.json # 8 个工具的基础配置 ├── src/ │ ├── collector.js # 被动采集脚本 │ ├── scanner.js # 主动扫描逻辑 │ ├── analyzer.js # URL 分类与统计 │ └── report.js # 生成 JSON / CSV 报告 ├── data/ │ ├── requests.json # 采集到的原始请求 │ ├── urls.json # 去重后的 URL 列表 │ └── report.csv # 最终结果 └── seeds.txt # 种子 URL 列表3.3 初始化项目并安装依赖mkdir url-audit cd url-audit npm init -y接下来安装核心依赖npm install axios cheerio puppeteer三个依赖的作用axios用于发起 HTTP 请求适合主动扫描场景cheerio用于解析 HTML提取页面里的资源链接puppeteer用于控制无头浏览器捕获浏览器运行过程中的全部请求。安装 Puppeteer 时它会默认下载一个 Chromium 内核这个过程在网络不稳定的情况下比较慢。如果下载失败在后面“常见问题”里我会给出解决方案。4. 核心实现URL 采集脚本4.1 定义种子 URL 列表先创建seeds.txt每一行写一个待测试的 URLhttps://example.com/ https://httpbin.org/html https://httpbin.org/status/404 https://www.iana.org/domains/example https://httpbin.org/redirect/2 https://httpbin.org/image/png种子 URL 的选择可以直接影响审计结果。建议至少包含三类正常可访问页面会返回 404 或 5xx 的异常页面带重定向的页面。这样可以观察工具在异常输入时是否仍然发起了额外请求比如错误上报、日志采集等。4.2 被动采集监听浏览器请求被动采集使用 Puppeteer 控制浏览器打开目标 URL并监听request事件。文件路径src/collector.jsconst puppeteer require(puppeteer); const fs require(fs); const seeds fs.readFileSync(seeds.txt, utf-8) .split(\n) .map((url) url.trim()) .filter((url) url.length 0); const requests []; function classifyResource(type) { const typeMap { document: document, script: script, stylesheet: stylesheet, image: image, font: font, xhr: xhr, fetch: xhr, media: media, other: other, }; return typeMap[type] || other; } async function collectForUrl(browser, seedUrl) { const page await browser.newPage(); page.on(request, (request) { const reqUrl request.url(); requests.push({ source_url: seedUrl, request_url: reqUrl, resource_type: classifyResource(request.resourceType()), method: request.method(), timestamp: new Date().toISOString(), }); }); page.on(response, (response) { const matched requests.find((req) req.request_url response.url()); if (matched) { matched.status_code response.status(); } }); try { await page.goto(seedUrl, { waitUntil: networkidle2, timeout: 30000, }); } catch (error) { console.warn([warn] 打开 ${seedUrl} 失败: ${error.message}); } await page.close(); } (async () { const browser await puppeteer.launch({ headless: new, args: [--no-sandbox, --disable-setuid-sandbox], }); for (const seedUrl of seeds) { console.log([info] 正在采集: ${seedUrl}); await collectForUrl(browser, seedUrl); } await browser.close(); fs.writeFileSync(data/requests.json, JSON.stringify(requests, null, 2)); console.log([done] 采集完成共 ${requests.length} 条请求); })();这段代码的核心逻辑是逐行读取种子 URL新建浏览器页面在request事件中记录请求 URL、请求类型和请求时间在response事件中补全状态码打开页面后等待网络空闲再关闭页面最后把所有请求写入data/requests.json。4.3 主动扫描提取 HTML 中的资源链接被动采集能拿到浏览器实际发起的请求但对于“静态页面里引用了哪些 URL”这种问题还可以用主动扫描来补充。文件路径src/scanner.jsconst axios require(axios); const cheerio require(cheerio); const fs require(fs); const urls [ https://example.com/, https://httpbin.org/html, https://www.iana.org/domains/example, ]; const resourceSelectors { script: script[src], link: link[href], img: img[src], iframe: iframe[src], video: video source[src], audio: audio source[src], }; async function scanUrl(url) { const result []; try { const response await axios.get(url, { timeout: 15000, headers: { User-Agent: Mozilla/5.0 (compatible; URL-Audit/1.0), }, }); const html response.data; const $ cheerio.load(html); for (const [type, selector] of Object.entries(resourceSelectors)) { $(selector).each((_, el) { const resourceUrl $(el).attr(src) || $(el).attr(href); if (resourceUrl !resourceUrl.startsWith(data:)) { result.push({ source_url: url, resource_url: new URL(resourceUrl, url).toString(), resource_type: type, }); } }); } } catch (error) { console.warn([warn] 扫描 ${url} 失败: ${error.message}); } return result; } (async () { const allResults []; for (const url of urls) { console.log([info] 正在扫描: ${url}); const result await scanUrl(url); allResults.push(...result); } fs.writeFileSync(data/scan-results.json, JSON.stringify(allResults, null, 2)); console.log([done] 扫描到 ${allResults.length} 个静态资源链接); })();主动扫描和被动采集可以形成互补被动采集能看到“实际发生了什么请求”主动扫描能看到“页面声称会加载什么资源”。两者对比后能够发现一些工具是否在 HTML 解析阶段就停止了请求或者是否额外注入了自己的脚本。4.4 URL 分类与统计拿到原始请求后需要按域名、资源类型、第三方归属做分类。文件路径src/analyzer.jsconst fs require(fs); const rawData JSON.parse(fs.readFileSync(data/requests.json, utf-8)); function getDomain(url) { try { return new URL(url).hostname; } catch (error) { return invalid-domain; } } function isThirdParty(url, toolDomain) { const domain getDomain(url); return domain ! toolDomain; } function analyze(data) { const summary { total: data.length, uniqueUrls: new Set(data.map((item) item.request_url)).size, domains: {}, resourceTypes: {}, thirdPartyCount: 0, }; const toolDomains new Set([tool-a.example, tool-b.example]); for (const item of data) { const domain getDomain(item.request_url); const type item.resource_type || other; summary.domains[domain] (summary.domains[domain] || 0) 1; summary.resourceTypes[type] (summary.resourceTypes[type] || 0) 1; if (!toolDomains.has(domain)) { summary.thirdPartyCount 1; } } return summary; } const result analyze(rawData); console.log(JSON.stringify(result, null, 2));实际审计时工具提供方声明的域名可能不止一个你需要根据每个工具的实际域名维护一份白名单。白名单之外的域名都视为第三方请求。4.5 生成报告为了让结果更容易阅读我还会把分析结果导出成 CSV 文件。文件路径src/report.jsconst fs require(fs); const requests JSON.parse(fs.readFileSync(data/requests.json, utf-8)); const headers [ source_tool, source_url, request_url, domain, resource_type, status_code, is_third_party, ]; const rows requests.map((item) { let domain -; try { domain new URL(item.request_url).hostname; } catch (error) { domain invalid; } return [ item.source_tool || -, item.source_url || -, item.request_url || -, domain, item.resource_type || -, item.status_code || -, item.is_third_party ? yes : no, ]; }); const csv [headers.join(,), ...rows.map((row) row.join(,))].join(\n); fs.writeFileSync(data/report.csv, csv); console.log([done] 报告已生成共 ${rows.length} 行);生成的 CSV 可以直接用 Excel 或 Numbers 打开做透视表分析。4.6 运行完整审计流程把四个脚本串起来执行node src/collector.js node src/scanner.js node src/analyzer.js node src/report.js建议在package.json里配置 npm script{ scripts: { audit: node src/collector.js node src/scanner.js node src/analyzer.js node src/report.js } }之后只需要执行npm run audit5. 结果分析与观察5.1 请求类型分布在 743 个 URL 中资源类型分布大致如下资源类型数量说明xhr/fetch214接口调用往往是核心业务逻辑script185脚本加载包含统计脚本和功能脚本image122图片资源部分是页面原图部分是图标document96页面跳转或 iframe 加载font71字体文件部分来自第三方 CDNstylesheet55样式表偶尔包含工具自身样式这个分布说明工具在做“打开页面”这个简单操作时背后会发起大量与页面内容本身无关的请求。尤其要注意的是 script 和 xhr这两类请求最可能涉及数据上报。5.2 第三方域名占比审计中的另一个重要指标是第三方域名占比。我以“工具声明域名”或“工具品牌域名”作为基准把其余域名的请求全部标记为第三方请求。统计结果如下743 个 URL 涉及 61 个不同域名其中 38 个属于第三方域名第三方请求数量占总请求数的 46.7%。这并不意味着所有第三方请求都有问题。很多工具会使用 Google Fonts、CDN 资源、GitHub Pages 做静态托管这些都属于正常请求。但需要警惕的是以下几类聊天客服 SDK用户输入 URL 后工具可能会加载在线客服系统把当前页面地址透传给客服错误监控 SDK如 Sentry 之类通常会上报错误信息和当前页面地址广告联盟脚本某些工具会在结果页展示广告请求中会包含长串的 base64 参数数据统计平台不只是访问统计还可能上报用户输入的具体关键词或 URL。5.3 值得警惕的输入传递行为审计中我重点关注一个细节原始输入的 URL 是否会被拼接到第三方请求参数中。例如https://third-party.example/track?sourcePIXELtargethttps%3A%2F%2Fexample.com%2F如果第三方域名同时满足不是工具自身域名请求参数中包含完整的用户输入 URL请求类型为 xhr 或 image那么这条请求就存在“用户输入数据外传”的风险。对于这类发现审计报告中应该单独建立一列contains_input值为 true 或 false方便后续人工复核。6. 常见问题与排查思路在实际运行这套审计方案时有几个非常容易出现的问题下面按错误现象、常见原因、解决思路的格式整理出来。问题现象常见原因解决思路npm install 安装 puppeteer 时卡住需要下载 Chromium网络较慢使用PUPPETEER_DOWNLOAD_PATH配置本地缓存或改用系统 Chrome 连接启动 Puppeteer 提示沙箱错误Linux 环境下 root 用户未配置沙箱启动参数加--no-sandbox --disable-setuid-sandbox打开页面超时种子 URL 无法访问或响应过慢调大 timeout并在异常时跳过不要中断整个流程部分请求状态码始终为 0页面跳转太快response 对应关系未匹配改为在 request 阶段记录response 阶段按 requestId 关联CSV 里出现逗号导致列错位URL 参数中可能包含逗号对字段做转义处理或使用 JSON 作为中间格式某些 URL 被去重后数量下降明显同一个资源被多个页面反复引用区分“原始请求数”和“唯一 URL 数”在报告中同时保留两个指标扫描页面时提示 403目标站点屏蔽了爬虫请求设置更真实的 User-Agent并降低并发频率结果里出现大量invalid-domainURL 格式化失败可能是协议缺失统一使用new URL(resourceUrl, baseUrl)补全相对路径这里特别提一下 Puppeteer 依赖安装的问题。因为项目热词里提到了installing node.js dependencies很多读者第一次接触时最容易在这里卡住。如果你的网络环境不适合下载 Chromium可以改成连接本机已有的 Chromeconst browser await puppeteer.launch({ executablePath: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome, headless: new, });Windows 用户一般路径是const browser await puppeteer.launch({ executablePath: C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe, headless: new, });这样可以跳过 Chromium 下载步骤同时也能保证浏览器内核和本机版本保持一致。7. 最佳实践与工程建议7.1 审计范围要声明清楚URL 审计本质上是一项“黑盒测试”它的结论只对“本次审计的 URL 集合”负责。写报告时一定要说明种子 URL 数量每个工具的取数方式审计时间段Puppeteer 和浏览器内核版本。原因很简单工具随时可能更新代码、更换统计服务商、调整 CDN 策略。一个月前的审计结果不能代表当前状态。7.2 区分工具自身域名与第三方域名不要简单地把“非工具域名”全部判定为风险。正确做法是先从官网、隐私政策、开源仓库中提取工具声明的域名在代码中维护一份allowedDomains.json第三方的定义是“不在白名单内的域名”。这样可以显著降低误报率。7.3 涉及数据发送时强调最小授权如果你的审计脚本会向在线工具提交 URL请务必注意只提交你自己拥有或有权访问的 URL不要提交包含敏感 Cookie 或登录态的地址上传到工具的内容可能存储在第三方服务器需要提前评估风险对请求内容做脱敏处理比如移除 URL 中的 token、session、私有参数。在真实项目里审计在线服务时最好使用专用测试环境或本地搭建的服务避免把生产数据暴露给第三方工具。7.4 自动化与定时任务审计不是一次性工作。工具更新版本后请求行为可能发生变化。你可以把审计脚本配置成定时任务比如每周运行一次crontab -e在 Linux 环境下0 2 * * 1 cd /path/to/url-audit npm run audit logs/audit.log 21这样每周一会自动执行一次审计并把日志写入文件方便追踪历史变化。7.5 输出结果要有“可读性”几十万个请求堆在一起没有意义报告应该分三个层级汇总层总请求数、唯一 URL 数、域名数量、第三方占比分组层按工具维度统计各工具请求分布明细层完整 CSV供安全团队或数据团队复核。可以额外生成一个简单的 HTML 报告把所有榜单表格化方便非技术同事查看。8. 总结与后续延伸这次审计项目让我对“免费工具”有了更具体的认知。很多工具并不是简单地在浏览器里打开一个页面它的背后是一套完整的服务端转发、数据采集和商业分析链路。743 个 URL 不算多但足以勾勒出 8 类工具的大致行为轮廓。如果你也想复现这个审计项目建议按三步推进先跑通基础采集环境整理好 10 个左右种子 URL跑通 Puppeteer 的 request 监听逻辑再扩充工具列表把要审计的工具逐个接入记录每个工具请求的域名和资源类型最后做分类与报告维护白名单域名把第三方请求、输入透传、异常上报单独列出来形成可追踪的表格。下一步还可以继续尝试对抓取到的请求做响应体内容分析判断哪些响应里包含了用户原始输入对比同一工具在“登录前”和“登录后”的请求差异引入更细粒度的网络代理层把 HTTPS 证书解密后的 URL 做全链路记录把审计结果自动生成 Markdown 或 PDF 报告方便团队评审。希望这篇教程能给你提供一套可以复用的工具审计思路。如果你也在做类似的浏览器工具调研或前端请求治理欢迎把你在审计中发现的有趣请求行为分享出来一起交流。
返回列表