Insecure Direct Object Reference (IDOR)
1. O que é IDOR e por que isso importa?
Insecure Direct Object Reference (IDOR) é uma vulnerabilidade de segurança que ocorre quando uma aplicação expõe uma referência direta a um objeto interno (como um identificador de banco de dados, chave primária ou nome de arquivo) sem realizar a devida validação de autorização. Em termos simples: um usuário mal-intencionado pode manipular um parâmetro para acessar recursos que não deveria.
A diferença crucial entre IDOR e outros problemas de autorização (como Broken Access Control genérico) está na natureza da referência. Enquanto o Broken Access Control cobre qualquer falha na implementação de políticas de acesso, o IDOR é um subconjunto específico onde a vulnerabilidade está na exposição direta de identificadores de objetos internos.
Os impactos reais de IDOR são devastadores:
- Vazamento de dados privados: informações médicas, financeiras, mensagens pessoais
- Escalada de privilégios: acesso a funcionalidades administrativas
- Fraudes: manipulação de transações financeiras ou pedidos de outros usuários
2. Como IDOR se manifesta na prática
IDOR pode aparecer em diferentes locais de uma requisição:
Em URLs (GET requests):
GET /api/usuario/123
GET /pedido/456/documento/789
Em parâmetros de requisições POST/PUT/DELETE:
POST /api/transferencia
Body: {"conta_origem": "12345", "conta_destino": "67890", "valor": 1000}
Em cabeçalhos ou cookies:
Cookie: user_id=12345; session_token=abc123
Em referências indiretas:
GET /download?file=relatorio_financeiro_2024.pdf
3. Exemplos clássicos de exploração de IDOR
Exemplo 1: Alteração de ID na URL
Um sistema de mensagens privadas permite que usuários vejam suas próprias mensagens através do endpoint:
GET /mensagem/42
Um atacante simplesmente altera o ID:
GET /mensagem/43
GET /mensagem/44
GET /mensagem/45
Se o servidor não validar se a mensagem pertence ao usuário logado, o atacante terá acesso a todas as mensagens.
Exemplo 2: Manipulação de API RESTful
Uma API bancária permite consultar extratos:
GET /api/v1/contas/98765/extrato
O atacante tenta:
GET /api/v1/contas/98766/extrato
GET /api/v1/contas/98767/extrato
Sem validação de propriedade, dados financeiros de outros clientes são expostos.
Exemplo 3: Operações destrutivas
DELETE /api/usuario/123
Um atacante pode deletar contas de outros usuários apenas alterando o ID.
4. Diferença entre autenticação e autorização
Este é um dos pontos mais críticos e frequentemente mal compreendidos:
- Autenticação: confirma quem você é (ex: login com usuário/senha)
- Autorização: define o que você pode fazer (ex: acesso apenas aos seus próprios dados)
O erro comum é assumir que, porque um usuário está autenticado, ele automaticamente tem permissão para acessar qualquer recurso. IDOR ocorre exatamente quando a autorização por objeto não é verificada após a autenticação.
Código vulnerável (exemplo conceitual):
# Apenas verifica se o usuário está logado
if usuario_autenticado:
dados = banco.buscar_usuario(id_usuario) # IDOR aqui!
return dados
Código corrigido:
# Verifica autenticação E autorização
if usuario_autenticado:
if usuario_logado.id == id_usuario or usuario_logado.eh_admin:
dados = banco.buscar_usuario(id_usuario)
return dados
else:
return erro_permissao
5. Estratégias de prevenção no backend
Verificações de propriedade (ownership checks)
Sempre valide se o recurso pertence ao usuário que está fazendo a requisição:
function buscarMensagem(id_mensagem, usuario_logado):
mensagem = banco.buscar_mensagem(id_mensagem)
if mensagem.remetente_id != usuario_logado.id AND
mensagem.destinatario_id != usuario_logado.id:
return erro_403
return mensagem
Uso de identificadores não previsíveis
Substitua IDs sequenciais por UUIDs ou hashes:
# Vulnerável
GET /usuario/123
# Mais seguro
GET /usuario/a1b2c3d4-e5f6-7890-abcd-ef1234567890
UUIDs dificultam a enumeração, mas não substituem a validação de autorização!
Validação no servidor
Nunca confie na validação do frontend. Toda verificação deve ser refeita no servidor:
# FRONTEND (não confiável)
if (usuario.id === recurso.usuario_id) {
fetch(`/api/recurso/${recurso.id}`)
}
# BACKEND (obrigatório)
if (usuario_logado.id !== recurso.usuario_id && !usuario_logado.eh_admin) {
return res.status(403).json({ erro: 'Acesso negado' })
}
6. Boas práticas no design de APIs
Evitar expor IDs internos diretamente
Use referências indiretas (tokens, slugs):
# Ruim
GET /api/pedido/456
# Melhor
GET /api/pedido/token_aleatorio_unicopara_cada_usuario
Implementar autorização baseada em contexto
Combine RBAC (Role-Based Access Control) com ABAC (Attribute-Based Access Control):
function autorizarAcesso(usuario, recurso, acao):
# RBAC: verifica papel do usuário
if usuario.papel == 'admin':
return True
# ABAC: verifica atributos do contexto
if usuario.id == recurso.proprietario_id:
return True
# Verifica permissões específicas
if usuario.tem_permissao(acao, recurso.tipo):
return True
return False
Logging e monitoramento
Registre tentativas suspeitas:
function logTentativaAcesso(usuario_id, recurso_id, sucesso):
if not sucesso:
alerta_seguranca(usuario_id, recurso_id, 'Tentativa de IDOR')
salvar_log(usuario_id, recurso_id, timestamp, sucesso)
7. Testes e ferramentas para detectar IDOR
Testes manuais
- Autentique-se como Usuário A
- Capture uma requisição legítima (ex:
GET /mensagem/1) - Altere o ID na URL ou corpo (
GET /mensagem/2) - Verifique se a resposta contém dados do Usuário B
Ferramentas automatizadas
- Burp Suite: use o módulo Intruder para testar variações de IDs
- OWASP ZAP: scanner automático que identifica IDOR em muitos casos
- Scripts personalizados: crie testes automatizados com Python/Requests
Checklist de code review
- [ ] Todos os endpoints que recebem IDs de objetos validam propriedade?
- [ ] Operações em lote verificam cada item individualmente?
- [ ] APIs internas e externas têm políticas de acesso diferentes?
- [ ] IDs expostos são previsíveis (sequenciais)?
- [ ] A autorização é verificada no servidor, não no frontend?
8. Mitigação em cenários complexos
IDOR em sistemas multi-tenant
Em sistemas onde múltiplos clientes compartilham a mesma infraestrutura:
function buscarDadosTenant(tenant_id, usuario):
# Verifica se o usuário pertence ao tenant
if usuario.tenant_id != tenant_id:
return erro_403
# Filtra todos os dados pelo tenant
dados = banco.query(
"SELECT * FROM dados WHERE tenant_id = ?",
[tenant_id]
)
return dados
Operações em lote (batch requests)
Valide cada item individualmente, não apenas o lote:
function processarLote(ids_recursos, usuario):
for id_recurso in ids_recursos:
recurso = banco.buscar(id_recurso)
if recurso.proprietario_id != usuario.id:
return erro_403 # Falha total ou parcial
processar(recurso)
APIs públicas vs internas
Diferencie claramente as políticas de acesso:
# API pública (acesso restrito)
GET /api/publico/produto/123
# API interna (requer autenticação adicional)
GET /api/interno/admin/usuario/123
Referências
- OWASP: Insecure Direct Object Reference (IDOR) — Documentação oficial da OWASP sobre o ataque IDOR, com exemplos e definições detalhadas
- PortSwigger: What is IDOR (Insecure Direct Object Reference)? — Guia completo do Burp Suite sobre IDOR, incluindo laboratórios práticos para teste
- OWASP API Security Top 10: Broken Object Level Authorization — Categoria relacionada a IDOR no contexto de APIs, com exemplos de exploração
- HackerOne: IDOR Vulnerability Examples — Casos reais de IDOR reportados em programas de bug bounty, com impactos documentados
- CWE-639: Authorization Bypass Through User-Controlled Key — Classificação oficial do MITRE para vulnerabilidades de IDOR, com descrição técnica e exemplos
- Auth0 Blog: Preventing Insecure Direct Object Reference (IDOR) — Artigo técnico com estratégias de prevenção e implementação de autorização por objeto