Перейти к содержанию

Возможность доступа к произвольному файлу через ContentProvider

Критичность: СРЕДНИЙ
Способ обнаружения: IAST

Описание

Уязвимость позволяет получить доступ к файлам приложения с помощью экспортируемого ContentProvider.

Уязвимость присутствует в приложениях, в которых реализация метода openFile класса производного от ContentProvider не проводит надлежащим образом проверки Uri-параметра. Вредоносное приложение может специальным образом сформировать Uri, передать его в этот ContentProvider и получить доступ к произвольному файлу.

Пример уязвимого кода:

@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
File file = new File(getContext().getFilesDir(), uri.getLastPathSegment());
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE);
}

Вредоносное приложение может использовать такой код:

Uri uri = Uri.parse("content://vuln.app.pkg.some_authority/private_internal_file");
try {
Log.d("Evil", IOUtils.toString(getContentResolver().openInputStream(uri), Charset.defaultCharset()));
} catch (Throwable th) {
Log.e("Evil", "Error was occured during openInputStream call");
throw new RuntimeException(th);
}

В результате вредоносное приложение получит доступ к файлу private_internal_file в директории уязвимого приложения (vuln.app.pkg).

Проблема

Реализация openFile(...) (а также openAssetFile(...)) в экспортируемом ContentProvider, которая формирует путь к файлу из неконтролируемого Uri без проверки, позволяет вредоносному приложению с помощью символов обхода каталога (../) или специально сформированного сегмента пути выйти за пределы предполагаемого каталога. Это даёт доступ на чтение (а при MODE_READ_WRITE — и на запись) к произвольным файлам в приватной директории приложения, что приводит к утечке конфиденциальных данных или их перезаписи.

Рекомендации

Для устранения подобных проблем в приложении необходимо убедиться в соответствии нескольким правилам.

  1. Реализовать private/in-house видимость у ContentProvider.

    Например, объявить ContentProvider внутренним:

    <provider
    android:name=".PrivateProvider"
    android:authorities="notvuln.app.pkg.some_authority"
    android:exported="false" />
    

    Чтобы оградить ContentProvider от его использования сторонними приложениями, необходимо определить permission с protectionLevel="signature" и прописать его в объявлении этого ContentProvider:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="notvuln.app.pkg">
    
        <!-- *** 1 *** Определите in-house полномочие (permission) с protectionLevel="signature" -->
        <permission
            android:name="notvuln.app.pkg.inhouseprovider.MY_PERMISSION"
            android:protectionLevel="signature" />
    
        <application
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name" >
    
            <!-- *** 2 *** Ограничьте доступ к **ContentProvider** при его объявлении с помощью in-house полномочия -->
            <!-- *** 3 *** Явно указывайте атрибут exported="true" -->
            <provider
                android:name=".InhouseProvider"
                android:authorities="notvuln.app.pkg.inhouseprovider"
                android:permission="notvuln.app.pkg.inhouseprovider.MY_PERMISSION"
                android:exported="true" />
        </application>
    </manifest>
    
  2. Если ContentProvider должен оставаться публичным для сторонних приложений, то необходимо проводить валидацию canonical-пути файла непосредственно перед его возвратом запрашивающему приложению.

    Сравнивать префикс нужно с канонизированным базовым каталогом, к которому добавлен завершающий разделитель (File.separator). Проверка вида file.getCanonicalPath().startsWith(sdcardDir) без разделителя обходится: например, для sdcardDir = "/data/files" путь /data/files_evil пройдёт проверку, хотя находится за пределами целевого каталога.

    @Override
    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
        try {
            String canonicalDir = new File(sdcardDir).getCanonicalPath();
            File file = new File(canonicalDir, uri.getLastPathSegment());
            String canonicalPath = file.getCanonicalPath();
            if (!canonicalPath.equals(canonicalDir)
                    && !canonicalPath.startsWith(canonicalDir + File.separator)) {
                throw new IllegalArgumentException();
            }
            return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
        } catch (IOException e) {
            throw new FileNotFoundException();
        }
    }
    

Ссылки

  1. Path traversal
  2. Основы ContentProvider
  3. Безопасность ContentProvider
К началу