第 7 章 · 03 协议专项(GraphQL / OAuth 2.0 / OIDC) 本节摘要:协议层的攻击面与应用框架不同——它由协议规范本身的特性与实现方的常见误解决定。GraphQL 的 resolver 级鉴权常在父字段检查、子字段假设已校验,留下「边/子 resolver 缺口」;alias batching 能绕过每请求限流做枚举;federation 的 让网关与子图的鉴权边界不一致。OAuth 2.0/OIDC 的失败几乎总通向账户接管:redirect URI 操纵、authorization code 泄露/重放、state/nonce 缺失、PKCE 降级、token audience 混淆。
本节摘要:协议层的攻击面与应用框架不同——它由协议规范本身的特性与实现方的常见误解决定。GraphQL 的 resolver 级鉴权常在父字段检查、子字段假设已校验,留下「边/子 resolver 缺口」;alias batching 能绕过每请求限流做枚举;federation 的
_entities让网关与子图的鉴权边界不一致。OAuth 2.0/OIDC 的失败几乎总通向账户接管:redirect URI 操纵、authorization code 泄露/重放、state/nonce 缺失、PKCE 降级、token audience 混淆。本节合并这两个协议,聚焦「协议特有的攻击面与实现陷阱」,让你理解为什么「每个 redirect、每个 client identifier、每个 token 交换都是授权边界,而非便利层」。
⚠️ 仅限授权测试:本节所有技术仅用于你自己的应用或有书面授权的渗透测试。OAuth/GraphQL 的账户接管测试尤其要控制影响,避免干扰真实用户。未经授权对他人系统使用这些技术是非法的。
阅读完本节,你应当能够:
_service.sdl、_entities 跨子图 IDOR)。GraphQL 安全测试聚焦 resolver 级鉴权、字段/边访问控制、batching 滥用、federation 信任边界。与 REST 不同,GraphQL 把「查询哪些字段」交给客户端,这放大了鉴权漂移的影响。
操作:query、mutation、subscription;persisted query / Automatic Persisted Query(APQ)。
传输:HTTP POST/GET(application/json 或 application/graphql);WebSocket:graphql-ws、graphql-transport-ws 协议;multipart 文件上传。
schema 特性:introspection(__schema、__type);directive(@defer、@stream、自定义 auth directive @auth/@private);自定义标量(Upload、JSON、DateTime);Relay:global node ID、connection/cursor、interface/union。
架构:federation(Apollo、GraphQL Mesh):_service、_entities;网关 vs 子图鉴权边界。
端点发现:
POST /graphql {"query":"{__typename}"} POST /api/graphql {"query":"{__typename}"} POST /v1/graphql {"query":"{__typename}"} POST /gql {"query":"{__typename}"} GET /graphql?query={__typename}
检查 GraphiQL/Playground 是否带凭证暴露(跨源带 cookie 可经 postMessage 桥接泄露数据)。
schema 获取:introspection 开启时:
{__schema{types{name fields{name args{name}}}}}
introspection 关闭时,经以下推断:__typename 探候选字段;字段建议错误(提交近似名收割建议);"Expected one of" 错误暴露枚举值;类型强制错误暴露字段结构;错误分类:「未知字段」vs「未授权字段」的不同错误码暴露存在性。
schema 映射:映射根操作、对象类型、interface/union、directive、自定义标量。识别敏感字段:email、token、role、billing、API key、admin 标志、文件 URL。注意子 resolver 在父假设下可能跳过鉴权的级联路径。
字段级 IDOR:用 alias 在单请求对比归属 vs 外国对象:
query { own: order(id:"OWNED_ID") { id total owner { email } } foreign: order(id:"FOREIGN_ID") { id total owner { email } } }
边/子 resolver 缺口:父 resolver 查鉴权,子 resolver 假设已校验:
query { user(id:"FOREIGN") { id privateData { secrets } # 子可能跳过鉴权 } }
Relay node 解析:解码 base64 global ID,交换 type/id 对:
query { node(id:"VXNlcjoxMjM=") { ... on User { email } } }
确保每类型的鉴权在 resolver 内强制。验证 connection 过滤(归属者/租户)在分页前应用;游标篡改不应跨归属边界。
Mutation 绕过:探 mutation 的部分更新绕过校验(JSON Merge Patch 语义);测 mutation 接受额外字段传给下游逻辑。
经 alias 枚举(绕过每请求限流,暴露逐字段 vs 每请求鉴权不一致):
query { u1:user(id:"1"){email} u2:user(id:"2"){email} u3:user(id:"3"){email} }
数组 batching(若支持,非标准):提交多操作实现部分失败、绕过限制。
类型混淆:{id: 123} vs {id: "123"}、{id: [123]} vs {id: null}、{id: 0} vs {id: -1}。
重复 key: {"id": 1, "id": 2}——parser 优先级不同,可能绕过校验。也测默认参数值。
额外字段:input object 发意外 key,后端可能传给 resolver 或下游逻辑。
游标操纵:解码游标(通常 base64)操纵 offset/ID、跳过过滤、跨归属边界。
directive 滥用:
@defer/@stream:
query { me { id } ... @defer { adminPanel { secrets } } }
可能在增量交付返回受门控数据。确认服务端支持增量交付。
自定义 directive:@auth、@private 等常只标注意图不强制——验证每个 resolver 路径的实际检查。
复杂度攻击:
Fragment 炸弹:
fragment x on User { friends { ...x } } query { me { ...x } }
测深度/复杂度限制、查询成本分析器、超时。
宽选择集:滥用选择集与 fragment 强制过度获取敏感子字段。
federation 利用:
SDL 暴露:
query { _service { sdl } }
entity 物化:
query { _entities(representations:[ {__typename:"User", id:"TARGET_ID"} ]) { ... on User { email roles } } }
网关可能强制鉴权;子图 resolver 可能不强制。找跨子图 IDOR(归属检查不一致)。
subscription 安全:只在握手鉴权、不逐消息;经 filter 参数订阅他人 channel;跨租户事件泄露;滥用 subscription resolver 的 filter 参数引用外国 ID。
persisted query 滥用:客户端 bundle 泄露的 APQ 哈希;用攻击者变量重放特权操作;常见操作的哈希爆破;验证 hash→操作映射强制 principal 与操作白名单。
CORS/CSRF:cookie 鉴权 + GET 查询启用 mutation 经查询参数的 CSRF;GraphiQL/Playground 跨源带凭证泄露数据;缺 SameSite 与 origin 校验。
文件上传(GraphQL multipart spec):多个 Upload 标量;文件名/路径穿越技巧;意外 content-type、超大 chunk;返回 URL 的服务端归属/scoping。
查询重塑:注释与块字符串("""...""");Unicode 转义;alias/fragment 间接;JSON 变量 vs 内联参数;GET vs POST vs application/graphql。
fragment 拆分:把字段拆进 fragment 与 inline spread 避开朴素签名:
fragment a on User { email } fragment b on User { password } query { me { ...a ...b } }
传输切换:
Content-Type: application/json Content-Type: application/graphql Content-Type: multipart/form-data GET with query params
时序与限流:HTTP/2 多路复用与连接复用拓宽时序窗;batching 绕限流。
命名技巧:大小写/下划线变体;Unicode 同形字(服务端依赖);alias 掩盖敏感字段名。
缓存混淆:CDN 缓存缺 Vary on Authorization;变量操纵影响缓存 key;重定向与 304/206 行为泄露部分响应。
_service 与 _entities 的子图鉴权缺口。验证:配对请求(归属者 vs 非归属者)展示非授权访问;resolver 级绕过(父检查在、子字段暴露数据);传输对等证明(HTTP 与 WebSocket 同操作);federation 绕过(_entities 无子图鉴权访问数据);最小 payload 带精确选择集与变量形态;记录漏强制的精确 resolver 路径。
💡 Pro Tips:GraphQL 的「父字段鉴权 ≠ 子字段鉴权」——
user(id){privateData{secrets}}里privateDataresolver 常假设父已校验;alias batching 是被低估的枚举利器,既绕限流又暴露逐字段鉴权不一致;introspection 关了别放弃,__typename+ 字段建议错误一样能还原 schema;federation 的_entities是网关-子图鉴权边界的破绽。
OAuth 与 OIDC 的失败常通向账户接管、token 窃取、跨客户端 token 混淆。把每个 redirect、每个 client identifier、每个 token 交换都当授权边界——而非便利层。
流程:授权码(带/不带 PKCE);implicit(legacy)、hybrid、设备授权、client credentials;refresh token 轮换、token introspection、revocation。
端点:/authorize、/token、/userinfo、/introspect、/revoke、/logout;/.well-known/openid-configuration、/jwks.json;动态客户端注册(若启用)。
token 类型:authorization code、access token、refresh token、ID token;opaque vs JWT 格式;reference token vs 自包含 JWT。
客户端类型:public client(SPA、移动)vs confidential(服务端);多 redirect URI、通配/模式匹配、自定义 URI scheme。
Discovery:
GET /.well-known/openid-configuration GET /oauth2/.well-known/openid-configuration GET /.well-known/oauth-authorization-server
提取:authorization_endpoint、token_endpoint、registration_endpoint、支持的 response_types、code_challenge_methods_supported、grant_types_supported。
客户端枚举:检查 JS bundle、移动 APK/IPA 配置、GitHub 仓库找 client_id、redirect URI、scope;查错误消息的客户端校验提示("invalid redirect_uri"、"unregistered client")。
开放重定向链:注册或猜宽松 redirect 模式:https://app.com/callback、仅路径前缀、子域通配;测:追加路径、fragment、查询注入、@ 技巧、编码斜杠、反斜杠变体。
https://app.com/callback.evil.com https://app.com/callback%2f..%2f@evil.com https://app.com/callback?next=https://evil.com com.app://callback (移动自定义 scheme)
redirect URI 校验绕过:尾斜杠、大小写、端口、scheme 降级(http vs https);IdP 校验器与消费应用间的路径规范化差异;redirect_uri 参数污染(首 vs 末胜);通配子域接受:*.app.com → 注册 attacker.app.com 或找悬空子域。
code 泄露:code 在 URL fragment、Referer 头、浏览器历史、服务端日志、分析工具;过期前重放、缺一次性使用强制;绑定弱时 code 发到错误 redirect_uri。
code 注入/mix-up:攻击者发起流程,受害者完成登录,code 送到攻击者 redirect;mix-up 攻击:在 authorize 与 token 步骤间交换 client_id;token 端点缺 redirect_uri 绑定。
state/nonce:缺失、可预测或可复用的 state → OAuth 登录 CSRF(会话固定、账户链接);OIDC 缺 nonce → ID token 注入/重放;state 不绑定客户端会话或 PKCE verifier。
PKCE 绕过:code_challenge_method 降级(接受 plain 而非 S256);public client 缺 PKCE 要求;code_verifier 不校验或大小写不敏感弱匹配;授权码无 challenge 签发、token 端点接受任意 verifier。
public client 滥用:token 端点对 confidential client 接受无 client_secret 请求;token/introspection 端点仅 client_id 认证;动态注册用攻击者控制的 redirect URI。
secret 泄露:移动应用、SPA、公开仓库里的硬编码 secret;查询串里的 client_secret 或记入访问日志。
scope 与 token:scope 升级——请求 admin/offline_access/openid profile email 超出应用需要,服务端授予全部请求 scope;refresh token 不轮换或复用未检测 → 持久访问;access token 跨服务接受(缺 audience/resource 绑定);token introspection 无正确鉴权返回 active:true。
OIDC 特有:ID token 被资源服务器当 access token(token 混淆);step-up 要求不验 acr、amr、auth_time;userinfo 端点无匹配 access token scope 返回 PII;不验 iss 时跨 issuer 的 sub 碰撞。
Referer 泄露:把授权 redirect 作为攻击者页面的子资源嵌入;策略允许时从 Referer 收割 code。
设备流滥用:用猜测的 code 轮询 device_code 端点(限流缓慢);用户批准攻击者发起的设备登录。
账户链接:OAuth 登录把攻击者 IdP 身份链到受害者本地账户,无需重新认证;邮箱碰撞:不同 IdP provider 的同邮箱。
测试方法:
state 发起 OAuth;流程中交换会话。验证:演示经 redirect 操纵或 Referer 泄露窃取的 authorization code/token;展示用攻击者 OAuth 会话的账户接管或受害者资源访问;证明 CSRF(受害者在适用时无同意 UI 绕过完成登录进攻击者链接会话);记录精确校验缺口(redirect 绑定、PKCE、state、audience);提供完整 authorize → callback → token 请求链的前后证据。
误报:redirect URI 在所有绕过尝试中一致拒绝;public client 正确要求 PKCE S256 与严格 verifier 校验;state/nonce 强制且绑定,CSRF 测试如预期失败;资源服务器正确校验 token audience/issuer;自定义 scheme redirect 需应用所有权证明(已验证 Android/iOS app link)。
影响:经窃取 authorization code/token 的完整账户接管;经 refresh token 窃取的持久访问;经 token 混淆的跨租户/跨客户端数据访问;userinfo 或 ID token claim 泄露的 PII 暴露。
工具:沙箱自带 jwt_tool(已克隆在 /home/pentester/tools/jwt_tool)加 curl——足以覆盖 OAuth/OIDC 的 token 侧。
python3 /home/pentester/tools/jwt_tool/jwt_tool.py <ID_TOKEN> # 解码/检查 python3 /home/pentester/tools/jwt_tool/jwt_tool.py <ID_TOKEN> -X a # alg:none python3 /home/pentester/tools/jwt_tool/jwt_tool.py <ID_TOKEN> -X k -pk pub.pem # RS256->HS256 混淆
curl 手动驱动 authorize → callback → token 链,让你控制每个参数(redirect_uri、client_id、state、PKCE code_challenge/code_verifier),测上面的绑定/降级。人类常用 Burp 的 EsPReSSO(RUB-NDS)SSO 扩展做流程可视化,但它是 GUI,沙箱内优先手动 curl + jwt_tool。
💡 Pro Tips:永远捕获完整重定向链含中间 302 location;对比 authorize 步与 token 步的参数绑定(
redirect_uri、client_id、PKCE);web 与移动客户端都测——校验规则常不同;检查 logout/revocation——"登出"后 token 可能仍有效;把 OAuth 链到合法 redirect_uri 上的开放重定向或 XSS 来外泄 code。
user(id){privateData{secrets}} 的 privateData 常假设父已校验——这是头号缺口。__typename + 字段建议错误 + 错误分类推断,别放弃。_service { sdl } 暴露 schema;_entities(representations:[...]) 找跨子图 IDOR,网关强制鉴权但子图可能不强制。fragment x on User { friends { ...x } })测深度限制;@defer/@stream 可在增量交付返回受门控数据;@auth directive 常只标注不强制。application/json/application/graphql/multipart/GET)避开朴素签名。@ 技巧)与校验绕过(尾斜杠/大小写/端口/scheme 降级/参数污染)是账户接管主路径。client_id;缺/可预测 state = 登录 CSRF;PKCE 降级 plain 或 verifier 弱匹配。下一节进入基础设施层——AWS、GCP、Kubernetes。IAM 过宽、公开存储桶、元数据服务(IMDS)、RBAC、容器逃逸,是云环境的特有攻击面。