一、业务场景
FileProvider 是 ContentProvider 的一个特殊子类,通过生成 content:// URI(而非 file:/// URI)来安全地实现应用间的文件共享。
典型配置如下:
<application ...>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.demo.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
res/xml/provider_paths.xml 文件如下:
解释如下:-
files-path → 映射到 Context.getFilesDir()
即路径:
/data/user/<user_id>/<package>/files/ -
path=“share” → 允许共享 files/share/ 子目录 path=“share” → 允许共享 files/share/ 子目录
-
name=“shared_files” → 仅为规则名称,不影响访问路径或权限
当应用通过以下代码获取 URI:
Uri uri = FileProvider.getUriForFile(
this,
"com.example.demo.fileprovider",
file
);
最终可共享的实际目录为:
/data/user/<user_id>/<包名>/files/share/
二、配置不当及风险分析
-
标签配置过宽风险
provider_paths.xml 中除了 files-path 之外,还有其他标签可定义根目录,例如 root-path、external-path 等,全部标签定义与风险如下表所示。
| paths 标签 | 对应 Context / API(系统实际调用) | 实际共享路径示例(user 0) | 风险等级与原因说明 |
|---|---|---|---|
| root-path | 无,直接指向系统根目录 '/' | / | 极高:理论上可映射任意路径,配置不当 = 全盘裸奔 |
| files-path | Context.getFilesDir() | /data/user/0/[package]/files | 较低:应用私有目录,通常可控,官方推荐为共享根目录 |
| cache-path | Context.getCacheDir() | /data/user/0/[package]/cache | 较低:私有缓存目录,但文件不长期存储,泄露影响较小 |
| external-path | Environment.getExternalStorageDirectory() | /sdcard/ | 极高:暴露整个外置存储,攻击者可越权读取大量无关文件 |
| external-files-path | Context.getExternalFilesDirs(null) | /sdcard/Android/data/[package]/files | 中等:虽为 App 专属目录,但可能包含大量业务数据,泄露面较大 |
| external-cache-path | Context.getExternalCacheDirs() | /sdcard/Android/data/[package]/cache | 中等:缓存内容不可控,易形成侧信道或残留敏感数据泄露 |
| external-media-path | Context.getExternalMediaDirs() | /sdcard/Android/media/[package]/ | 中~偏高:多存放媒体文件,若配置过宽可能暴露用户照片/视频等隐私资源 |
-
path路径配置不当风险
上述是标签的定义,但实际上,要分享的目录还进一步去取决于path的定义,path 的设置直接决定了共享权限的边界。
FileProvider 最终生效的共享目录 = 标签对应的基础路径 + path 属性指定的子路径。
例如:
<files-path name="shared_files" path="share" />实际共享目录为:/data/user/0/[package]/files/share/
如果 path 设置为 空字符串 ("") 、点 (.) 或 斜杠 (/) ,则 FileProvider 不会追加任何子路径,导致:
-》最终共享目录 = 标签对应的整个基础目录
-》共享范围过大
-》同目录下的所有文件均可能被其他应用访问
-》直接造成数据泄露风险
以files-path为例,其基础路径为:/data/user/0/<package>/files,path设置为空字符串 ("") 、点 (.) 或 斜杠 (/),其最终的路径都是/data/user/0/<package>/files
三、攻击场景
标签与path路径结合定义出来的目录范围过宽,并不会立刻产生漏洞,要真正形成漏洞,需要以下 两个条件同时成立:
- 可被外部攻击者控制的文件名 / 文件路径输入
- 应用存在实际的共享行为(如分享 Intent 或导出 URI),授权给外部恶意应用 如果当前业务没有第 2 点,即便路径配置过宽,也不会直接导致越权。但不符合最小权限原则,属于必须整改的高危配置。
满足以上两个条件,可能会被攻击者利用,如下三个场景:
场景 1:恶意 App 劫持分享 Intent(Activity Hijacking)
攻击链:
-
受害 App 构造分享文件的 Intent
-
系统匹配可接收的 Activity
-
恶意 App 注册高优先级接收器
-
恶意 App 抢占 Intent → 自动获得 READ 临时权限
影响:可直接读取 FileProvider 暴露的全部目录内容
场景 2:恶意 App 强制触发分享逻辑
攻击方式:
-
构造特殊 deep link
-
引导受害 App 打开页面
-
自动触发分享逻辑
-
系统为恶意 App 授予读取权限
风险:宽松路径可能使整个目录被读取
场景 3:宽松路径放大业务未来迭代的风险
即便当前没有文件名可控输入 或 没有对外分享行为,宽松配置依旧是潜在隐患:
只要未来版本中添加了“对外共享文件”的功能,或用户可控文件名,漏洞就会立刻被触发。
这也是安全审计中最常见的风险放大点。
四、安全建议(强制)
1. 禁用高风险标签
以下标签应避免使用:
-
root-path
-
external-path
原因:这两个目录范围过大,极易造成无意中共享整个文件系统区域。
2. 严格限制 path 值
禁止:
-
path=""
-
path="."
-
path="/"
推荐:
- 明确限定子目录,例如:
<files-path name="secure_images" path="images_secure/" />
<cache-path name="tmp_export" path="export_tmp/" />
3. 最小化授权(强制)
- android:exported="false"
- 必须使用 grantUriPermissions="true" 才能对指定接收者动态授权
- 不允许全局向任意 App 暴露访问权限
五、风险排查方法
排查步骤:
-
检查 AndroidManifest 中的 FileProvider 定义
-
定位其对应的 provider_paths.xml
-
查找是否存在以下危险配置:
-
external-path
-
root-path
-
path=""、 "."、"/"
-
若发现宽松配置,进一步核实:
- 是否存在用户可控 filename
- 是否存在分享行为(例如 ACTION_SEND)
- 是否存在导出 URI 给三方的流程
- 是否存在可被外部 App 触发的自动分享机制
即使当前不存在漏洞,也需按安全要求立即整改。