La aparición de decenas de apps dedicadas a brindar servicios relacionados al COVID-19 ha sido sin dudas una de las tendencias más importantes en lo que a seguridad móvil respecta en lo que va de este atípico 2020. En el caso de las apps para el rastreo de contactos de COVID-19 -comúnmente llamadas “COVID trackers”-, las mismas nacieron con la intención de geolocalizar individuos que potencialmente fueran portadores del virus, buscando servir como herramientas de diagnóstico temprano y también como una fuente de estadísticas para los diversos gobiernos que las impulsaron. Muchas otras aparecieron para brindar soporte a nuevos servicios originados en el marco de las diversas restricciones sanitarias que regulan la circulación de personas.

Esta situación en la que nos vemos inmersos ciertamente ha desafiado la capacidad de los gobiernos para desplegar rápidamente servicios digitales al público, sin menoscabar la seguridad de los datos que procesan. Por ello, imaginé que sería interesante descargar y analizar las apps para Android más relevantes relacionadas al COVID-19 impulsadas por autoridades latinoamericanas. El objetivo de esta investigación es poder rever a través de una serie de artículos que iremos publicado cuáles son algunos de los errores más comunes que hemos visto a la hora de generar este tipo de aplicaciones para poder aprender de ellos y así evitarlos a futuro.

Lectura relacionada: Apps de rastreo de contactos de COVID‑19: ¿ayuda tecnológica o preocupación para la privacidad?

En esta primera entrega analizamos la seguridad de las bases de datos de Firebase destinadas a almacenar la información de los usuarios, las cuales han sido utilizadas por varias apps de rastreo de contactos en distintos países, presentando en algunos casos errores en la configuración que afectaban a la seguridad y privacidad de los datos.

Resultado de la investigación

Al momento de escribir este artículo analizamos 17 aplicaciones pertenecientes a autoridades gubernamentales de países como Argentina, Bolivia, Brasil, Chile, Colombia, Ecuador, Guatemala, México, Perú y Uruguay. Del total de aplicaciones analizadas -todas disponibles en la Play Store-, 14 utilizaban Firebase Realtime Database para almacenar datos.

Tras aplicar las técnicas que mencionaremos más adelante en esta publicación, detectamos que dos de estas apps de rastreo, ambas de Argentina e impulsadas por gobiernos provinciales y municipalidades locales, eran vulnerables a posibles ataques dado que se conectaban con bases de datos públicas para procesar datos privados de más de 6000 usuarios, como son nombres, apellidos, fechas de nacimiento, DNI, correos electrónicos, miles de puntos de geolocalización (algunos directamente asociados a un usuario puntual), números de teléfono, y datos de seguimiento médicos sobre los pacientes (si se les realizó un hisopado y si resultaron positivos), entre otra información.

Cabe destacar que las vulnerabilidades que mencionaremos en este artículo han sido reparadas antes de su publicación, siguiendo las políticas de divulgación responsable.

Cómo se gestiona la seguridad en Realtime Database

Creo que es seguro decir que todo programador o pentester móvil conoce o ha trabajado alguna vez con Firebase. Esta suite de desarrollo creada por Google es la primera opción en la que se piensa cuando se precisa una solución rápida para el almacenamiento de datos y el envío de mensajes en aplicaciones cliente-servidor.

Los datos se guardan en formato JSON y pueden ser consultados o modificados a través de una API tipo REST. Aunque existen reglas que pueden configurarse en cascada para controlar el acceso a la información sensible, muchas veces estas reglas están mal definidas y permiten a un atacante recuperar los datos almacenados en diferentes niveles de la ruta de acceso.

Primero, demos un rápido repaso por las reglas de seguridad de Firebase Realtime Database. Básicamente, existen tres reglas a tener en cuenta: .read (lectura), .write (escritura) y .validate (validación de datos). Las reglas de acceso definidas por los desarrolladores se almacenan en el archivo rules.json en la raíz de la jerarquía.

La autenticación puede darse de forma anónima asignando un identificador aleatorio a cada usuario, vía email/contraseña, o con la ayuda de apps de terceros mediante OAuth (por ejemplo, usando el UID de Facebook). Cuando el usuario se haya autenticado exitosamente, la variable auth definida por el lenguaje de reglas dejará de ser null. La autenticación anónima está inhabilitada por defecto y puede configurarse accediendo a la siguiente ruta en el panel de control:

https://console.firebase.google.com/u/0/project/[id-proyecto]/authentication/providers

Imagen 1. Habilitación de proveedores de autenticación en la consola de Firebase

La forma más sencilla de controlar el acceso consiste en otorgar o denegar permisos de lectura/escritura sobre la ruta de acceso de manera completa, sin tener en cuenta procesos de autenticación de usuarios (e.g. “.read”:true).

Entonces, ahora que conocemos cómo funciona, ¿qué puede salir mal?

  • La base de datos permite lectura/escritura a cualquiera. Es raro que esto ocurra en producción dado que desde 2016, las reglas .read y .write que no estén expresamente definidas serán false por defecto, y las nuevas bases de datos se crean con ambos permisos definidos en false. Por lo que el desarrollador deberá expresamente redefinir estas reglas a true o mantener los permisos de desarrollo en producción para que este escenario de ataque funcione. En código, esta vulnerabilidad se vería así:
{
	"rules":{
		".read": true,
		".write": true
	}
}
  • La base de datos permite lectura/escritura a cualquier usuario autenticado. En este escenario, si la base de datos tiene habilitada la opción de autenticación anónima, el atacante podría acceder a los datos mediante un ID aleatorio. Además, dependiendo de la aplicación, para un atacante puede llegar a ser muy sencillo crear un usuario dentro del sistema para presumir ser un usuario legítimo. Por ello, no podemos confiar en que la variable auth sea distinta a null para garantizar la legitimidad del usuario.
{

        "rules":{

                 ".read": “auth.uid != null”,

                 ".write": “auth.uid != null”

        }

}

 

Por el contrario, debemos chequear que el request.auth.uid del usuario logueado esté anidado dentro del recurso o ruta al que este intenta acceder. Por ejemplo:

{
	"rules": {
		"secreto": {
			"$uid": {
				".read": "request.auth.uid == uid"
				".write": "request.auth.uid == uid"
				}
			}
		}
}
  • Las reglas en cascada se anulan entre ellas. Las reglas .read y .write definidas en niveles más altos de la jerarquía anulan cualquier regla que se defina para las rutas de acceso en ellos contenidas. Por ejemplo, miremos el siguiente código:

Imagen 2. Ejemplo de reglas en cascada inseguras en Realtime Database

A pesar de que vemos que el atributo content está protegido por una validación, un atacante igualmente puede acceder a él de forma pública porque existe una regla “.read”: true en un nivel superior de la jerarquía.

Imagen 3. Ejemplo de reglas en cascada inseguras en Realtime Database

Explotando malas configuraciones en Firebase

Para poder explotar una base de datos Firebase deberemos encontrar el ID de proyecto dentro del archivo resources.arsc>res>values>string.xml de los recursos del APK. En particular, buscamos la variable de nombre “firebase_database_url”, que contiene una URL terminada en “.firebaseio.com”:

Imagen 4. Variables de Firebase en los recursos del APK

Para corroborar si la base de datos tiene permisos de lectura públicos, podemos realizar una consulta con curl utilizando la API REST de Firebase y  añadiendiendo .json al final de la URL. Si la base de datos es muy grande, podemos usar el parámetro shallow de la siguiente manera:

curl 'https://[id-proyecto].firebaseio.com/.json?shallow=true'

Si la consulta nos devuelve un JSON con datos o null, la base de datos es vulnerable. De lo contrario, nos devolverá un mensaje {"error":"Permission denied"}. Existen un par de herramientas que permiten automatizar este proceso, como Insecure-Firebase-Exploit, FireBase Scanner, este script o pyrebase, aunque creo que lo más práctico es utilizar directamente curl.

Ahora bien, si deseáramos chequear permisos de escritura podemos utilizar la misma API para hacer una petición PUT con algún valor arbitrario, para luego eliminarlo.

curl -X POST -d '{"data" : "DB is vulnerable!"}' https://[id-proyecto].firebaseio.com/.json

Esta consulta devolverá un JSON indicando el ID del nuevo nodo (por ejemplo, {"name":"-MBg-QdL0k9hjYenkXI5"}), que luego podremos utilizar para deshacer la modificación de la siguiente manera:

curl -X DELETE https://[id-proyecto].firebaseio.com/ID.json

Chequear por permisos públicos de lectura/escritura es lo que se acostumbra al momento de analizar la seguridad de una base de datos Firebase. Sin embargo, existen otras comprobaciones que pueden realizarse para hallar fallos de seguridad. Por ejemplo, para verificar si el acceso anónimo está permitido en la base de datos. Para ello, una buena pista puede ser encontrar en la aplicación la utilización del método signInAnonymously(). De cualquier manera, podemos corroborar si este modo de inicio de sesión está habilitado en la base de datos emitiendo la siguiente consulta:

curl 'https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=[api-key]' -H 'Content-Type: application/json' - -data-binary '{"returnSecureToken":true}'

Podemos obtener el valor api-key analizando el código fuente de la aplicación. Por ejemplo, si la aplicación se creó con Cordova o React Native y siempre y cuando no esté ofuscada, podemos utilizar herramientas como binwalk o un simple grep para encontrarlo dentro de los assets de la aplicación de la siguiente manera:

Imagen 5. Claves de API para conectar con Realtime Database

También podemos utilizar Runtime Mobile Security (RMS) para hookear los métodos que manejan peticiones a la base de datos, como com.google.firebase.FirebaseApp.getInstance(), para obtener este valor de manera dinámica.

Imagen 6. Generación de un token de manera anónima

Si la consulta devuelve mensajes como “PERMISSION_DENIED” o “ADMIN_ONLY_OPERATION”, entonces sabemos que no es posible autenticarse de manera anónima. De lo contrario, obtendremos un resultado como el siguiente:

Imagen 7. Generación de un token de manera anónima.

Luego, podemos utilizar el token antes obtenido para consultar la base de datos y ver si obtenemos acceso al contenido que anteriormente era privado.

curl "https://[id-proyecto].firebaseio.com/ruta/del/recurso.json?auth=<ID_TOKEN>"

Puede que, aún con una sesión anónima, no tengamos acceso a la base de datos entera. En este caso, podemos analizar el código de la aplicación para deducir las rutas de acceso y probar si están bien configuradas. También podemos intentar crear un usuario de prueba en el sistema y luego usar su token de autenticación para peticionar diferentes recursos.

Otra de las cosas que podría probar un atacante es encontrar otras bases de datos asociadas al mismo proyecto siguiendo la convención de nombres del desarrollador. Es decir, si el ID de proyecto es empresa-producto-prod, podría corroborar la existencia de empresa-producto-test o empresa-producto-dev para intentar obtener información de ellas. Muchas veces, estas bases de datos poseen reglas de acceso más flexibles y están repletas de información sensible.

Conclusión

Encontrar bases de datos vulnerables en desarrollos móviles no es un problema nuevo. En 2018, investigadores escanearon apps en busca de bases de datos de Firebase mal configuradas y descubrieron que el 9% de los APK analizados se conectaban a bases de datos vulnerables. Esta cifra aumentaba al 47% para apps de iOS.

Un reporte más reciente publicado en mayo de 2020 por Comparitech encontró que un 4.8% de las apps que usan Google Firebase no están correctamente configuradas, pudiendo conducir a fugas de información. Los investigadores estimaron que un 0.83% de todas las apps publicadas en Google Play exponen datos sensibles a través de Firebase, lo que representaría aproximadamente un total de 24.000 aplicaciones vulnerables.

La buena noticia es que este tipo de errores es completamente evitable. Solo debemos cerciorarnos de que entendemos cómo funciona la autenticación y autorización en la suite de Firebase, qué información queremos proteger y realizar testeos sobre nuestras bases de datos en producción para asegurarnos de que no son susceptibles de este tipo de ataques. En la documentación de la plataforma podremos encontrar mucha información sobre la forma correcta de configurar estos productos, como este artículo sobre configuraciones inseguras o este otro con 7 consejos de seguridad.