Categoria OWASP: MASVS-STORAGE: Storage
Panoramica
La vulnerabilità Zip Path Traversal, nota anche come ZipSlip, è correlata alla gestione degli archivi compressi. In questa pagina, dimostriamo questa vulnerabilità utilizzando il formato ZIP come esempio, ma problemi simili possono verificarsi nelle librerie che gestiscono altri formati, come TAR, RAR o 7z.
Il motivo di fondo di questo problema è che all'interno degli archivi ZIP ogni file compresso viene archiviato con un nome completo, che consente caratteri speciali come barre e punti. La libreria predefinita del pacchetto java.util.zip
non controlla i nomi delle voci dell'archivio per i caratteri di attraversamento della directory (../
), pertanto è necessario prestare particolare attenzione quando si concatena il nome estratto dall'archivio con il percorso della directory di destinazione.
È molto importante convalidare eventuali snippet di codice o librerie di estrazione ZIP da fonti esterne. Molte di queste librerie sono vulnerabili agli attraversamenti di percorso Zip.
Impatto
La vulnerabilità Zip Path Traversal può essere utilizzata per ottenere la sovrascrittura arbitraria dei file. A seconda delle condizioni, l'impatto può variare, ma in molti casi questa vulnerabilità può portare a gravi problemi di sicurezza, come l'esecuzione di codice.
Mitigazioni
Per risolvere questo problema, prima di estrarre ogni voce, devi sempre verificare che il percorso di destinazione sia un elemento secondario della directory di destinazione. Il codice riportato di seguito presuppone che la directory di destinazione sia sicura, ovvero scrivibile solo dalla tua app e non sotto il controllo di un malintenzionato. In caso contrario, la tua app potrebbe essere soggetta ad altre vulnerabilità, come gli attacchi tramite link simbolici.
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;
}
Per evitare di sovrascrivere accidentalmente i file esistenti, assicurati anche che la directory di destinazione sia vuota prima di iniziare il processo di estrazione. In caso contrario, rischi potenziali arresti anomali dell'app o, in casi estremi, una compromissione dell'applicazione.
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);
…
}
}
}
Risorse
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Attraversamento del percorso