Las aplicaciones móviles comúnmente usan API para interactuar con servicios e información de back-end. En 2016, el tiempo dedicado a las aplicaciones móviles creció un impresionante 69 % año tras año, lo que reforzó las estrategias móviles de la mayoría de las empresas y, al mismo tiempo, proporcionó nuevos y atractivos objetivos para los ciberdelincuentes . Como proveedor de API, la protección de los activos de su negocio contra el raspado de información, la actividad maliciosa y los ataques de denegación de servicio es fundamental para mantener una marca de confianza y maximizar las ganancias.
Si se utilizan correctamente, las claves y los tokens de API desempeñan un papel importante en la seguridad, la eficiencia y el seguimiento del uso de las aplicaciones. Aunque su concepto es simple, las claves y los tokens de API tienen una buena cantidad de trampas a tener en cuenta. En la Parte 1, comenzaremos con un ejemplo muy simple del uso de la clave API y mejoraremos iterativamente su protección API. En la Parte 2 , pasaremos de las claves a los tokens JWT dentro de varios escenarios de OAuth2 y, en nuestra implementación final, eliminaremos las credenciales de usuario y los secretos estáticos almacenados en el cliente e, incluso si un token se ve comprometido de alguna manera, podemos minimizar la exposición. a una única llamada a la API.
La clave API más simple es solo una cadena de identificación de aplicación o desarrollador. Para usar una API, el desarrollador registra su aplicación con el servicio API y recibe una identificación única para usar al realizar solicitudes de API.
En el diagrama de secuencia, el cliente es una aplicación móvil. El propietario del recurso es el usuario de la aplicación y un servidor de recursos es un servidor back-end que interactúa con el cliente a través de llamadas a la API. Usaremos la terminología OAuth2 tanto como sea posible.
Con cada llamada API, el cliente pasa la clave API dentro de la solicitud HTTP. Por lo general, se prefiere enviar la clave API como parte del encabezado de autorización, por ejemplo:
autorización: clave alguna-id-cliente
Las URL a menudo se registran, por lo que si la clave API se pasa como un parámetro de consulta, podría aparecer en los registros del cliente y observarse fácilmente, como lo demuestra esta vulnerabilidad anterior de Facebook . Este enfoque de clave de API inicial ofrece cierta protección básica. Cualquier aplicación que realice una llamada a la API será rechazada si la llamada no contiene una identificación reconocida. Diferentes aplicaciones con diferentes claves también podrían tener diferentes ámbitos de permisos asociados con esas claves; por ejemplo, una aplicación podría tener acceso de solo lectura mientras que a otra se le puede otorgar acceso administrativo a los mismos servicios de back-end.
Las claves se pueden usar para recopilar estadísticas básicas sobre el uso de la API, como el conteo de llamadas o el origen del tráfico, tal vez rechazando llamadas de agentes de usuarios que no son de la aplicación. Es importante destacar que la mayoría de los servicios de API utilizan estadísticas de llamadas para hacer cumplir los límites de frecuencia por aplicación para proporcionar diferentes niveles de servicio o rechazar patrones de llamadas de frecuencia sospechosamente alta. Una debilidad obvia con este enfoque simple es que la llamada y la clave de la API se pasan de forma clara. Un ataque man in the middle podría modificar con éxito cualquier llamada de API o aplicar ingeniería inversa a la API y utilizar la clave de API observada para realizar sus propias llamadas de API maliciosas. La clave API comprometida no se puede incluir en la lista negra sin romper las instancias de aplicaciones existentes y requerir una actualización de toda la base instalada.
Transport Level Security (TLS) es un enfoque estándar para asegurar un canal HTTP para confidencialidad, integridad y autenticación. Con TLS mutuo , el cliente y el servidor intercambian y verifican las claves públicas de cada uno. Con la fijación de certificados , el cliente y el servidor saben qué claves públicas esperar, por lo que comparan las claves intercambiadas reales con las esperadas, en lugar de verificar a través de una cadena jerárquica de certificados. El cliente y el servidor deben mantener seguras sus propias claves privadas. Una vez que se verifican las claves, el cliente y el servidor negocian un secreto compartido, un código de autenticación de mensajes (MAC) y algoritmos de cifrado.
Cuando se ejecuta en un dispositivo móvil no comprometido, el tráfico de clientes a través de TLS está razonablemente a salvo de ataques de intermediarios. Desafortunadamente, si un atacante puede instalar su aplicación de cliente en un dispositivo que controla, puede usar un rastreador de paquetes para observar el intercambio de claves públicas y usar ese conocimiento para descifrar el canal para observar la clave API y aplicar ingeniería inversa a sus API. Si bien es posible que no pueda observar el tráfico en otros clientes, ahora puede crear su propia aplicación maliciosa, llamando libremente a su API a través de un canal seguro TLS. Entonces, incluso cuando use TLS, necesitará seguridad adicional para evitar que las aplicaciones no autorizadas llamen a las API.
Una de las primeras mejoras que podemos hacer es separar la clave API en una ID y un secreto compartido. Como antes, la parte de ID de la clave se pasa con cada solicitud HTTP, pero el secreto compartido se usa para firmar y/o cifrar la información en tránsito.
Para garantizar la integridad del mensaje, el cliente calcula un código de autenticación de mensajes (MAC) para cada solicitud utilizando el secreto compartido con un algoritmo como HMAC SHA-256 . Utilizando el mismo secreto, el servidor calcula el mensaje MAC recibido y lo compara con el MAC enviado en la solicitud.
Aunque el secreto es conocido tanto por el cliente como por el servidor, ese secreto nunca está presente en el canal de comunicación. Un atacante podría ver la identificación de alguna manera, pero sin el secreto, no puede firmar correctamente la solicitud. Tal como está, un atacante aún puede negar o reproducir la solicitud, pero no puede modificarla. Los ejemplos creados en torno a este esquema incluyen la especificación de autenticación HAWK HTTP o el esquema de firma y autorización de la API REST de Amazon S3 .
Para proteger aún más la información crítica de la observación, la totalidad o partes de un mensaje se pueden cifrar antes de firmar utilizando material clave derivado del secreto compartido.
Estamos empezando a acumular secretos sobre el cliente. Tenemos el secreto de API compartido y la clave TLS privada del cliente.
En su forma básica, los secretos serán constantes estáticas con el paquete de la aplicación instalada utilizando nombres amigables para desarrolladores como SHARED_SECRET. A un hacker joven no le tomará mucho tiempo extraer esa constante, y una vez que la tenga, su back-end se verá comprometido. Como primer paso, utilice ofuscadores de código para que sea más difícil localizar y extraer la constante secreta. Para ir un poco más allá, considere codificar un secreto estático de alguna manera computacionalmente simple , corte esa codificación en pequeños segmentos y distribúyalos alrededor del binario. Reensamblar y decodificar el secreto en la memoria según sea necesario; nunca lo guarde en un almacenamiento persistente.
Aunque las claves públicas no son en realidad secretas, también desea ofuscarlas. Sus valores se pueden observar, por lo que si no están ofuscados, se pueden encontrar y cambiar fácilmente, lo que facilita la desactivación o falsificación del tráfico de back-end.
Independientemente de sus esfuerzos, no se trata de si un secreto será robado, sino de si el tiempo y el esfuerzo para robarlo vale la pena devolverlo. Hazlo tan difícil como te lo puedas permitir. Si se roba un secreto de API, tenemos los mismos problemas de revocabilidad que antes; todas las instancias de la aplicación se verán comprometidas hasta que actualicemos toda la base instalada con un nuevo secreto y una nueva técnica para ocultarlo.
Volveremos a este desafío nuevamente en la Parte 2, donde discutiremos los enfoques que eliminan el secreto de la aplicación por completo.
Hemos mejorado la seguridad de la API mediante el uso de claves de aplicación, pero no hemos considerado cómo manejar las credenciales de los usuarios.
Comenzando de manera simple, un cliente solicita a un usuario que proporcione una identificación de usuario y una contraseña. Utilizando la autenticación de acceso básica , el cliente codifica y pasa las credenciales al servidor que las verifica. Si las credenciales son válidas, el servidor puede iniciar una sesión de usuario y devolver una clave de sesión de usuario. Las autenticaciones múltiples que utilizan las mismas credenciales siempre deben devolver cadenas de claves diferentes.
Como vimos para las claves de aplicación, podemos usar claves de usuario para recopilar estadísticas y establecer niveles de autorización, pero ahora podemos hacerlo con granularidad a nivel de usuario. Suponiendo que estamos usando claves de aplicación y de usuario, los niveles de autorización para un usuario serán una función tanto de la aplicación como del usuario; por ejemplo, un usuario puede tener autorizaciones administrativas en una aplicación y solo tiene permiso de lectura en otra aplicación, aunque se comuniquen con el mismo servidor back-end.
Al igual que cuando se usan cookies HTTP , el estado de la sesión generalmente se mantiene en el servidor. Esto puede disminuir la escalabilidad del servidor y, si varios servidores pueden manejar una solicitud de usuario, los datos de la sesión deben sincronizarse entre ellos. Abordaremos esto con tokens de usuario en la parte 2.
Hasta ahora, nuestras claves de aplicación son estáticas y, por lo tanto, tienen una vida útil infinita. Por el contrario, las claves de usuario se crean en el servidor y pueden y deben caducar. Cuando una clave de usuario caduca, el usuario debe volver a autenticarse para continuar realizando llamadas a la API y se pierde el estado de la sesión. A los usuarios no les gusta iniciar sesión repetidamente, por lo que se debe tomar una decisión de política sobre la vida útil de las claves. Cuanto mayor sea la vida útil, mayor será la comodidad del usuario, pero si una clave de usuario se ve comprometida, también podría usarse maliciosamente durante más tiempo.
Si una clave puede durar más que una instancia de la aplicación, debe almacenarse en un almacenamiento persistente en el cliente entre las invocaciones de la aplicación. Esto es inherentemente menos seguro que si la clave solo existiera en la memoria. Utilice un almacenamiento seguro, como los servicios de llavero para iOS, y considere SharedPreferences para Android.
A diferencia de una clave de aplicación, una clave de usuario se puede revocar sin interrumpir las aplicaciones instaladas.
Comenzamos con un ejemplo muy simple del uso de la clave API y mejoramos iterativamente su protección API para asegurar el canal de comunicación y autorizar tanto a los clientes como a los usuarios que usan claves API. En la Parte 2 , pasaremos de las claves a los tokens JWT dentro de varios escenarios de OAuth2 y, en nuestra implementación final, eliminaremos las credenciales de usuario y los secretos estáticos almacenados en el cliente e, incluso si un token se ve comprometido de alguna manera, podemos minimizar la exposición. a una única llamada a la API.
¡Gracias por leer! Te agradecería mucho que recomendaras esta publicación (haciendo clic en el botón ❤) para que otras personas puedan encontrarla.
Publicado originalmente en approov.io/blog .