示意性示例报告
一张发票被返回给另一个账户。
这是一个虚构例子,说明一条发现如何从证据记录到复测。域名、账户和数据都是编造的。
范围
范围内:app.example.com 和 api.example.com/v1,使用两个已授权的测试账户。按约定排除:第三方支付页面。身份验证在确定范围时已确认,这次外部测试不需要源代码。
测试账户
测试账户 A
session=test-account-a
拥有发票 demo-01
测试账户 B
session=test-account-b
拥有发票 demo-02
请求和响应
以测试账户 A 登录后,我们请求了一张属于测试账户 B 的发票。
请求
GET /api/invoices/demo-02 HTTP/1.1 Host: api.example.com Cookie: session=test-account-a
响应200 OK
{ "invoice": "demo-02",
"owner": "test account B"}API 把账户 B 的发票返回给了账户 A。研究人员在报告之前复现了这个结果。
复现步骤
- 在 app.example.com 以测试账户 A 登录。
- 发送GET /api/invoices/demo-02 使用账户 A 的会话。
- 看到包含账户 B 发票的 200 响应。
- 再以账户 B 重复一次,确认 demo-02 属于 B。
代码上下文 Defense 的拟定范围
路径:Web 应用,然后是账单服务,然后是发票查询。处理函数按 id 加载发票并返回,没有核对所有者。
export async function getInvoice(req, res) {
const invoice = await invoices.find(req.params.id)
return res.json(invoice) // owner not compared}建议做法:在返回发票之前,把发票所有者与当前登录账户做比较。
开发人员复核
贵方开发人员复核指导并决定如何修改。一种可能的写法:
const invoice = await invoices.find(req.params.id) + if (!invoice || invoice.accountId !== req.session.accountId) {+ return res.status(403).end()+ } return res.json(invoice)
复测预期
在贵方团队修复之后,我们在约定范围内重复同一条已授权请求。
在贵方团队修复后的预期:403
账户 A 自己的发票仍应返回 200。403
账户 A 自己的发票仍应返回 200。403