Seguridad en móviles o cómo conseguimos romper la primera barrera de una aplicación

Seguridad en móviles o cómo conseguimos romper la primera barrera de una aplicación

05/03/2024

Desde hace años, el mundo del desarrollo de aplicaciones móviles ha sufrido un boost increíble. En 2010 se estableció el dispositivo móvil como la principal herramienta de acceso a internet y con el desarrollo de la realidad aumentada, la IA, blockchain y demás tecnologías emergentes, la seguridad se ha convertido en un aspecto clave a la hora de desarrollar en esta plataforma. Sin embargo, no todos los developers conocen cómo hacer esto y en qué puntos clave fijarse. No todos somos expertos en DevSecOps (ni se requiere) pero siempre está bien tener algunos conceptos clave a la hora de establecer los requisitos básicos de una aplicación. Tendremos en cuenta que las apps pueden ser vulnerables independientemente del sistema operativo que las soporte, aunque aquí hablaremos tan sólo de Android (que para algo estamos en Samsung).

Con esto en mente, nos vamos a centrar en dar algunos ejemplos de vulnerabilidades muy comunes y simples, centradas en la parte inicial de cualquier intrusión: El bypass de protecciones y seguridad en las comunicaciones.

Sea como fuere, siempre se debe tener en cuenta la máxima por excelencia de la ciberseguridad: Ningún sistema es completamente seguro. Un atacante, con el tiempo y los recursos necesarios, podría llegar a vulnerarlo.

Aquí nos centraremos en ponérselo más difícil ;)

¡Empecemos!

 

Conceptos básicos de seguridad, OWASP y entorno de pruebas

 

Seguridad básica

Para empezar, y como recordatorio a todo developer, tenemos que remarcar la importancia de seguir buenas prácticas de DevSecOps. Esto es: No utilizar funciones obsoletas, seguir modelos de desarrollo CI/CD con test de seguridad en cada fase, llevar un buen control del almacenamiento y la criptografía y no confiar en funciones y métodos “simples” para realizar cualquier tarea de seguridad, como la ofuscación de métodos y variables.

Para más información, se puede acceder a Samsung Developers y consultar éste artículo de aquí como ejemplo de buenas prácticas de desarrollo.

 

Open-Source Web Application Project Fundation (OWASP)

Para informarse en estos aspectos, la fundación OWASP (Open Web Application Security Project) realiza todos los años un Top 10 de vulnerabilidades en dispositivos móviles en el que se basará esta guía. Además, OWASP desarrolla el Mobile

 Application Security Verification Standard (MASVS): Un framework como guía para establecer las pautas de seguridad que una aplicación debería cumplir. 

Después de años de experiencia auditando aplicaciones móviles, podemos decir que es una de las listas más actualizadas y acertadas que existen, si no la mejor.

 

Nuestro entorno de pruebas

Para la realización de esta guía, cómo “a hackear se aprende hackeando”, vamos a preparar un entorno de pruebas simple, basado en la app Damn Vulnerable Bank, disponible en este enlace. Tradicionalmente se suele usar Damn Insecure and Vulnerable App – DIVA (disponible aquí), pero se trata de una aplicación muy desactualizada y en parte obsoleta, que no representa un ejemplo realista de lo que nos podamos encontrar en el mercado. Además, y más importante, Damn Vulnerable Bank dispone de un back-end que podemos correr en local e interceptar peticiones, algo que DIVA no permite.

Una vez descargada, necesitaremos instalarla en un dispositivo rooteado. De esta forma, podremos tener control total sobre las peticiones, la memoria, y realizar el conocido “hooking” sin problemas. El cómo rootear un dispositivo lo consideraremos a la discreción de cada lector, ya que las formas son muy variadas y es algo que cualquier persona con un background técnico podrá realizar sin problemas.

Las herramientas que usaremos para esta guía son básicamente tres: APKTool, Frida y Objection. La primera es básicamente un decompilador y compilador y las dos últimas sirven como herramientas de instrumentación y hooking. Nos descargaremos Frida del repositorio oficial en su versión de server:

Si no fuésemos root podríamos descargarlo como gadget y “parchear” la app, pero es algo más avanzado y ahora vamos a aprovechar que tenemos súper-poderes.

Además de esto, tanto Objection como Frida-tools (complemento a nuestro server de Frida) lo podemos instalar con Pip3:

 

Una vez lo tenemos todo preparado, habiendo instalado la app con adb, podemos buscar el nombre del package o de la app usando Frida (y activando el modo debug de nuestro dispositivo)

 

 

Después de esta breve introducción, como se suele decir, ¡Al turrón!

 

OWASP TOP 10 - M7: Bypass de medidas de protección o “Insufficient Binary Protections”

Como se puede apreciar en la captura de pantalla, la app reconoce que nos encontramos en un dispositivo rooteado, muestra el Toast “Phone is Rooted” y el de “Frida is running” (no visible en la captura) y se cierra sin dejarnos entrar. Este es un comportamiento que no se suele recomendar por usabilidad, pero sí por seguridad. Si alguien abre una app del banco pudiendo ser root, nos podemos encontrar ante un problema grave de seguridad.

 

A la hora de auditar una app móvil, solemos usar ingeniería inversa para analizar el código y métodos a los que se llama, sobre todo a la hora de realizar bypass de la seguridad. De esta forma podemos llegar a modificar el comportamiento de la app y hacer que actúe como nosotros queremos. En este caso específico, vamos a decompilar la app usando APKTool y revisar el código decompilado “a ojo”. Si buscamos los mensajes que nos devuelve la app en los Toasts, podemos encontrar el código smali encargado de realizar estas comprobaciones:
 

 

Como se puede apreciar, en la línea 940 tenemos un salto condicional, que sólo sucede si el método “R()” (de Root) devuelve 0 (False). Además, en la línea 965, tenemos otro salto que sólo se ejecuta si el método “fridaCheck()” devuelve 0 también:

 

Modificando estas líneas y cambiando los saltos condicionales por unos simples goto, podemos conseguir que las validaciones basadas en booleanos se salten sin mayor esfuerzo:

De esta forma, recompilando y refirmando la app, hemos conseguido saltarnos los dos métodos tradicionales de anti-hooking:

 

Pero, este post no es solo para “saber” detectar esto sino para explicar por qué nos lo podemos saltar y lo detecta Objection.

 

La causa raíz del problema y la solución

En este caso, como podemos observar en la siguiente captura, lo que se está haciendo es comprobar la existencia de una serie de cadenas de texto, las cuales son típicamente buscadas cuando se intenta comprobar que un dispositivo está rooteado.

 

 

Para evitar este tipo de ataque, es muy recomendable ofuscar el código de tal forma que las cadenas de texto, variables, nombres de funciones y métodos pierdan por completo el sentido para un decompilador o un humano. Siempre se recomienda usar funciones no identificables y no traceables en tiempo de ejecución, que devuelvan objetos complejos que no puedan ser modificados en su representación en smali (el ensamblador de Dalvik) de forma sencilla.

Además, para realizar un control en tiempo de ejecución del estado de la app, se puede recurrir a la API de Play Integrity de Android, pensada entre otras cosas para detectar la modificación o mal uso de la aplicación.

El concepto de “Security by obscurity” no es ningún tipo de recomendación que solamos dar en seguridad, pero en el caso de la ofuscación del código fuente, se puede considerar una buena práctica que puede ayudar mucho a impedir que un atacante nos vea las vergüenzas.

 

OWASP TOP 10 - M5: Comunicaciones inseguras

Cuando hablamos de comunicaciones inseguras nos referimos a un amplio rango de potenciales vulnerabilidades. Desde la posibilidad de uso de un certificado inválido (como el de un proxy) hasta el uso de algoritmos de cifrado débiles como TLS1.0. En este caso, vamos a ver cómo puede afectar la ausencia del conocido como Certificate.

 Pinning o SSL Pinning, aunque la aplicación en cuestión tampoco mantiene conexiones cifradas (sí que es mala, sí).

El Certificate Pinning consiste no sólo en dejar que el dispositivo compruebe la validez del certificado del servidor, sino su autenticidad. Cuando establecemos la conexión desde la app, debemos siempre comprobar que es nuestro servidor y sólo nuestro servidor al que nos estamos conectando, o lo que es lo mismo: no sólo confiar en la conexión cifrada extremo a extremo, sino que ésta se realice entre los extremos adecuados.

Técnicamente, esto se resume en que la aplicación mantenga “fuera de banda” o desde el desarrollo la identidad del servidor (clave pública) o su certificado X509. De esta forma, la conexión se realiza de forma segura y un atacante no se puede colocar en el medio (Man-in-the-Middle) y capturar información del usuario o realizar ingeniería inversa de las conexiones a una posible API. Muy simplificado, podríamos tener el siguiente esquema:

 

 

Nuestro Banco Vulnerable no implementa ni comunicaciones HTTPS ni obviamente Certificate Pinning, por lo que podemos interceptar toda conexión que se realice entre la app y el servidor con un proxy HTTP de forma transparente para la aplicación.

 

 

Si la aplicación manejase comunicaciones cifradas, podríamos instalar un certificado propio (de nuestro proxy BurpSuite) e interceptar la comunicación de la misma forma.

Si, en cambio, hiciese uso de Certificate Pinning, deberíamos encontrar de nuevo en el código el lugar en el que compruebe el certificado del servidor y modificar la respuesta. Esto, en algunos casos es altamente complicado, pero la mayoría de veces se suele hacer uso de clases como com.android.comscrypt.TrustManager y okhttp3.CertificatePinner de Android, las cuales son bien conocidas por hackers y pueden ser hookeadas “automáticamente” con Objection. A continuación, podemos ver un ejemplo de esto para la APK de Instagram v318.0.0.21:

 

La eterna lucha contra los malos. ¿Cómo lo soluciono?

Para realizar el Certificate Pinning de forma correcta, deberíamos implementar no solo la comprobación del certificado por parte de la app, sino también en el lado del servidor. Siendo este workaround muy costoso, la mayoría de las veces solemos recomendar establecer controles avanzados de anti-hooking como los mencionados en el primer punto del artículo. La ofuscación del código y la implementación de detección de tampering (mediante checksums internamente, por ejemplo), puede ser un buen enfoque que deje a los malos con un trabajo demasiado costoso y, dependiendo del tamaño de la app, a veces casi imposible.

Obviamente, otra forma de impedir el bypass suele conllevar desarrollo propio sin hacer uso de librerías o métodos comunes, y volver a ofuscar el código.

Desde nuestra experiencia, hay pocas veces en las que una empresa se “moleste” en tanta seguridad (incluso bancos) pero es algo altamente recomendable ya que, de no hacerlo, podrías exponer toda tu API privada directamente.

 

Conclusiones

Como veis, y como ya decíamos en un principio, no hay aplicación que no se pueda vulnerar, pero siempre se lo podemos poner todo lo difícil que podamos a quien quiera hacer cosas que no debe con nuestra app.

Algunos de los puntos vistos aquí son:

  • Ofuscar el código todo lo posible para evitar el tampering  
  • Mantener un control de la integridad de la aplicación, por ejemplo, mediante checksums
  • Evitar el uso de métodos obsoletos y, por lo tanto, probablemente vulnerables
  • Implementar buenas prácticas de DevSecOps
  • Usar control de integridad y depuración en tiempo de ejecución
  • Implementar conexiones cifradas y reforzar el Certificate Pinning
  • Preferiblemente, realizar test de penetración siempre que se realice una nueva versión de la app

Hay muchos otros vectores de ataque que no hemos tocado, pero que son muy interesantes y que os animamos a buscar si esta pequeña introducción os ha gustado. Siempre tened en cuenta que esto tan sólo se trata de lo que solemos llamar initial access o vector de entrada. A partir de aquí, comenzaríamos a buscar información, modificar peticiones, analizar la memoria y comprobar incongruencias en la autenticación, entre otros. Hay un sinfín de posibles vulnerabilidades, pero eso lo tendremos que dejar para más adelante.

Manteneos (vosotros y vuestros pipelines de desarrollo) a salvo.

¡Nos vemos en el siguiente artículo!

Pablo Gómez - @hackermate_

 

Referencias