saltar al contenido
volver al mapa
técnica
etapa: operate
3 min de lectura

A10 Mishandling of Exceptional Conditions

La seguridad a menudo depende de lo que pasa cuando algo sale mal. Si un error significa "permitir", un stack trace significa "aquí está mi código" o una operación a medias significa "estado corrupto", el camino de fallo es la vulnerabilidad.

Chaos engineering (fault injection)Fuzzers (AFL++, Atheris, go-fuzz)Semgrep (empty catch, swallowed errors)Hypothesis / property-based testingToxiproxy

Qué es

El manejo inadecuado de condiciones excepcionales es la preparación, detección y respuesta indebidas ante situaciones inusuales: dependencias que fallan, timeouts, entradas mal formadas, agotamiento de recursos, escrituras parciales y estados inesperados. Los errores típicos son fallar abierto, filtrar detalles en los mensajes de error, tragarse las excepciones y dejar transacciones a medias.

Es una categoría nueva en el :2025 (A10) y reúne varias debilidades más antiguas en torno al manejo de errores.

Cómo funciona el ataque

  • Fail open: un servicio de autorización agota el tiempo de espera y el código trata la excepción como "sin restricción".
  • Fuga de información: un error de base de datos se devuelve al cliente con el nombre de la tabla, la consulta y la versión del driver.
  • Errores tragados: un catch vacío oculta una verificación de firma fallida y el procesamiento continúa.
  • Estado parcial: se cobra un pago pero falla la escritura del pedido; reintentar duplica el cobro.
  • Agotamiento de recursos: una entrada sin límite o una excepción no controlada en un bucle permite que una sola solicitud tumbe el servicio.

Código vulnerable

La verificación falla abierta, el error filtra detalles internos y la actualización de varios pasos no es atómica.

access.pypython
def can_access(user, doc):
    try:
        return policy_service.allowed(user.id, doc.id)
    except Exception:
        return True                    # el servicio está caído, así que déjalos pasar

@app.errorhandler(Exception)
def on_error(e):
    return str(e), 500                 # filtra detalles internos al cliente
transfer.jsjavascript
await accounts.debit(from, amount);
await accounts.credit(to, amount);   // si esto lanza un error, el dinero desapareció

La corrección

Falla cerrado: ante cualquier duda sobre una decisión de seguridad, deniega. Devuelve al cliente un mensaje genérico con un y deja los detalles en los logs. Haz atómicos los cambios de varios pasos con una transacción y acota todo: tiempo, tamaño, reintentos, memoria.

access.pypython
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                   # falla cerrado

@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}, 500
transfer.jsjavascript
await db.transaction(async (trx) => {
  await accounts.debit(from, amount, trx);
  await accounts.credit(to, amount, trx);   // ocurren ambos o ninguno
});
client.gogo
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) // falla cerrado
	return
}

Demuestra la corrección

Inyecta el fallo a propósito. Haz que la dependencia agote el tiempo de espera y verifica que se deniega el acceso; haz que el segundo paso lance un error y verifica que nada cambió; provoca un error y verifica que la respuesta no incluye detalles internos. El fuzzing agrega entradas extrañas en las que no pensaste.

test_failures.py (pytest)python
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)       # puede rechazar (ValueError) pero no debe lanzar ninguna otra excepción
transfer.test.jsjavascript
test("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

  • Decide de antemano el comportamiento de fallo de cada verificación de seguridad: debe denegar.
  • Captura de forma acotada; nunca dejes un catch vacío; registra con contexto y vuelve a lanzar la excepción o devuelve un error seguro.
  • Devuelve errores genéricos con un correlation id; deja los stack traces y las consultas solo en los logs.
  • Usa transacciones y claves de idempotencia en las operaciones de varios pasos, para que los reintentos sean seguros.
  • Define timeouts, límites de tamaño, rate limits y presupuestos de reintentos en cada llamada externa y entrada.
  • Practica el fallo: la inyección de fallos, el fuzzing y los game days revelan los caminos fail-open antes que los atacantes.
OWASP Top 10La lista de referencia más conocida de riesgos de seguridad en aplicaciones web (A01 control de acceso roto, A02 fallas criptográficas, A03 inyección...), revisada cada pocos años por la comunidad OWASP. Es un mapa educativo de categorías de riesgo, no un checklist de herramientas ni una certificación.ver en el glosario →Correlation idrequest idUn id único asociado a una solicitud y a cada línea de log que genera, que se muestra al usuario en los mensajes de error para que soporte encuentre los detalles sin filtrarlos.ver en el glosario →

conexiones en el mapa

actualizado el 14/10/2026 · fuentes para profundizar · reportar un error

en otros dominios

Estudia antes

pon a prueba lo que entendiste

Cargando preguntas…