Categoría de OWASP: MASVS-PLATFORM: Interacción con la plataforma
Descripción general
Según la documentación, ContentResolver
es una "clase que proporciona a las aplicaciones acceso al modelo de contenido". ContentResolvers expone métodos para interactuar, recuperar o modificar contenido proporcionado de lo siguiente:
- Apps instaladas (esquema de URI
content://
) - Sistemas de archivos (esquema de URI
file://
) - Compatibilidad con APIs que proporciona Android (esquema de URI
android.resource://
)
En resumen, las vulnerabilidades relacionadas con ContentResolver
pertenecen a la clase de difusión confundida, ya que el atacante puede usar los privilegios de una aplicación vulnerable para acceder a contenido protegido.
Riesgo: Abuso basado en el URI file:// no confiable
El abuso de ContentResolver
con la vulnerabilidad del URI file://
se aprovecha de la capacidad de ContentResolver
para devolver descriptores de archivos que describe el URI. Esta vulnerabilidad afecta funciones como openFile()
, openFileDescriptor()
, openInputStream()
, openOutputStream()
o openAssetFileDescriptor()
de la API de ContentResolver
. La vulnerabilidad se puede usar de forma indebida con un URI file://
controlado de manera total o parcial por un atacante para forzar a la aplicación a acceder a archivos a los que no se debería poder acceder, como bases de datos internas o preferencias compartidas.
Una de las posibles situaciones de ataque sería crear una galería maliciosa o un selector de archivos que, cuando una app vulnerable los use, devolvería un URI malicioso.
Hay algunas variantes de este ataque:
- Un URI de
file://
completamente controlado por el atacante que apunta a los archivos internos de una app - Parte del URI de
file://
está controlado por atacantes, por lo que es propenso a los saltos de directorio - El URI de
file://
dirigido a un vínculo simbólico (symlink) controlado por atacantes que apunta a los archivos internos de la app - Similar a la variante anterior, pero, en este caso, el atacante cambia el objetivo de symlink de forma repetida de un destino legítimo a los archivos internos de una app. El objetivo es aprovecharse de una condición de carrera entre un posible control de seguridad y el uso de rutas de archivos
Impacto
El impacto de aprovecharse de esta vulnerabilidad varía según el uso que se haga de ContentResolver. En muchos casos, puede provocar que se extraigan los datos protegidos de una app o que terceros no autorizados modifiquen los datos protegidos.
Mitigaciones
Para mitigar esta vulnerabilidad, usa el siguiente algoritmo y valida el descriptor de archivo. Después de pasar la validación, se puede usar el descriptor de archivo de forma segura.
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.
}
Riesgo: Uso indebido basado en el URI de content:// no confiable
El uso indebido de un ContentResolver
con una vulnerabilidad de URI de content://
ocurre cuando se pasa un URI que un atacante controla de forma total o parcial a las APIs de ContentResolver
para que opere en contenido al que no se debería poder acceder.
Hay dos situaciones principales para este ataque:
- La app opera en su propio contenido interno. Por ejemplo, después de obtener un URI de un atacante, la app de correo electrónico adjunta datos de su propio proveedor de contenido interno, en lugar de una foto externa.
- La app actúa como un proxy y accede a los datos de otra aplicación para el atacante. Por ejemplo, la aplicación de correo electrónico adjunta datos de la app X que están protegidos por un permiso que normalmente no permitiría al atacante ver ese archivo adjunto específico. Está disponible para la aplicación que realiza el archivo adjunto, pero no al principio, por lo que se retransmite este contenido al atacante.
Una posible situación de ataque es crear una galería o un selector de archivos maliciosos que, cuando una app vulnerable los use, devolverán un URI malicioso.
Impacto
El impacto de aprovecharse de esta vulnerabilidad varía según el contexto asociado con el ContentResolver. Esto podría provocar que se extraigan los datos protegidos de una app o que grupos no autorizados modifiquen los datos protegidos.
Mitigaciones
General
Valida los URIs entrantes. Por ejemplo, se recomienda usar una lista de autoridades esperadas permitidas.
El URI se orienta al proveedor de contenido no exportado o protegido por permisos que pertenece a la app vulnerable
Verifica si el URI se orienta a tu app:
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);
}
O bien, si se exportó el proveedor objetivo:
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;
}
O si se otorga permiso explícito al URI, esta verificación se basa en la suposición de que si se otorga permiso explícito para acceder a los datos, el URI no es malicioso:
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;
}
El URI se orienta a un ContentProvider protegido por permisos que pertenece a otra app que confía en la app vulnerable.
Este ataque es relevante en las siguientes situaciones:
- Ecosistemas de aplicaciones en los que las apps definen y usan permisos personalizados o, también, otros mecanismos de autenticación
- Ataques de proxy de permiso, en los que un atacante hace uso indebido de una app vulnerable que tiene un permiso de tiempo de ejecución, como READ_CONTACTS, para recuperar datos de un proveedor del sistema
Prueba si se otorgó el permiso de 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;
}
Si el uso de otros proveedores de contenido no requiere una concesión de permisos, como cuando la app permite que todas las apps del ecosistema accedan a todos los datos, entonces prohíbe de forma explícita el uso de estas autoridades.