Устранение уязвимости обхода Zip-пути в Android

Я загрузил свое приложение в Google Play Store, и Google предупредил, что это «Безопасность Android».

В приложении мы загрузили папку Zip и сохранили эту папку Zip во внутреннем хранилище, а затем разархивировали эту папку во внутреннем хранилище устройства.

вот код папки UnZip:

public static void doUnzip(String inputZipFile, String 
   destinationDirectory, ZipProgressListener zipProgressListener) throws 
    IOException, RuntimeException {

 Log.e(TAG, "doUnzip:inputZipFile: " + inputZipFile);
 Log.e(TAG, "doUnzip:destinationDirectory: " + destinationDirectory);

int BUFFER = 6 * 1024;
List zipFiles = new ArrayList();
  File sourceZipFile = FileUtils.createValidFile(inputZipFile);
   File unzipDestinationDirectory = 
  FileUtils.createValidFile(destinationDirectory);
unzipDestinationDirectory.mkdir();
   String newPath = unzipDestinationDirectory.getAbsolutePath() + 
 File.separator + 
     FileUtils.getFileNameWithoutExtension(sourceZipFile.getName());
  new File(newPath).mkdir();

 ZipFile zipFile;
// Open Zip file for reading
zipFile = new ZipFile(sourceZipFile, ZipFile.OPEN_READ);
int entries = zipFile.size();
int total = 0;
Log.e(TAG, "doUnzip: entries Found !!" + entries);

// Create an enumeration of the entries in the zip file
Enumeration zipFileEntries = zipFile.entries();

if (zipProgressListener != null) {
    zipProgressListener.onZipStart();
}

// Process each entry
while (zipFileEntries.hasMoreElements()) {
    // grab a zip file entry
    ZipEntry entry = (ZipEntry) zipFileEntries.nextElement();

    String currentEntry = entry.getName();
    Log.i(TAG, "[doUnzip] " + currentEntry);

    File file = new File(newPath);

    File destFile = new File(newPath, currentEntry);
    Log.i(TAG, "doUnzip getCanonicalPath : " + 
destFile.getCanonicalPath());

    if (Build.VERSION.SDK_INT <= VERSION_CODES.LOLLIPOP) {
        Log.i(TAG, "doUnzip: LOLLIPOP");
        if 
  (!destFile.getCanonicalPath().startsWith(destinationDirectory)) {
            throw new RuntimeException(destFile.getCanonicalPath() + 
  " is outside of targetDirectory: " + destinationDirectory);
        }
    } else {
        Log.i(TAG, "doUnzip: Above ");
        if(!destFile.getCanonicalPath().contains(file.getName()) && 
    !destFile.getCanonicalPath().contains("/")){
            throw new RuntimeException(destFile.getCanonicalPath() + 
     " is outside of targetDirectory: " + destinationDirectory);
          }
      }

      if (currentEntry.endsWith(".zip")) {
        zipFiles.add(destFile.getAbsolutePath());
    }

    // grab file's parent directory structure
    File destinationParent = destFile.getParentFile();
    // create the parent directory structure if needed
    destinationParent.mkdirs();

       try {
        // extract file if not a directory
        if (!entry.isDirectory()) {
            BufferedInputStream is = new 
       BufferedInputStream(zipFile.getInputStream(entry));
            int currentByte;
            // establish buffer for writing file
            byte data[] = new byte[BUFFER];

            // write the current file to disk
            FileOutputStream fos = new FileOutputStream(destFile);
            BufferedOutputStream dest = new BufferedOutputStream(fos, 
        BUFFER);

            // read and write until last byte is encountered
            while ((currentByte = is.read(data, 0, BUFFER)) != -1) {
                dest.write(data, 0, currentByte);
            }

            Log.e(TAG, "unzip:outPath: =>" + 
        destFile.getAbsolutePath() + "\nFile size: " + destFile.length() 
         / 1024);
            dest.flush();
            dest.close();
            is.close();
        }

        int progress = 0;
        if (zipProgressListener != null) {
            progress = (total++ * 100 / entries);
            zipProgressListener.onZipProgressUpdate(progress);
        }
        Log.e(TAG, "unzip: PROGRESS::" + progress);
      } catch (IOException ioe) {
        ioe.printStackTrace();
      }
       }
       zipFile.close();

      for (Object zipFile1 : zipFiles) {
         String zipName = (String) zipFile1;
         Log.i(TAG, "doUnzip: ");
         doUnzip(zipName, destinationDirectory + File.separator + 
         zipName.substring(0, zipName.lastIndexOf(".zip")),
            zipProgressListener);
     }

   if (zipProgressListener != null) {
    Log.i(TAG, "doUnzip: " + sourceZipFile.getName());

    zipProgressListener.onZipCompleted(destinationDirectory +
            File.separatorChar + sourceZipFile.getName().substring(0, 
  sourceZipFile.getName().lastIndexOf(".zip")));
  }

   }

Вот предупреждение Google:

Эта информация предназначена для разработчиков приложений, содержащих небезопасные шаблоны распаковки, которые потенциально могут привести к атаке Zip Path Traversal. Расположение уязвимых классов приложений, содержащих небезопасные шаблоны распаковки, можно найти в уведомлении Play Console для вашего приложения.

Дополнительные детали

Zip-файлы могут содержать запись (файл или каталог) с символами обхода пути («../») в имени. Если разработчики разархивируют такие записи zip-файлов без проверки их имени, это потенциально может вызвать атаку с обходом пути, что приведет к записи в произвольные каталоги или даже к перезаписи файлов в личных папках приложения.

Мы рекомендуем исправить эту проблему в вашем приложении, проверив, находятся ли канонические пути к разархивированным файлам в ожидаемом каталоге. В частности, перед использованием объекта File, созданного с использованием возвращаемого значения метода ZipEntry getName(), всегда проверяйте, принадлежит ли возвращаемое значение File.GetCanonicalPath() предполагаемому пути к каталогу. Например:

InputStream is = new InputStream(untrustedFileName);
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(is));
while((ZipEntry ze = zis.getNextEntry()) != null) {
  File f = new File(DIR, ze.getName());
  String canonicalPath = f.getCanonicalPath();
  if (!canonicalPath.startsWith(DIR)) {
    // SecurityException
  }
  // Finish unzipping…
}

Как я могу решить это предупреждение в выше Android OS-6?


person axita.savani    schedule 25.05.2019    source источник
comment
Мой ZipUtils должен справиться с этим.   -  person CommonsWare    schedule 25.05.2019
comment
Да, я думаю, это работает для меня, я попробую.   -  person axita.savani    schedule 28.05.2019


Ответы (1)


Проверьте уязвимость, как это

InputStream is = new InputStream(untrustedFileName);
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(is));
while((ZipEntry ze = zis.getNextEntry()) != null) {
    File outputFile = new File(outputDir, ze.getName());
    try {
        ensureZipPathSafety(outputFile, outputDir);
    } catch (Exception e) {
        e.printStackTrace();
        return;
    }
    // Finish unzipping…
}


private void ensureZipPathSafety(final File outputFile, final String destDirectory) throws Exception {
    String destDirCanonicalPath = (new File(destDirectory)).getCanonicalPath();
    String outputFilecanonicalPath = outputFile.getCanonicalPath();
    if (!outputFileCanonicalPath.startsWith(destDirCanonicalPath)) {
        throw new Exception(String.format("Found Zip Path Traversal Vulnerability with %s", canonicalPath));
    }
}
person Indra Kumar S    schedule 31.08.2019
comment
Спасибо за решение. Я уже сделал то же, что и ваш ответ - @Indra Kumar S. - person axita.savani; 31.08.2019
comment
@ Индра Кумар, ты имеешь в виду! мы не можем заархивировать файл в /data/data/mypackage/mydir/ ??? - person Attaullah; 23.01.2020