A10 Mishandling of Exceptional Conditions
A segurança muitas vezes depende do que acontece quando algo dá errado. Se um erro significa "permitir", um stack trace significa "aqui está o meu código" ou uma operação pela metade significa "estado corrompido", o caminho de falha é a vulnerabilidade.
O que é
O tratamento inadequado de condições excepcionais é a preparação, detecção e resposta indevidas a situações incomuns: dependências que falham, timeouts, entrada malformada, esgotamento de recursos, escritas parciais e estados inesperados. Os erros típicos são falhar aberto, vazar detalhes em mensagens de erro, engolir exceções e deixar transações pela metade.
É uma categoria nova no :2025 (A10) e reúne várias fraquezas mais antigas em torno do tratamento de erros.
Como o ataque funciona
- Fail open: um serviço de autorização sofre timeout e o código trata a exceção como "sem restrição".
- Vazamento de informação: um erro de banco de dados é devolvido ao cliente com o nome da tabela, a consulta e a versão do driver.
- Erros engolidos: um catch vazio esconde uma falha na verificação de assinatura e o processamento continua.
- Estado parcial: um pagamento é cobrado, mas a escrita do pedido falha; tentar de novo duplica a cobrança.
- Esgotamento de recursos: uma entrada sem limite ou uma exceção não tratada em um loop permite que uma única requisição derrube o serviço.
Código vulnerável
A verificação falha aberta, o erro vaza detalhes internos e a atualização em várias etapas não é atômica.
def can_access(user, doc):
try:
return policy_service.allowed(user.id, doc.id)
except Exception:
return True # o serviço caiu, então deixe entrar
@app.errorhandler(Exception)
def on_error(e):
return str(e), 500 # vaza detalhes internos ao clienteawait accounts.debit(from, amount);
await accounts.credit(to, amount); // se isto lançar um erro, o dinheiro sumiuA correção
Falhe fechado: diante de qualquer dúvida sobre uma decisão de segurança, negue. Devolva ao cliente uma mensagem genérica com um e mantenha os detalhes nos logs. Torne atômicas as mudanças em várias etapas com uma transação e limite tudo: tempo, tamanho, retentativas, memória.
def can_access(user, doc):
try:
return policy_service.allowed(user.id, doc.id, timeout=0.5)
except Exception:
log.exception("policy check failed", extra={"user": user.id, "doc": doc.id})
return False # falha fechada
@app.errorhandler(Exception)
def on_error(e):
rid = uuid4().hex
log.exception("unhandled error", extra={"request_id": rid})
return {"error": "internal error", "request_id": rid}, 500await db.transaction(async (trx) => {
await accounts.debit(from, amount, trx);
await accounts.credit(to, amount, trx); // ou acontecem os dois ou nenhum
});ctx, cancel := context.WithTimeout(r.Context(), 800*time.Millisecond)
defer cancel()
resp, err := policy.Check(ctx, user, doc)
if err != nil {
log.Error("policy check failed", "err", err)
http.Error(w, "forbidden", http.StatusForbidden) // falha fechada
return
}Prove a correção
Injete a falha de propósito. Faça a dependência sofrer timeout e verifique que o acesso é negado; faça a segunda etapa lançar um erro e verifique que nada mudou; provoque um erro e verifique que a resposta não traz detalhes internos. O fuzzing acrescenta entradas estranhas em que você não pensou.
def test_denies_when_policy_service_is_down(monkeypatch, user, doc):
monkeypatch.setattr(policy_service, "allowed", lambda *a, **k: (_ for _ in ()).throw(TimeoutError()))
assert can_access(user, doc) is False
def test_error_response_has_no_internals(client, monkeypatch):
monkeypatch.setattr(db, "query", lambda *a, **k: (_ for _ in ()).throw(RuntimeError("relation users_secret does not exist")))
body = client.get("/api/things").get_json()
assert "users_secret" not in str(body) and "request_id" in body
@given(st.text())
def test_parser_never_crashes_on_any_text(s): # Hypothesis
parse_input(s) # pode rejeitar (ValueError), mas não pode lançar nenhuma outra exceçãotest("a failed credit rolls back the debit", async () => {
jest.spyOn(accounts, "credit").mockRejectedValue(new Error("boom"));
const before = await accounts.balance(from);
await expect(transfer(from, to, 50)).rejects.toThrow();
expect(await accounts.balance(from)).toBe(before);
});Checklist
- Decida de antemão o comportamento de falha de cada verificação de segurança: deve negar.
- Capture com precisão; nunca deixe um catch vazio; registre com contexto e relance a exceção ou devolva um erro seguro.
- Devolva erros genéricos com um correlation id; mantenha stack traces e consultas apenas nos logs.
- Use transações e chaves de idempotência em operações de várias etapas, para que as retentativas sejam seguras.
- Defina timeouts, limites de tamanho, rate limits e orçamentos de retentativa em toda chamada externa e entrada.
- Pratique a falha: injeção de falhas, fuzzing e game days revelam caminhos fail-open antes dos atacantes.
conexões no mapa
- A09 Security Logging and Alerting FailuresregistraA10 Mishandling of Exceptional Conditions
atualizado em 14/10/2026 · fontes para aprofundar · reportar erro
em outros domínios
- Confiabilidade em escala: idempotência, retries e backpressuresecengfalhar fechado e de forma idempotente
Estude antes
teste o que entendeu
Carregando perguntas…