Kategoria OWASP: MASVS-STORAGE: Storage
Omówienie
Luka w zabezpieczeniach dotycząca przemierzania ścieżki ZIP, znana też jako ZipSlip, jest związana z obsługą skompresowanych archiwów. Na tej stronie pokazujemy tę lukę w zabezpieczeniach na przykładzie formatu ZIP, ale podobne problemy mogą występować w bibliotekach obsługujących inne formaty, takie jak TAR, RAR czy 7z.
Przyczyną tego problemu jest to, że w archiwach ZIP każdy spakowany plik jest przechowywany z pełną nazwą, która może zawierać znaki specjalne, takie jak ukośniki i kropki. Domyślna biblioteka z pakietu java.util.zip
nie sprawdza, czy nazwy wpisów w archiwum zawierają znaki przemierzania katalogu (../
), dlatego podczas łączenia nazwy wyodrębnionej z archiwum ze ścieżką katalogu docelowego należy zachować szczególną ostrożność.
Bardzo ważne jest, aby sprawdzać wszystkie fragmenty kodu lub biblioteki do wyodrębniania plików ZIP pochodzące ze źródeł zewnętrznych. Wiele takich bibliotek jest podatnych na ataki typu Zip Path Traversal.
Wpływ
Luka w zabezpieczeniach dotycząca przemierzania ścieżki ZIP może zostać wykorzystana do zastąpienia dowolnego pliku. W zależności od warunków wpływ może być różny, ale w wielu przypadkach ta luka w zabezpieczeniach może prowadzić do poważnych problemów z bezpieczeństwem, takich jak wykonanie kodu.
Środki ograniczające ryzyko
Aby złagodzić ten problem, przed wyodrębnieniem każdego wpisu zawsze sprawdzaj, czy ścieżka docelowa jest elementem podrzędnym katalogu docelowego. Poniższy kod zakłada, że katalog docelowy jest bezpieczny – można w nim zapisywać dane tylko za pomocą aplikacji i nie jest on kontrolowany przez atakującego. W przeciwnym razie aplikacja może być podatna na inne luki w zabezpieczeniach, takie jak ataki z użyciem dowiązań symbolicznych.
Kotlin
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;
}
Aby uniknąć przypadkowego zastąpienia istniejących plików, przed rozpoczęciem procesu wyodrębniania upewnij się, że katalog docelowy jest pusty. W przeciwnym razie ryzykujesz potencjalne awarie aplikacji lub, w ekstremalnych przypadkach, naruszenie bezpieczeństwa aplikacji.
Kotlin
@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);
…
}
}
}
Materiały
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony.
- Przechodzenie ścieżki