Um id na URL não prova que o dado é seu: o filtro que quase toda API esquece
Peguei uma API pra revisar e testei a coisa mais boba possível: troquei o id do meu recurso pelo id do vizinho, direto na URL.
GET /orders/10432 -> 200, o meu pedido
GET /orders/10431 -> 200, o pedido de OUTRA conta
Sem erro, sem 403. O dado veio inteiro: nome, valor, e-mail de contato.
O endpoint conferia autenticação (o token era válido) e parava aí. Nunca perguntava se aquele pedido pertencia a quem pediu. O código problemático costuma ser este:
// vulnerável: confia no id que veio da URL
app.get('/orders/:id', auth, async (req, res) => {
const order = await db.order.findUnique({ where: { id: req.params.id } })
res.json(order)
})
A correção é uma cláusula. O dono entra no WHERE, não numa checagem que alguém lembra de fazer depois:
// corrigido: dono no WHERE
app.get('/orders/:id', auth, async (req, res) => {
const order = await db.order.findFirst({
where: { id: req.params.id, userId: req.user.id },
})
if (!order) return res.sendStatus(404)
res.json(order)
})
E o mais importante: um teste negativo, que tenta o acesso indevido e exige a porta fechada. Rode um por endpoint que recebe id:
test('não vaza pedido de outro dono', async () => {
const res = await request(app)
.get(`/orders/${otherUsersOrderId}`)
.set(`Authorization`, `Bearer ${meuToken}`)
expect(res.status).toBe(404) // ou 403
})
Isso é broken access control, o item nº 1 do OWASP Top 10 há anos. É a classe de bug mais barata de fechar e a mais fácil de subir pra produção sem perceber, porque num fluxo normal ninguém digita o id de outra pessoa — o problema só existe pra quem procura.
Versão completa no blog: https://revin.com.br/pt/blog/troquei-um-numero-na-url-e-vi-o-pedido-de-outro-cliente