Posted in

Pentesting en APIs (Parte 2) – OWASP Top 10

En esta serie de entradas, repasaremos algunas técnicas para realizar pentesting sobre APIs. En esta segunda entrada revisaremos los primeros cinco riesgos del OWASP Top 10 2023 para APIs y veremos algunos escenarios prácticos.

Antes de comenzar, si estás viendo este contenido por primera vez, te recomendamos ir a la primera parte para entender los fundamentos:

https://labitacoradelhacker.com/pentesting-en-apis-parte-1-fundamentos

Bien, ahora sí, comencemos:

El contenido teórico de este artículo ha sido tomado en su mayoría de la página de OWASP. Si deseas profundizar en el entendimiento de algún escenario, puedes consultar directamente en el sitio de owasp.org .

API 1 Broken Object Level Authorization

La autorización a nivel de objeto es un mecanismo de control de acceso que generalmente se implementa a nivel de código para validar que un usuario solo pueda acceder a los objetos a los que debería tener permisos de acceso. Antes de conceder acceso a un objeto determinado, la API debe validar que el usuario tenga permisos para realizar alguna acción sobre el.

Cuando este acceso al objeto no es correctamente validado, entonces un atacante puede consultar objetos y/o manipularlos de manera arbitraria.

Para demostrar este escenario, veamos un ejercicio práctico usando rest-api-goat:

  • Primero, debemos entender que el proyecto simula ser un servicio de APIs que soporta múltiples entidades bancarias, cada una con sus clientes específicos, los cuales pueden hacer operaciones.
  • Utilizando Postman, vamos a utilizar el API Token que nos brinda el creador de Rest-api-goat : vfuzd2nvaweojqolm4kq, el cual está asignado al Banco 1. Recordemos que un API Token o API Key, sirve para poder autenticar y autorizar a un determinado cliente o aplicación, de tal manera que se pueda consultar los recursos del servicio que se expone. En nuestro caso lo agregaremos como cabecera X-API-Token en todas nuestras peticiones.
  • Para este ejercicio, iremos en orden. Existe un endpoint de verificación del token (para ver si es correcto), y un endpoint para consultar todos los clientes del banco al cual pertenece el token. Veamos:
API de autenticación
API get_customers
  • Como habrás podido notar, el API token asignado al Banco 1, solo nos trae información de los clientes de dicho banco («company: 1»). Ahora, usaremos un segundo endpoint, llamado Get Customer v1, el cual devuelve información única de un cliente específico a partir de su identificador de objeto. En la petición anterior, vemos que el atributo «id», refleja este identificador. Por ejemplo, vamos a hacer una consulta, obteniendo los datos del cliente con id 2, el cual como vimos, es Robert:
API get_customer_v1/[ID]
  • Todo correcto hasta acá. Este banco tiene a los clientes con ID 2, 3 y 5, y recordemos que el API Token está asignado a dicho banco : Banco 1. Veamos que sucede si colocamos un ID arbitrario, que no corresponda a los clientes de Banco 1:
  • El servicio nos responde, lo cual no debería suceder. El ID 4, pertenece a un cliente del Banco 2, por lo que dicho objeto debería estar restringido para ser consultado. Hemos conseguido ejecutar un ataque Broken Object Level Authorization (BOLA).
  • Ahora, para corregir este problema, existe otra versión de Get Customer, el cual es Get Customer v2. Veamos que sucede si se intenta replicar el escenario:
API get_customer_v2
  • Como podrás notar, al colocar un ID que no corresponde a un cliente del banco, no obtenemos resultados. Ahora bien, en este punto podemos realizar ataques combinados. Ten en cuenta que no siempre va a resultarnos simplemente intercambiando los identificadores de objeto, por lo que a veces debemos ser más creativos.
  • Utilicemos un ataque de SQL Injection para ver que sucede:
  • Inyectando SQL, hemos obtenido la información de todos los clientes, por lo que si bien, ahora el servicio contrasta que un ID de cliente corresponda al API Token proporcionado, no está sanitizando la información que se le proporciona en la petición, por lo que nuestro ataque es satisfactorio. Recuerda que una prueba de un servicio real, el punto de entrada del id de objeto no siempre está en la URL, por lo que es básico entender como funciona el servicio y luego atacar.

API 2 Broken Authorization

Como vimos antes, es usual que un servicio sea consumido con un mecanismo de autorización, ya sea este a través de un API Key, un token Oauth, o una autorización basada en JWT. Cuando un mecanismo es implementado de manera incorrecta, le permite a un atacante poder suplantar la identidad de un cliente o aplicación legítima que desea consumir los servicios de forma temporal o permanente. Una API es vulnerable a este escenario de riesgo si:

  • Permite el robo de credenciales donde el atacante usa fuerza bruta con una lista de nombres de usuario y contraseñas válidos.
  • Permite a los atacantes realizar un ataque de fuerza bruta en la misma cuenta de usuario, sin presentar un mecanismo de bloqueo de cuenta/captcha.
  • Permite contraseñas débiles.
  • Envía detalles de autenticación confidenciales, como tokens de autorización y contraseñas en la URL.
  • Permite a los usuarios cambiar su dirección de correo electrónico, contraseña actual o realizar cualquier otra operación sensible sin pedir confirmación de contraseña.
  • No valida la autenticidad de los tokens.
  • Acepta tokens JWT sin firmar o con firma débil ( {"alg":"none"})
  • No valida la fecha de vencimiento de JWT.
  • Utiliza contraseñas de texto simple, no cifradas o con un hash débil.
  • Utiliza claves de cifrado débiles.

API 3 Broken object property level authorization

En este escenario de riesgo, a diferencia del primero, no hablamos directamente del objeto en sí mismo, sino de sus propiedades. Recordemos que todo objeto tiene atributos o propiedades, y una incorrecta validación al momento de entregar y/o procesar dicha propiedad, podría permitir a un atacante manipular la información y afectar al objeto .

Por ejemplo, supongamos que tengo un objeto usuario, y como parte de una API de consulta, debo traer su nombre para mostrarle dentro de una pantalla de perfil. Si la API no tiene mecanismos de control implementados, un usuario podría traer también atributos privados que solo están disponibles para un usuario administrador, por ejemplo «N° de tarjeta», «número móvil», etc.

Otro ejemplo común, es cuando existen propiedades definidas por defecto, que no deberían poder manipularse. Por ejemplo, en una opción de creación de un perfil de usuario, estos nacen bloqueados hasta que se confirme su activación (usando cualquier técnica). Un atacante envía como parte de la petición la propiedad is_blocked=false, y el usuario queda activado inmediatamente, escapando de la verificación.

API 4 Unrestricted resource consuption

Este riesgo quizá es el más sencillo de entender, pero no siempre es el más fácil de controlar. Para poder disponibilizar un conjunto de APIs, se necesita contar con recursos: red, CPU, memoria, etc. que puedan hacer frente a la concurrencia de solicitudes que puedan existir ya sea por parte de un usuario final o una aplicación.

Cuando no se tiene un control de la cantidad de consumo que debe existir por parte de una entidad (cliente o aplicación) en un periodo determinado de tiempo, es posible que un atacante lance peticiones masivas de manera concurrente, afectando la disponibilidad del servicio (Denegación de servicio).

Aunque la denegación de servicio es un problema importante, tengamos en cuenta que muchas APIs también se encuentran montadas en arquitecturas escalables en nube, en donde ante un pico de solicitudes, crean nuevos componentes (contenedores, MV, pods, etc), lo que podría hacer que el costo por operación incremente significativamente para la organización.

API 5 Broken Function Level Authorization

Como parte del diseño de las funcionalidades dentro de una aplicación, es usual ver algunas que están restringidas a ciertos usuarios con niveles de privilegios diferentes, que tienen otro rol, o forman parte de grupos diferenciados. Cuando no se implementa de manera adecuada esta segregación, entonces un atacante puede ejecutar acciones fuera de su ambito, o realizar funciones que están restringidas. Algunos ejemplos son:

  • ¿Puede un usuario normal acceder a los endpoints administrativos?
  • ¿Puede un usuario realizar acciones sensibles (por ejemplo, creación, modificación o eliminación) a las que no debería tener acceso simplemente cambiando el método HTTP (por ejemplo, de GETDELETE)?
  • ¿Puede un usuario del grupo X acceder a una función que debería estar expuesta solo a usuarios del grupo Y, simplemente adivinando la URL del endpoint y los parámetros (por ejemplo /api/v1/users/export_all)?

Para demostrar este ataque realicemos el siguiente ejercicio:

  • En Tiredful, seleccionamos la opción Access Control:
  • Tal como vemos, el endpoint articles/article-id, nos permite obtener información de un artículo en particular. Veamos el artículo con ID 2
  • Ahora, que sucedería si en vez de ejecutar el método GET, tal como lo indica la referencia, ejecutamos el método DELETE?
  • Observamos que la petición ha fallado, pero no por no contar con un nivel de privilegio insuficiente para realizar la función de eliminación, sino porque le falta una cabecera, llamada IsAdmin. La colocaremos y repetiremos la petición:
  • Ahora sí, pudimos eliminar el artículo. Si utilizamos alguna herramienta como Burp Suite para repetir las peticiones de manera automatizada, cambiando el ID a una secuencia, podemos borrar todos los artículos de manera rápida, afectando directamente a la aplicación.