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 alla base 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 tutti gli snippet di codice o le librerie di estrazione ZIP da fonti esterne. Molte di queste librerie sono vulnerabili a Zip Path Traversal.
Impatto
La vulnerabilità Zip Path Traversal può essere utilizzata per sovrascrivere file arbitrari. 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 mitigare questo problema, prima di estrarre ogni voce, devi sempre verificare che il percorso di destinazione sia un figlio della directory di destinazione. Il codice riportato di seguito presuppone che la directory di destinazione sia sicura, scrivibile solo dalla tua app e non sotto il controllo dell'aggressore, altrimenti la tua app potrebbe essere soggetta ad altre vulnerabilità come gli attacchi di symlink.
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, devi anche assicurarti che la directory di destinazione sia vuota prima di avviare 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
- Path traversal