Категория OWASP: MASVS-ПЛАТФОРМА: Взаимодействие платформы
Обзор
Согласно документации , ContentResolver
— это «класс, предоставляющий приложениям доступ к модели контента» . ContentResolvers предоставляют методы для взаимодействия, извлечения или изменения содержимого, предоставленного из следующих источников:
- Установленные приложения (схема URI
content://
) - Файловые системы (схема URI
file://
) - Поддержка API, предоставляемых Android (схема URI
android.resource://
).
Подводя итог, можно сказать, что уязвимости, связанные с ContentResolver
относятся к классу «запутанный заместитель» , поскольку злоумышленник может использовать привилегии уязвимого приложения для доступа к защищенному контенту.
Риск: злоупотребление на основе ненадежного URI file://
Злоупотребление ContentResolver
с использованием уязвимости file://
URI использует способность ContentResolver
возвращать файловые дескрипторы, описанные URI. Эта уязвимость затрагивает такие функции, как openFile()
, openFileDescriptor()
, openInputStream()
, openOutputStream()
или openAssetFileDescriptor()
из ContentResolver
API . Эту уязвимость можно использовать с помощью URI file://
полностью или частично контролируемого злоумышленником, чтобы заставить приложение получить доступ к файлам, доступ к которым не предполагался, например, к внутренним базам данных или общим настройкам.
Одним из возможных сценариев атаки может быть создание вредоносной галереи или средства выбора файлов, которое при использовании уязвимым приложением будет возвращать вредоносный URI.
Существует несколько вариантов этой атаки:
- Полностью контролируемый злоумышленником URI
file://
, указывающий на внутренние файлы приложения. - Часть URI
file://
контролируется злоумышленником, что делает его уязвимым для обхода путей. -
file://
URI, нацеленный на контролируемую злоумышленником символическую ссылку (символическую ссылку), указывающую на внутренние файлы приложения. - Аналогично предыдущему варианту, но здесь злоумышленник неоднократно меняет цель символической ссылки с законной цели на внутренние файлы приложения. Цель состоит в том, чтобы использовать состояние гонки между потенциальной проверкой безопасности и использованием пути к файлу.
Влияние
Последствия использования этой уязвимости различаются в зависимости от того, для чего используется ContentResolver. Во многих случаях это может привести к краже защищенных данных приложения или изменению защищенных данных неавторизованными сторонами.
Смягчения
Чтобы устранить эту уязвимость, используйте приведенный ниже алгоритм для проверки дескриптора файла. После прохождения проверки файловый дескриптор можно безопасно использовать.
Котлин
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.
}
Ява
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.
}
Риск: злоупотребление на основе ненадежного контента:// URI
Злоупотребление ContentResolver
с использованием уязвимости URI content://
происходит, когда URI, полностью или частично контролируемый злоумышленником, передается API-интерфейсам ContentResolver
для работы с контентом, доступ к которому не предполагался.
Существует два основных сценария этой атаки:
- Приложение работает на собственном внутреннем контенте. Например: получив URI от злоумышленника, почтовое приложение прикрепляет данные от своего внутреннего поставщика контента вместо внешней фотографии.
- Приложение действует как прокси-сервер, а затем получает доступ к данным другого приложения для злоумышленника. Например: почтовое приложение прикрепляет данные из приложения X, защищенные разрешением, которое обычно не позволяет злоумышленнику увидеть это конкретное вложение. Он доступен приложению, выполняющему вложение, но изначально не передает этот контент злоумышленнику.
Одним из возможных сценариев атаки является создание вредоносной галереи или средства выбора файлов, которое при использовании уязвимым приложением будет возвращать вредоносный URI.
Влияние
Последствия использования этой уязвимости варьируются в зависимости от контекста, связанного с ContentResolver. Это может привести к краже защищенных данных приложения или изменению защищенных данных неавторизованными сторонами.
Смягчения
Общий
Проверка входящих URI. Например, хорошей практикой считается использование списка разрешенных ожидаемых полномочий.
URI нацелен на поставщика неэкспортированного или защищенного контента, который принадлежит уязвимому приложению.
Проверьте, нацелен ли URI на ваше приложение:
Котлин
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)
}
Ява
boolean belongsToCurrentApplication(Context ctx, Uri uri){
String authority = uri.getAuthority();
ProviderInfo info = ctx.getPackageManager().resolveContentProvider(authority, 0);
return ctx.getPackageName().equals(info.packageName);
}
Или, если целевой поставщик экспортируется:
Котлин
fun isExported(ctx: Context, uri: Uri): Boolean {
val authority = uri.authority.toString()
val info: ProviderInfo =
ctx.packageManager.resolveContentProvider(authority, 0)!!
return info.exported
}
Ява
boolean isExported(Context ctx, Uri uri){
String authority = uri.getAuthority();
ProviderInfo info = ctx.getPackageManager().resolveContentProvider(authority, 0);
return info.exported;
}
Или, если предоставлено явное разрешение на URI — эта проверка основана на предположении, что если предоставлено явное разрешение на доступ к данным, URI не является вредоносным:
Котлин
// 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
}
Ява
// 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 нацелен на ContentProvider, защищенный разрешениями, который принадлежит другому приложению, которое доверяет уязвимому приложению.
Эта атака актуальна в следующих ситуациях:
- Экосистемы приложений, в которых приложения определяют и используют специальные разрешения или другие механизмы аутентификации.
- Атаки через прокси-сервер, когда злоумышленник злоупотребляет уязвимым приложением, имеющим разрешение времени выполнения, например READ_CONTACTS, для получения данных от поставщика системы.
Проверьте, предоставлено ли разрешение URI:
Котлин
// 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
}
Ява
// 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;
}
Если использование других поставщиков контента не требует предоставления разрешения (например, когда приложение разрешает всем приложениям из экосистемы доступ ко всем данным), тогда явно запретите использование этих полномочий.