Категория OWASP: MASVS-PLATFORM: Взаимодействие платформ
Обзор
Согласно документации , ContentResolver — это «класс, предоставляющий приложениям доступ к модели контента» . ContentResolver предоставляет методы для взаимодействия, получения или изменения контента, предоставляемого следующими источниками:
- Установленные приложения (схема URI
content://) - Файловые системы (URI-схема
file://) - Поддержка API, предоставляемых Android (схема URI
android.resource://).
В заключение, уязвимости, связанные с ContentResolver относятся к категории «запутанных посредников» , поскольку злоумышленник может использовать привилегии уязвимого приложения для доступа к защищенному контенту.
Риск: Злоупотребление на основе ненадежного URI file://
Использование уязвимости file:// в URI класса ContentResolver позволяет злоупотреблять возможностью ContentResolver возвращать дескрипторы файлов, описываемые этим URI. Эта уязвимость затрагивает такие функции, как openFile() , openFileDescriptor() , openInputStream() , openOutputStream() или openAssetFileDescriptor() из API ContentResolver . Уязвимость может быть использована для принудительного доступа приложения к файлам, которые не предназначены для доступа, например, к внутренним базам данных или общим настройкам, с помощью полностью или частично контролируемого злоумышленником URI file://
Один из возможных сценариев атаки — создание вредоносной галереи или средства выбора файлов, которое при использовании уязвимым приложением будет возвращать вредоносный URI.
Существует несколько вариантов этой атаки:
- Полностью контролируемый злоумышленником URI
file://, указывающий на внутренние файлы приложения. - Часть URI
file://контролируется злоумышленником, что делает его уязвимым для обхода пути. - URI
file://нацелен на контролируемую злоумышленником символическую ссылку (symlink), указывающую на внутренние файлы приложения. - Аналогично предыдущему варианту, но здесь злоумышленник многократно меняет целевой объект символической ссылки с легитимного объекта на внутренние файлы приложения. Цель состоит в том, чтобы использовать состояние гонки между потенциальной проверкой безопасности и использованием пути к файлу.
Влияние
Последствия использования этой уязвимости различаются в зависимости от того, для чего используется 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.
}
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.
}
Риск: Злоупотребление на основе ненадежного содержимого://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)
}
Java
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
}
Java
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
}
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 нацелен на 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
}
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;
}
Если использование ресурсов других поставщиков контента не требует предоставления разрешений — например, когда приложение разрешает всем приложениям из экосистемы доступ ко всем данным, — то следует явно запретить использование таких разрешений.