Обход почтового пути

Категория OWASP: MASVS-STORAGE: Хранилище

Обзор

Уязвимость Zip Path Traversal, также известная как ZipSlip, связана с обработкой сжатых архивов. На этой странице мы демонстрируем эту уязвимость на примере формата ZIP, но аналогичные проблемы могут возникнуть в библиотеках, обрабатывающих другие форматы, такие как TAR, RAR или 7z.

Основная причина этой проблемы заключается в том, что внутри ZIP-архивов каждый упакованный файл хранится с полным именем, которое допускает специальные символы, такие как косые черты и точки. Библиотека по умолчанию из пакета java.util.zip не проверяет имена элементов архива на наличие символов обхода каталогов ( ../ ), поэтому следует проявлять особую осторожность при объединении имени, извлеченного из архива, с целевым путем к каталогу.

Крайне важно проверять на достоверность любые фрагменты кода или библиотеки для извлечения ZIP-архивов из внешних источников. Многие такие библиотеки уязвимы для обхода путей ZIP-архивов.

Влияние

Уязвимость ZIP Path Traversal может быть использована для произвольной перезаписи файлов. В зависимости от условий последствия могут различаться, но во многих случаях эта уязвимость может привести к серьезным проблемам безопасности, таким как выполнение кода.

Меры по смягчению последствий

Чтобы избежать этой проблемы, перед извлечением каждой записи всегда следует проверять, является ли целевой путь дочерним по отношению к целевому каталогу. Приведенный ниже код предполагает, что целевой каталог безопасен — доступен для записи только вашему приложению и не находится под контролем злоумышленника — в противном случае ваше приложение может быть подвержено другим уязвимостям, таким как атаки с использованием символических ссылок.

Котлин

companion object {
    @Throws(IOException::class)
    fun newFile(targetPath: File, zipEntry: ZipEntry): File {
        val name: String = zipEntry.name
        val f = File(targetPath, name)
        val canonicalPath = f.canonicalPath
        if (!canonicalPath.startsWith(
                targetPath.canonicalPath + File.separator)) {
            throw ZipException("Illegal name: $name")
        }
        return f
    }
}

Java

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;
 }

Чтобы избежать случайной перезаписи существующих файлов, перед началом процесса извлечения убедитесь, что целевой каталог пуст. В противном случае вы рискуете столкнуться со сбоями приложения или, в крайних случаях, с его компрометацией.

Котлин

@Throws(IOException::class)
fun unzip(inputStream: InputStream?, destinationDir: File) {
    if (!destinationDir.isDirectory) {
        throw IOException("Destination is not a directory.")
    }
    val files = destinationDir.list()
    if (files != null && files.isNotEmpty()) {
        throw IOException("Destination directory is not empty.")
    }
    ZipInputStream(inputStream).use { zipInputStream ->
        var zipEntry: ZipEntry
        while (zipInputStream.nextEntry.also { zipEntry = it } != null) {
            val targetFile = File(destinationDir, zipEntry.name)
            // ...
        }
    }
}

Java

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);
        
    }
  }
}

Ресурсы

{% verbatim %} {% endverbatim %}
  • Примечание: текст ссылки отображается, когда JavaScript отключен.
  • Обход пути
{% verbatim %}
{% endverbatim %}