OWASP 类别: MASVS-PLATFORM:平台交互
概述
根据 文档,ContentResolver
是一个“为应用程序提供对内容模型的访问的类”。ContentResolvers 公开方法来交互、获取或修改以下内容提供的内容
- 已安装的应用程序(
content://
URI 方案) - 文件系统(
file://
URI 方案) - Android 提供的支持 API(
android.resource://
URI 方案)。
总之,与 ContentResolver
相关的漏洞属于 混淆代理 类,因为攻击者可以使用易受攻击应用程序的权限来访问受保护的内容。
风险:基于不可信 file:// URI 的滥用
使用 file://
URI 漏洞滥用 ContentResolver
会利用 ContentResolver
返回 URI 描述的文件描述符的功能。此漏洞影响 ContentResolver
API 中的函数,例如 openFile()
、openFileDescriptor()
、openInputStream()
、openOutputStream()
或 openAssetFileDescriptor()
。此漏洞可以通过完全或部分由攻击者控制的 file://
URI 来滥用,以强制应用程序访问不应访问的文件,例如内部数据库或共享首选项。
一种可能的攻击场景是创建一个恶意图片库或文件选择器,当易受攻击的应用程序使用它时,它会返回一个恶意 URI。
此攻击有几种变体
- 指向应用程序内部文件的完全由攻击者控制的
file://
URI file://
URI 的一部分由攻击者控制,使其容易受到路径遍历的攻击file://
URI 针对指向应用程序内部文件的由攻击者控制的符号链接(符号链接)- 类似于前面的变体,但在这里,攻击者反复将符号链接目标从合法目标切换到应用程序的内部文件。目标是利用潜在的安全检查和文件路径使用之间的竞争条件
影响
利用此漏洞的影响取决于 ContentResolver 的用途。在许多情况下,它会导致应用程序的受保护数据被泄露,或者由未经授权的方修改受保护数据。
缓解措施
要缓解此漏洞,请使用以下算法验证文件描述符。通过验证后,可以安全地使用文件描述符。
Kotlin
fun isValidFile(ctx: Context, pfd: ParcelFileDescriptor, fileUri: Uri): Boolean {
// Canonicalize to resolve symlinks and path traversals.
val fdCanonical = File(fileUri.path!!).canonicalPath
val pfdStat: StructStat = Os.fstat(pfd.fileDescriptor)
// Lstat doesn't follow the symlink.
val canonicalFileStat: StructStat = Os.lstat(fdCanonical)
// Since we canonicalized (followed the links) the path already,
// the path shouldn't point to symlink unless it was changed in the
// meantime.
if (OsConstants.S_ISLNK(canonicalFileStat.st_mode)) {
return false
}
val sameFile =
pfdStat.st_dev == canonicalFileStat.st_dev &&
pfdStat.st_ino == canonicalFileStat.st_ino
if (!sameFile) {
return false
}
return !isBlockedPath(ctx, fdCanonical)
}
fun isBlockedPath(ctx: Context, fdCanonical: String): Boolean {
// Paths that should rarely be exposed
if (fdCanonical.startsWith("/proc/") ||
fdCanonical.startsWith("/data/misc/")) {
return true
}
// Implement logic to block desired directories. For example, specify
// the entire app data/ directory to block all access.
}
Java
boolean isValidFile(Context ctx, ParcelFileDescriptor pfd, Uri fileUri) {
// Canonicalize to resolve symlinks and path traversals
String fdCanonical = new File(fileUri.getPath()).getCanonicalPath();
StructStat pfdStat = Os.fstat(pfd.getFileDescriptor());
// Lstat doesn't follow the symlink.
StructStat canonicalFileStat = Os.lstat(fdCanonical);
// Since we canonicalized (followed the links) the path already,
// the path shouldn't point to symlink unless it was changed in the meantime
if (OsConstants.S_ISLNK(canonicalFileStat.st_mode)) {
return false;
}
boolean sameFile =
pfdStat.stDev == canonicalFileStat.stDev && pfdStat.stIno == canonicalFileStat.stIno;
if (!sameFile) {
return false;
}
return !isBlockedPath(ctx, fdCanonical);
}
boolean isBlockedPath(Context ctx, String fdCanonical) {
// Paths that should rarely be exposed
if (fdCanonical.startsWith("/proc/") || fdCanonical.startsWith("/data/misc/")) {
return true;
}
// Implement logic to block desired directories. For example, specify
// the entire app data/ directory to block all access.
}
风险:基于不可信 content:// URI 的滥用
当完全或部分由攻击者控制的 URI 传递给 ContentResolver
API 以对不应访问的内容进行操作时,就会发生使用 content://
URI 漏洞滥用 ContentResolver
。
此攻击主要有两种场景
- 该应用程序使用其自身内部内容进行操作。例如:在从攻击者获取 URI 后,邮件应用程序会附加来自其自身内部内容提供程序的数据,而不是外部照片。
- 该应用程序充当代理,然后访问另一个应用程序的数据以供攻击者使用。例如:邮件应用程序附加来自应用程序 X 的数据,这些数据受权限保护,通常不允许攻击者查看该特定附件。它可用于进行附件操作的应用程序,但最初不可用,因此将此内容转发给攻击者。
一种可能的攻击场景是创建一个恶意图库或文件选择器,当易受攻击的应用程序使用它时,它将返回一个恶意 URI。
影响
利用此漏洞的影响因与 ContentResolver 相关的上下文而异。这可能导致应用程序的受保护数据被泄露或被未经授权的方修改。
缓解措施
一般
验证传入的 URI。例如,使用预期授权的允许列表被认为是最佳实践。
URI 针对属于易受攻击应用程序的非导出或权限保护的内容提供程序
检查 URI 是否针对您的应用程序
Kotlin
fun belongsToCurrentApplication(ctx: Context, uri: Uri): Boolean {
val authority: String = uri.authority.toString()
val info: ProviderInfo =
ctx.packageManager.resolveContentProvider(authority, 0)!!
return ctx.packageName.equals(info.packageName)
}
Java
boolean belongsToCurrentApplication(Context ctx, Uri uri){
String authority = uri.getAuthority();
ProviderInfo info = ctx.getPackageManager().resolveContentProvider(authority, 0);
return ctx.getPackageName().equals(info.packageName);
}
或者目标提供程序是否已导出
Kotlin
fun isExported(ctx: Context, uri: Uri): Boolean {
val authority = uri.authority.toString()
val info: ProviderInfo =
ctx.packageManager.resolveContentProvider(authority, 0)!!
return info.exported
}
Java
boolean isExported(Context ctx, Uri uri){
String authority = uri.getAuthority();
ProviderInfo info = ctx.getPackageManager().resolveContentProvider(authority, 0);
return info.exported;
}
或者是否已授予对 URI 的显式权限 - 此检查基于以下假设:如果已授予对数据的显式访问权限,则 URI 并非恶意
Kotlin
// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
fun wasGrantedPermission(ctx: Context, uri: Uri?, grantFlag: Int): Boolean {
val pid: Int = Process.myPid()
val uid: Int = Process.myUid()
return ctx.checkUriPermission(uri, pid, uid, grantFlag) ==
PackageManager.PERMISSION_GRANTED
}
Java
// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
boolean wasGrantedPermission(Context ctx, Uri uri, int grantFlag){
int pid = Process.myPid();
int uid = Process.myUid();
return ctx.checkUriPermission(uri, pid, uid, grantFlag) == PackageManager.PERMISSION_GRANTED;
}
URI 针对属于另一个应用程序的权限保护的内容提供程序,该应用程序信任易受攻击的应用程序。
此攻击与以下情况相关
- 应用程序生态系统,其中应用程序定义和使用自定义权限或其他身份验证机制。
- 权限代理攻击,攻击者滥用持有运行时权限(如 READ_CONTACTS)的易受攻击的应用程序来检索来自系统提供程序的数据。
测试 URI 权限是否已授予
Kotlin
// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
fun wasGrantedPermission(ctx: Context, uri: Uri?, grantFlag: Int): Boolean {
val pid: Int = Process.myPid()
val uid: Int = Process.myUid()
return ctx.checkUriPermission(uri, pid, uid, grantFlag) ==
PackageManager.PERMISSION_GRANTED
}
Java
// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
boolean wasGrantedPermission(Context ctx, Uri uri, int grantFlag){
int pid = Process.myPid();
int uid = Process.myUid();
return ctx.checkUriPermission(uri, pid, uid, grantFlag) == PackageManager.PERMISSION_GRANTED;
}
如果使用其他内容提供程序不需要权限授予(例如,当应用程序允许生态系统中的所有应用程序访问所有数据时),则明确禁止使用这些授权。