先别继续扩大扫描范围。把“正常页面”和“异常页面”的请求逐项对齐,只保留一个变量不同,是缩小复现条件最有效的一步。通常问题不在整条链接是否可达,而在某个参数触发了服务端分支、缓存键差异或前端路由改写,导致同一路径在不同参数下返回不同结果。
从异常页面里挑一个最稳定复现的 URL,记录完整请求行、参数顺序、编码方式、请求方法和请求头中的关键字段。若同一个参数换序后结果发生变化,说明服务端或中间层可能按原始查询字符串做键,而不是按解析后的参数集合做键。此时应把参数顺序和编码形式都写进复现条件,而不是只记参数名。
实际动作是:复制异常 URL,去掉一个参数后重发,观察状态码、响应体和最终落地 URL。若去掉某参数即恢复正常,下一步就只围绕该参数构造最小请求,不再扫描全站。这个动作的价值在于把“某类页面异常”压缩成“某参数参与时才异常”,后续排查对象随之减少。
第一种解释是服务端按参数走不同分支。例如带 ?id=0 时查询为空,程序返回 404 或 500;不带参数时走列表页,返回 200。第二种解释是链路中的缓存、重定向或前端路由改写。例如 CDN 按完整查询字符串缓存,某个参数值命中了旧缓存;或前端把带特定参数的路径重写成不存在的资源。
区分这两种解释的证据不同。服务端分支通常表现为:参数值变化直接改变状态码或响应体结构,且去掉参数后稳定恢复。链路改写通常表现为:同一参数在换网络、换缓存节点或加随机查询参数后结果不同,或者响应头里出现与源站不一致的缓存标记。若异常只在特定参数值出现,优先怀疑服务端分支;若异常随缓存节点或请求头变化,优先怀疑链路改写。
把候选变量列成一张对照表,每次只改一项,其余保持完全一致。可用的对照维度包括:参数是否存在、参数值、参数顺序、编码形式、请求方法、请求头中的 Accept 与 User-Agent、是否带 Cookie、是否经过缓存层。每改一项就记录状态码、最终 URL 和响应体首段特征。若某一项改变后异常消失,该项就是当前最可疑的复现条件。
%2F 与 / 是否得到不同结果。这张表的作用不是穷举,而是让下一步有明确取舍。若异常只在带 Cookie 时出现,就转向会话与权限分支;若只在特定编码下出现,就转向解析与规范化环节。缩小条件后,修复目标也从“整站死链”变成“某参数解析分支”。
假设某列表页在 ?page=2 时正常,在 ?page=0 时返回 404。去掉参数后正常,改成 ?page=1 也正常。此时可先假设服务端对页码做了范围校验,零值落入未处理分支。验证方式是只改页码值,保持路径、请求头和 Cookie 不变,观察 404 是否稳定跟随零值出现。若稳定跟随,下一步就检查该分支的校验逻辑与错误页配置,而不是继续检查全站链接。
若换成加随机查询参数后 404 消失,则假设应改为缓存层按完整查询字符串缓存了旧结果。此时下一步是核对缓存键规则与刷新策略,而不是修改页面模板。两种假设对应完全不同的修复动作,所以必须先拿到能区分它们的证据。
缩小复现条件后,用一句话写清:在什么请求方法、什么参数组合、什么请求头或缓存状态下,异常稳定出现;改动哪一项后恢复正常。把这句话连同原始请求与对照请求一起留存,后续修复和验证都围绕它进行。若暂时无法区分服务端分支与链路改写,就保留两组对照请求,分别交给对应环节核查,不要用一次扫描结果下结论。
需要留意的是,robots.txt 的抓取限制不等于可靠的索引移除,站点地图也不保证收录;这些规则与参数异常是否复现没有直接关系,不能用来替代请求级对照。只有把变量收敛到可重复的最小条件,死链检测的结果才具备可操作的下一步。