跳到主要内容

客户端可信数据的认证绕过

这一类漏洞的核心思想是:服务端用攻击者可控的请求成分(请求头、Cookie、请求体字段)做鉴权决策。鉴权数据一旦来自客户端,绕过就只是改一个值的事。

仅限授权环境

认证绕过测试只允许在授权靶场或明确授权的范围内进行,并避免影响真实用户会话。


手法清单

可控成分手法触发条件
X-Forwarded-For伪造 127.0.0.1 绕过 IP 白名单服务端只信任该头,不校验真实链路
Referer含 Host 字符串即跳过 JWT 校验校验逻辑误用 Referer 做“本站”判断
自定义 debug 头X-Sentinel-Debug: 1 直接发 token调试开关遗留在线上且无鉴权
Cookie篡改服务端信任的分数/状态值状态存客户端且无签名校验
请求体字段tenant_id: "system" 跨租户租户上下文取自请求体而非会话

方法 A:伪造请求头

X-Forwarded-For 绕过 IP 白名单

curl -sS -H 'X-Forwarded-For: 127.0.0.1' http://target/admin

仅当服务端 只看这个头 判定“来自本机”时生效;它伪造不了 TCP 层的真实来源。

Referer 顶替 JWT 校验

某些设计中,Referer 头里含 Host 字符串就视为“内部跳转”,跳过 JWT 校验。测试时只需带上:

curl -sS -H 'Referer: http://target/' http://target/api/protected

调试开关头

遗留的 debug 头可能直接放行认证并发 token:

curl -sS -H 'X-Sentinel-Debug: 1' http://target/auth

方法 B:篡改客户端状态

服务端若信任 Cookie 或请求体里提交的 分数、状态、租户 等值,直接改值即可越权:

# 跨租户:把请求体中的租户字段改成特权租户
{"tenant_id": "system", "action": "list"}

判断标准:这个值在服务端有没有与会话绑定的二次校验。没有,就是可篡改点。


方法 C:PHP strcmp() 数组混淆

strcmp() 比较数组与字符串时返回 NULL,而 NULL == 0 在松散比较下为真,导致比较逻辑被绕过:

# 提交数组参数,让 strcmp 返回 NULL
username[]=admin&password[]=foo

若代码写成 if (strcmp($user, $pass) == 0) 这类松散比较,登录校验直接通过。适用前提是目标为 PHP 且使用了 strcmp + 松散比较的组合。


方法 D:JWT 空密钥伪造

服务端用 空字符串密钥 验签 HS256 时,客户端可以用同样的空密钥签出任意 claims:

python3 -c 'import jwt; print(jwt.encode({"user": "admin", "role": "admin"}, "", algorithm="HS256"))'

常需配合 X-Forwarded-For: 127.0.0.1 通过依赖来源头的附加校验。拿到伪造 token 后替换 Cookie/Authorization 头重放请求确认。


定位信任点:差分测试

对每个可疑请求成分做 带/不带对照,观察状态码与响应体的差异:

对照组请求观察点
基线不带该头/Cookie状态码、错误信息
实验带伪造值状态码是否变化、是否返回敏感数据

差分有反应的位置,就是服务端的信任点;再沿该点尝试最小化绕过。带畸形值(数组、空值、超长)的边界输入可进一步暴露松散比较与类型混淆问题。


复核要点

检查点说明
鉴权依据来源请求头、Cookie、请求体中的任何鉴权字段都要列为可疑
比较函数PHP 松散比较与 strcmp 数组返回值必须验证
JWT 密钥空密钥、弱密钥都要实测伪造
越权范围确认用只读接口确认影响面,避免修改生产数据

防御建议

  1. 不用任何请求头(X-Forwarded-ForReferer、自定义 debug 头)做鉴权决策。
  2. 会话状态、分数、租户上下文一律由服务端维护,客户端只传不透明 ID。
  3. 密码与 token 比较使用常量时间比较函数,并显式校验类型,避免 NULL == 0 类混淆。
  4. JWT 使用足够强度的密钥并集中管理,禁止空密钥/默认密钥上线。
  5. 下线所有调试开关,或在网关层直接丢弃 debug 类请求头。