自Android 14起,出现一个利用零宽度字符(\u200b)绕过/sdcard/Android/data目录访问限制的漏洞。其原理是该字符在系统路径校验时被过滤,从而构造出合规的别名路径,使应用在未获授权时也能读取或写入该目录。该漏洞在Android 16上依然有效,已被MT管理器等应用采用。

博主博客

Android 6.0以前

应用只需要在 AndroidManifest.xml 下声明以下权限即可自动获取存储权限:

Android 6.0~10

需要动态申请权限:

// 检查是否有存储权限
boolean granted = context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) ==
    PackageManager.PERMISSION_GRANTED;
// 在activity中请求存储权限
requestPermissions(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, 0);

Android 10

暂停沙盒机制,在 AndroidManaifest.xml 加入:

<application
	...
	android:requestLegacyExternalStorage="true">

Android 11~12

使用官方提供的 SAF框架(Storage Access Framework)。

DocumentsContract.buildTreeDocumentUri(EXTERNAL_STORAGE_PROVIDER_AUTHORITY, String.format("primary:Android/data/%s", "com.uso6")

Android 13

使用 Shizuku 进行授权, 需要接入 Shizuku-API

MT管理器使用了这种方式进行实现。

Android 14+

利用零宽度字符的 BUG 进行访问, 目前在 Android 16 依然生效。问题原因大概是,Linux 默认存储是大小写敏感的,而Android 是大小写不敏感的。为了这个特性,Android 启用了 Linux 内核中一个叫 case-fold 的特性,可以把大小写表示视为同一种路径。但是这个特性标准化时会忽略一些特殊字符,进而导致了这个问题。

MT管理器使用了这种方式进行实现。

File dataDir = new File(Environment.getExternalStorageDirectory(), "Android/data");  
Log.e("nukix", "dataDir: read: " + dataDir.canRead() + ", write: " + dataDir.canWrite());
// dataDir: read: false, write: false
  
File dataDirZero = new File(Environment.getExternalStorageDirectory(), "Android/\u200bdata");  
Log.e("nukix", "dataDirZero: read: " + dataDirZero.canRead() + ", write: " + dataDirZero.canWrite());
// dataDirZero: read: true, write: true

参考文献