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

  1. Autentique-se como Usuário A
  2. Capture uma requisição legítima (ex: GET /mensagem/1)
  3. Altere o ID na URL ou corpo (GET /mensagem/2)
  4. 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