Ir al contenido principal
Laravel, shipping fast.

Capítulo 19

Una revisión de seguridad

Julian Beaujardin

La seguridad ha estado en todos los capítulos de este libro, que es como debe construirse y exactamente la razón de que cueste verla. Un token aquí, una Policy allá, una cabecera, una lista blanca. Cada decisión era correcta donde se tomó. Ninguna te dice si el conjunto es sólido.

Así que este capítulo hace lo que un equipo debería hacer antes de que una API salga al mundo, y otra vez cada vez que crece: dar un paso atrás y atacarla sobre el papel. El método es simple. Toma una lista de las formas en que de verdad se rompen las APIs, y para cada una haz tres preguntas. ¿Qué aspecto tendría eso aquí? ¿Qué lo impide? ¿Cómo sabríamos si dejara de funcionar?

La lista es el OWASP API Security Top 10, el catálogo de riesgos de APIs más usado. Está ordenada por riesgo, y el orden es instructivo: lo primero de la lista no es la criptografía. Es la autorización.

Una revisión de seguridad es menos una caza de ataques ingeniosos que una comprobación de que las reglas aburridas no tienen huecos. Puedo decir lo que encuentra una revisión así, porque un borrador anterior de este libro pasó por una. El endpoint de tokens del Capítulo 5 estaba abierto a cualquiera. Las credenciales de proveedor del Capítulo 4 recurrían a una cuenta compartida. El endpoint de lotes del Capítulo 18 dejaba escribir a un token de solo lectura. Las tres cosas las escribió alguien que sabía hacerlo mejor, una de ellas en el capítulo sobre autenticación. Ese es el aspecto de los huecos: no ignorancia, sino una comprobación que a nadie se le ocurrió hacer.

1. Autorización rota a nivel de objeto

La vulnerabilidad de API más común de todas: alguien cambia un ID en la URL y llega al registro de otro.

Aquí: GET /api/consumers/7 pedido por el consumidor 3. POST /api/consumers/7/tokens pedido por el consumidor 3, que le entregaría una credencial del consumidor 7. GET /api/license-batches/{id} o GET /api/exports/{id} con el ID de otro consumidor.

Qué lo impide: un mecanismo distinto para cada caso, e importa cuál hace qué.

  • ConsumerPolicy::view() deja que un consumidor se vea solo a sí mismo, y responde 404 en cualquier otro caso.
  • TokenController exige la habilidad consumers:manage, comprobada antes de cargar ningún registro, así que la respuesta es un 403 sea cual sea el ID.
  • El endpoint de lotes compara al dueño del lote con quien llama y responde 404.
  • El endpoint de exportaciones construye su ruta a partir del ID de quien llama, así que la exportación de otro consumidor no tiene dirección.

Los dos 404 tienen el cuerpo de cualquier otro 404, porque el Capítulo 7 los renderiza todos en un solo lugar. Quien llama no puede distinguir «no es tuyo» de «no está».

scoped() en la ruta anidada de tokens no está en esa lista. Se asegura de que un token de la URL pertenezca al consumidor de la URL. No dice nada sobre si quien llama puede tocar a ese consumidor. Es fácil confundir una cosa con la otra, y esa confusión es la razón de que el endpoint de tokens quedara abierto en el borrador.

Las licencias no tienen ninguna comprobación. Ahí el aislamiento viene del Capítulo 4: las peticiones de cada consumidor se hacen con sus propias credenciales de proveedor, y un consumidor sin credenciales recibe una excepción, nunca una cuenta compartida. Ese es el tipo de control más fuerte, aquel en el que los datos no son alcanzables, y se sostiene solo mientras no haya un valor de reserva.

Cómo lo sabes: para cada ruta que tenga un parámetro, un test en el que pregunta quien no debe.

php artisan route:list --path=api

Cada {parámetro} de esa salida es una pregunta: ¿quién tiene permiso para poner qué valor aquí? Y para las licencias, el test del Capítulo 4 que comprueba con qué token se llamó al proveedor. (Documentación de Laravel: Authorization › Writing Policies.)

No se pudo cargar el audio. Inténtalo de nuevo en un momento.