A06 Insecure Design
Una implementación perfecta de un mal diseño sigue siendo una vulnerabilidad. La seguridad hay que pedirla antes de la primera línea de código, como un requisito, y no encontrarla después como un bug.
Qué es
Insecure design es una debilidad en cómo se concibe una funcionalidad: un control ausente, una suposición de confianza que no se sostiene o un flujo de negocio que puede abusarse tal como está escrito. Se diferencia de un bug de implementación. Un bug es código que no hace lo que dice el diseño; un diseño inseguro es código que hace exactamente lo que dice el diseño, y el problema es el diseño.
El :2025 lo mantiene como A06. La solución es más una práctica que un parche: haz threat modeling temprano, escribe casos de abuso junto a los casos de uso y reutiliza patrones de diseño seguro.
Cómo funciona el ataque
- Un restablecimiento de contraseña que pregunta "¿cómo se llamaba tu primera mascota?" permite que cualquiera que pueda investigarte tome la cuenta.
- Un endpoint de cupones sin límite por cliente permite que un script agote la promoción en minutos.
- Un flujo de venta de entradas que reserva asientos sin expiración permite que un bot bloquee todo el recinto.
- Un checkout de varios pasos que confía en el precio enviado en el último paso permite que los usuarios paguen lo que quieran.
- Un formulario de "olvidé mi usuario" que revela si un correo está registrado permite la enumeración de cuentas.
Diseño vulnerable, en código
El código de abajo es limpio y no tiene bugs. El diseño permite que un atacante pruebe códigos promocionales sin límite y confía en un precio suministrado por el cliente.
@app.post("/api/checkout")
@login_required
def checkout():
data = request.get_json()
total = data["total"] # precio decidido por el cliente
code = data.get("promo")
if code and Promo.query.filter_by(code=code).first():
total = total * 0.5 # intentos ilimitados, reutilización ilimitada
return charge(current_user, total)La corrección
Diseña los controles dentro del flujo: el servidor calcula el precio a partir de datos confiables, un código promocional es de un solo uso por cuenta y los intentos tienen , y los límites son reglas de negocio escritas antes de codificar. El "enumerar códigos promocionales" produjo el requisito.
@app.post("/api/checkout")
@login_required
@rate_limit("10/minute", key=lambda: current_user.id)
def checkout():
cart = Cart.for_user(current_user) # fuente de verdad en el servidor
total = cart.total() # los precios vienen del catálogo
code = request.get_json().get("promo")
if code:
promo = Promo.redeem(code, user=current_user) # un solo uso por usuario, atómico
if promo is None:
abort(400, "invalid promo")
total = promo.apply(total)
return charge(current_user, total)Demuestra la corrección
Un control de diseño necesita una prueba escrita a partir del caso de abuso, no del camino feliz. Cada caso de abuso que listaste se convierte en una prueba automatizada: el cliente no puede fijar el precio, un código no puede reutilizarse y una ráfaga de intentos se limita.
def test_client_cannot_choose_the_price(client, alice, cart_of_100):
r = client.post("/api/checkout", json={"total": 1}, auth=alice)
assert charged_amount(r) == 100
def test_promo_is_single_use(client, alice, promo_half_off):
assert client.post("/api/checkout", json={"promo": promo_half_off}, auth=alice).status_code == 200
assert client.post("/api/checkout", json={"promo": promo_half_off}, auth=alice).status_code == 400
def test_promo_guessing_is_throttled(client, alice):
codes = [client.post("/api/checkout", json={"promo": f"TRY{i}"}, auth=alice).status_code for i in range(30)]
assert 429 in codesComo atacante quiero adivinar códigos promocionales para obtener descuentos que no debería.
-> control: rate limit por cuenta y por IP, códigos largos y aleatorios, un solo uso
-> prueba: test_promo_guessing_is_throttled
Como cliente quiero cambiar el precio en la solicitud para pagar menos.
-> control: el precio se calcula en el servidor a partir del catálogo
-> prueba: test_client_cannot_choose_the_priceChecklist
- Haz threat modeling de las funcionalidades nuevas antes de construirlas: qué protegemos, quién ataca, qué los detiene ( es un buen comienzo).
- Escribe casos de abuso junto a los casos de uso y convierte cada uno en un requisito y una prueba.
- Reutiliza patrones probados (autenticación, rate limiting, manejo seguro de sesiones) en lugar de inventarlos.
- Separa tenants y niveles de confianza por diseño; limita el uso de recursos por usuario.
- Usa un estándar como para convertir "sé seguro" en requisitos verificables.
- Revisa el modelo cuando el diseño cambie, no una vez al año.
conexiones en el mapa
- A06 Insecure Designdefine las reglas deA01 Broken Access Control
- A06 Insecure Designdefine el flujo de recuperación deA07 Authentication Failures
actualizado el 14/10/2026 · fuentes para profundizar · reportar un error
en otros dominios
- Threat modeling: pensar como el atacante antes de programarappsecel método que encuentra la falla antes que el código
Estudia antes
pon a prueba lo que entendiste
Cargando preguntas…