pular para o conteúdo
voltar ao mapa
técnica
etapa: design
3 min de leitura

A01 Broken Access Control

A autenticação diz quem você é; o controle de acesso diz o que você pode fazer. Quando o servidor esquece de verificar em cada requisição, trocar um id na URL é tudo o que um atacante precisa.

Burp Suite (Autorize)OWASP ZAPPostman or curl with two accountsSemgrepCasbin / OPA

O que é

O controle de acesso aplica a política para que usuários não ajam fora das permissões previstas. Ele quebra quando o servidor confia em algo que o cliente controla: um id na URL, um campo oculto, uma claim de papel, um cookie. O resultado é ler dados de outras pessoas, alterá-los ou alcançar funções de administração.

O :2025 o lista como A01. As duas formas do dia a dia são (insecure direct object reference, também chamado em segurança de APIs): a requisição nomeia um objeto e o servidor o devolve sem perguntar se quem chama é o dono; e a falta de verificações no nível de função: existe um endpoint para administradores, mas nada impede um usuário comum de chamá-lo.

Como o ataque funciona

O atacante faz login como usuário comum, abre a aba de rede do navegador e encontra uma requisição como GET /api/invoices/1041. Troca 1041 por 1042 e lê a fatura de outra pessoa. Se os ids forem sequenciais, um script de dez linhas percorre a tabela inteira.

  • Altere um id no path, na query string, no corpo JSON ou em um header, e veja se a resposta muda de dono.
  • Chame diretamente uma rota de admin ou interna (/admin/users, DELETE /api/users/7) com uma sessão comum.
  • Envie campos extras que o formulário nunca mostra ("role": "admin"): o deixa o cliente definir o que não deveria.
  • Esconder o botão na UI: a UI não é um controle, o servidor é.

Código vulnerável

Cada handler verifica se o usuário está logado e depois carrega o objeto apenas pelo id. Nada liga a fatura a quem chama.

invoices.js (Express + Prisma)javascript
app.get("/api/invoices/:id", requireLogin, async (req, res) => {
  const invoice = await db.invoice.findUnique({ where: { id: Number(req.params.id) } });
  res.json(invoice); // qualquer usuário logado pode ler qualquer fatura
});
invoices.py (Flask + SQLAlchemy)python
@app.get("/api/invoices/<int:invoice_id>")
@login_required
def get_invoice(invoice_id):
    invoice = Invoice.query.get_or_404(invoice_id)
    return jsonify(invoice.to_dict())
InvoiceController.java (Spring)java
@GetMapping("/api/invoices/{id}")
public Invoice get(@PathVariable long id) {
    return repo.findById(id).orElseThrow(NotFoundException::new);
}

A correção

Coloque a propriedade na própria consulta, de modo que uma linha que não é sua simplesmente não seja encontrada. Responda da mesma forma para "não existe" e "não é sua", para que a resposta não confirme quais ids são válidos. A regra é a mesma em qualquer linguagem: autorize no servidor, em cada requisição, negando por padrão.

invoices.jsjavascript
app.get("/api/invoices/:id", requireLogin, async (req, res) => {
  const invoice = await db.invoice.findFirst({
    where: { id: Number(req.params.id), ownerId: req.user.id },
  });
  if (!invoice) return res.status(404).end();
  res.json(invoice);
});
invoices.pypython
@app.get("/api/invoices/<int:invoice_id>")
@login_required
def get_invoice(invoice_id):
    invoice = Invoice.query.filter_by(
        id=invoice_id, owner_id=current_user.id
    ).first_or_404()
    return jsonify(invoice.to_dict())
InvoiceController.javajava
@GetMapping("/api/invoices/{id}")
public Invoice get(@PathVariable long id, @AuthenticationPrincipal User user) {
    return repo.findByIdAndOwnerId(id, user.getId())
               .orElseThrow(NotFoundException::new);
}
admin.js (verificação no nível de função, negar por padrão)javascript
const requireRole = (role) => (req, res, next) =>
  req.user?.roles?.includes(role) ? next() : res.status(403).end();

app.delete("/api/users/:id", requireLogin, requireRole("admin"), deleteUser);

Prove a correção

Um teste de regressão usa dois usuários. O usuário A cria uma fatura; o usuário B a solicita e deve receber 404 (ou 403), e uma chamada anônima deve receber 401. Rode-o no CI para que um refactor não remova a verificação sem ninguém notar. O mesmo formato de teste funciona em qualquer linguagem e para qualquer tipo de objeto.

test_invoices_access.py (pytest)python
def test_user_cannot_read_other_users_invoice(client, alice, bob):
    invoice_id = client.post("/api/invoices", json={"total": 10}, auth=alice).json()["id"]

    assert client.get(f"/api/invoices/{invoice_id}", auth=alice).status_code == 200
    assert client.get(f"/api/invoices/{invoice_id}", auth=bob).status_code == 404
    assert client.get(f"/api/invoices/{invoice_id}").status_code == 401
invoices.test.js (supertest)javascript
test("bob cannot read alice's invoice", async () => {
  const { body } = await request(app).post("/api/invoices").set(alice).send({ total: 10 });
  await request(app).get(`/api/invoices/${body.id}`).set(bob).expect(404);
  await request(app).get(`/api/invoices/${body.id}`).expect(401);
});

Checklist

  • : uma rota sem regra explícita é proibida, não aberta.
  • Aplique a propriedade em um único lugar compartilhado (um helper de consulta, um policy engine), e não copiando verificações em cada handler.
  • Não confie em ids, papéis ou preços que vêm do cliente. Derive a identidade da sessão.
  • Use ids imprevisíveis quando útil (UUIDs), mas nunca como único controle: um id impossível de adivinhar não é autorização.
  • Registre as falhas de controle de acesso e alerte sobre rajadas: alguém enumerando ids é visível.
  • Teste com duas contas a cada release. Scanners automatizados raramente entendem quem deveria ver o quê.
OWASP Top 10A lista de referência mais famosa de riscos de segurança em aplicações web (A01 acesso quebrado, A02 falhas criptográficas, A03 injeção...), revisada a cada alguns anos pela comunidade OWASP. É um mapa educacional de categorias de risco — não um checklist de ferramenta nem uma certificação.ver no glossário →IDORreferência direta insegura a objeto · BOLAA requisição nomeia um objeto (um id) e o servidor o devolve sem verificar se quem chama pode vê-lo. É a forma mais comum de controle de acesso quebrado e, em APIs, chama-se broken object level authorization.ver no glossário →BOLAbroken object level authorizationUma API aceita o id de um objeto e o devolve sem verificar se quem chama pode acessá-lo. É a forma de IDOR em APIs.ver no glossário →Mass assignmentUma API associa campos enviados pelo cliente a um objeto, permitindo que quem chama defina campos (como um papel) que não deveria controlar.ver no glossário →Negar por padrãoO acesso é proibido a menos que uma regra o permita explicitamente, de modo que uma rota esquecida falha fechada em vez de aberta.ver no glossário →

conexões no mapa

atualizado em 14/10/2026 · fontes para aprofundar · reportar erro

em outros domínios

Desbloqueia

teste o que entendeu

Carregando perguntas…