Path/directory traversal
Описание
Уязвимость Path Traversal возникает, когда злоумышленник контролирует часть пути, которая затем передается API-интерфейсам файловой системы без проверки. Это может привести к несанкционированным операциям с файловой системой. Например, злоумышленник может использовать специальные символы, такие как ../
, чтобы выйти за пределы целевого каталога.
Реализации openFile(...)
и openAssetFile(...)
в экспортированных ContentProviders могут быть уязвимы, если они должным образом не проверяют входящие параметры uri
. Вредоносное приложение может предоставить созданный uri (например, тот, который содержит /../
), чтобы заставить ваше приложение вернуть ParcelFileDescriptor
для файла за пределами предполагаемого каталога, тем самым позволяя вредоносному приложению получить доступ к любому файлу.
Примеры «небезопасных» (отсутствие проверок uri) реализаций методов openFile(...)
и openAssetFile(...)
.
public ParcelFileDescriptor openFile(Uri uri, String mode) throws RemoteException, FileNotFoundException {
return provider.openFile(uri, mode);
}
public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode)
throws FileNotFoundException {
ParcelFileDescriptor fd = openFile(uri, mode);
return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
}
Также в Android-экосистеме можно встретиться с уязвимостью Zip Path Traversal, также известной как ZipSlip, которая связана с обработкой сжатых архивов. Чуть ниже будет продемонстрирована данная уязвимость на примере формата ZIP, но аналогичные проблемы могут возникнуть и в библиотеках, обрабатывающих другие форматы, такие как TAR, RAR или 7z.
Проблема в том, что внутри ZIP-архивов каждый упакованный файл хранится с полным именем, которое допускает использование специальных символов, таких как косая черта и точки. Библиотека по умолчанию из пакета java.util.zip не проверяет имена записей архива на наличие символов обхода каталога (../
), поэтому необходимо соблюдать особую осторожность при объединении имени, извлеченного из архива, с целевым путем к каталогу.
Очень важно проверять любые фрагменты кода и библиотеки из внешних источников, связанные с работой с ZIP-архивами — многие из них уязвимы для Zip Path Traversals.
Влияние
Результат атаки зависит от операции и содержимого файла, но обычно приводит к перезаписи файла (при записи файлов), утечке данных (при чтении файлов) или изменению прав доступа (при изменении разрешений файла/каталога).
Уязвимость Zip Path Traversal может использоваться для произвольной перезаписи файла. В зависимости от условий последствия могут различаться, но во многих случаях эта уязвимость может привести к серьезным проблемам с безопасностью, например к выполнению кода.
Рекомендации
-
Получите канонический путь с помощью
File.getCanonicalPath()
и сравните префикс с ожидаемым каталогом. Один файл, существующий в системе, может иметь много разных путей к нему, но только один канонический путь. Канонический путь — это уникальный абсолютный путь для данного файла.public File saferOpenFile(String path, String expectedDir) throws IllegalArgumentException { File f = new File(path); String canonicalPath = f.getCanonicalPath(); if (!canonicalPath.startsWith(expectedDir)) { throw new IllegalArgumentException(); } return f; }
Дополнительные проверки:
- Проверьте, существует ли уже файл, чтобы предотвратить случайную перезапись.
- Проверьте, является ли целевой файл ожидаемой целью, чтобы предотвратить утечку данных или неправильное изменение разрешений.
- Проверьте, является ли текущий каталог операции точно таким, как ожидается в возвращаемом значении из канонического пути.
- Обеспечьте, чтобы система разрешений была явно привязана к операции (например, проверка того, что она не запускает службы от имени пользователя root) и разрешения каталога были ограничены указанной службой или командой.
-
Сделайте ваш
ContentProvider android:exported = false
, если это возможно. -
Установите в ContentProvider атрибут
android:permission
как разрешение сandroid:protectionLevel="signature"
, чтобы запретить приложениям, написанным другими разработчиками, отправлять намерения ContentProvider. -
Перед извлечением данных из архива всегда следует проверять, что целевой путь является дочерним по отношению к целевому каталогу. В приведенном ниже коде предполагается, что целевой каталог безопасен — он доступен для записи только вашему приложению и не находится под контролем злоумышленника.
public static File newFile(File targetPath, ZipEntry zipEntry) throws IOException { String name = zipEntry.getName(); File f = new File(targetPath, name); String canonicalPath = f.getCanonicalPath(); if (!canonicalPath.startsWith(targetPath.getCanonicalPath() + File.separator)) { throw new ZipException("Illegal name: " + name); } return f; }
-
Перед началом процесса извлечения данных из архива убедитесь, что каталог назначения пуст. Чтобы избежать случайной перезаписи существующих файлов, необходимо перед началом извлечения убедиться, что каталог назначения пуст, в противном случае вы рискуете потенциальными сбоями или в крайних случаях компрометацией приложения.
void unzip(final InputStream inputStream, File destinationDir) throws IOException { if(!destinationDir.isDirectory()) { throw IOException("Destination is not a directory."); } String[] files = destinationDir.list(); if(files != null && files.length != 0) { throw IOException("Destination directory is not empty."); } try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) { ZipEntry zipEntry; while ((zipEntry = zipInputStream.getNextEntry()) != null) { final File targetFile = new File(destinationDir, zipEntry); … } } }