跳过内容

MASTG-DEMO-0005: 应用通过 MediaStore API 写入外部存储

下载 MASTG-DEMO-0005 APK 打开 MASTG-DEMO-0005 文件夹 构建 MASTG-DEMO-0005 APK

示例

下面的代码片段显示了使用 MediaStore API 将文件写入共享存储(例如 /storage/emulated/0/Download/)的示例代码,该路径不需要任何访问权限,并且与其他应用程序共享。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package org.owasp.mastestapp

import android.content.ContentValues
import android.util.Log
import android.content.Context
import android.os.Environment
import android.provider.MediaStore
import java.io.OutputStream

class MastgTest (private val context: Context){

    fun mastgTest(): String {
        try {
            val resolver = context.contentResolver
            val contentValues = ContentValues().apply {
                put(MediaStore.MediaColumns.DISPLAY_NAME, "secretFile.txt")
                put(MediaStore.MediaColumns.MIME_TYPE, "text/plain")
                put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
            }
            val textUri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)

            textUri?.let {
                val outputStream: OutputStream? = resolver.openOutputStream(it)
                outputStream?.use {
                    it.write("MAS_API_KEY=8767086b9f6f976g-a8df76\n".toByteArray())
                    it.flush()
                }
                Log.d("MediaStore", "File written to external storage successfully.")
                return  "SUCCESS!!\n\nMediaStore inserted to $textUri"
            } ?: run {
                Log.e("MediaStore", "Error inserting URI to MediaStore.")
                return  "FAILURE!!\n\nMediaStore couldn't insert data."
            }
        } catch (exception: Exception) {
            Log.e("MediaStore", "Error writing file to URI from MediaStore", exception)
            return "FAILURE!!\n\nMediaStore couldn't insert data."
        }
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package org.owasp.mastestapp;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import java.io.OutputStream;
import kotlin.Metadata;
import kotlin.Unit;
import kotlin.io.CloseableKt;
import kotlin.jvm.internal.Intrinsics;
import kotlin.text.Charsets;
/* compiled from: MastgTest.kt */
@Metadata(d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u000e\n\u0000\b\u0007\u0018\u00002\u00020\u0001B\r\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0002\u0010\u0004J\u0006\u0010\u0005\u001a\u00020\u0006R\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000¨\u0006\u0007"}, d2 = {"Lorg/owasp/mastestapp/MastgTest;", "", "context", "Landroid/content/Context;", "(Landroid/content/Context;)V", "mastgTest", "", "app_debug"}, k = 1, mv = {1, 9, 0}, xi = 48)
/* loaded from: classes4.dex */
public final class MastgTest {
    public static final int $stable = 8;
    private final Context context;

    public MastgTest(Context context) {
        Intrinsics.checkNotNullParameter(context, "context");
        this.context = context;
    }

    public final String mastgTest() {
        try {
            ContentResolver resolver = this.context.getContentResolver();
            ContentValues contentValues = new ContentValues();
            contentValues.put("_display_name", "secretFile.txt");
            contentValues.put("mime_type", "text/plain");
            contentValues.put("relative_path", Environment.DIRECTORY_DOCUMENTS);
            Uri textUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
            if (textUri != null) {
                OutputStream outputStream = resolver.openOutputStream(textUri);
                if (outputStream != null) {
                    OutputStream outputStream2 = outputStream;
                    OutputStream it = outputStream2;
                    byte[] bytes = "MAS_API_KEY=8767086b9f6f976g-a8df76\n".getBytes(Charsets.UTF_8);
                    Intrinsics.checkNotNullExpressionValue(bytes, "this as java.lang.String).getBytes(charset)");
                    it.write(bytes);
                    it.flush();
                    Unit unit = Unit.INSTANCE;
                    CloseableKt.closeFinally(outputStream2, null);
                }
                Log.d("MediaStore", "File written to external storage successfully.");
                return "SUCCESS!!\n\nMediaStore inserted to " + textUri;
            }
            MastgTest mastgTest = this;
            Log.e("MediaStore", "Error inserting URI to MediaStore.");
            return "FAILURE!!\n\nMediaStore couldn't insert data.";
        } catch (Exception exception) {
            Log.e("MediaStore", "Error writing file to URI from MediaStore", exception);
            return "FAILURE!!\n\nMediaStore couldn't insert data.";
        }
    }
}

步骤

让我们针对示例代码运行我们的 semgrep 规则。

../../../../rules/mastg-android-data-unencrypted-shared-storage-no-user-interaction-apis.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
rules:
  - id: mastg-android-data-unencrypted-shared-storage-no-user-interaction-external-api-public
    severity: WARNING
    languages:
      - java
    metadata:
      summary: This rule looks for methods that returns locations to "external storage" which is shared with other apps
    message: "[MASVS-STORAGE] Make sure to encrypt files at these locations if necessary"
    pattern-either:
      - pattern: $X.getExternalStorageDirectory(...)
      - pattern: $X.getExternalStoragePublicDirectory(...)
      - pattern: $X.getDownloadCacheDirectory(...)
      - pattern: Intent.ACTION_CREATE_DOCUMENT
  - id: mastg-android-data-unencrypted-shared-storage-no-user-interaction-external-api-scoped
    severity: WARNING
    languages:
      - java
    metadata:
      summary: This rule looks for methods that returns locations to "scoped external storage"
    message: "[MASVS-STORAGE] These locations might be accessible to other apps on Android 10 and below given relevant permissions"
    pattern-either:
      - pattern: $X.getExternalFilesDir(...)
      - pattern: $X.getExternalFilesDirs(...)
      - pattern: $X.getExternalCacheDir(...)
      - pattern: $X.getExternalCacheDirs(...)
      - pattern: $X.getExternalMediaDirs(...)
  - id: mastg-android-data-unencrypted-shared-storage-no-user-interaction-mediastore
    severity: WARNING
    languages:
      - java
    metadata:
      summary: This rule scans for uses of MediaStore API that writes data to the external storage. This data can be accessed by other apps.
    message: "[MASVS-STORAGE] Make sure to want this data to be shared with other apps"
    pattern-either:
      - pattern: import android.provider.MediaStore
      - pattern: $X.MediaStore
run.sh
1
NO_COLOR=true semgrep -c ../../../../rules/mastg-android-data-unencrypted-shared-storage-no-user-interaction-apis.yml ./MastgTest_reversed.java --text -o output.txt

观察

该规则已识别出 2 个指示使用 MediaStore API 的位置。

output.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
┌─────────────────┐
 2 Code Findings 
└─────────────────┘

    MastgTest_reversed.java 
       rules.mastg-android-data-unencrypted-shared-storage-no-user-interaction-mediastore
          [MASVS-STORAGE] Make sure to want this data to be shared with other apps       

            8 import android.provider.MediaStore;
            ⋮┆----------------------------------------
           35 Uri textUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);

第一个位置是 MediaStore API 的导入语句,第二个位置是使用 MediaStore API 写入共享存储的位置。

评估

在审查输出中指定位置(文件和行号)的反编译代码后,我们可以得出结论,该测试失败,因为此实例写入的文件包含敏感数据,特别是 API 密钥。