
Documente CVE-2026-65320, une traversée de chemin de type tar-slip dans untar_dir() de fastcore, avec une preuve de concept inoffensive démontrant l'écriture arbitraire de fichiers et l'exécution de code.
untar_dir()Sévérité : Critique, CVSS 3.1 9.8
Vecteur (v3.1) : CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Affecté : fastcore <= 2.2.30
Corrigé dans : Non corrigé au moment de la rédaction
CWE : CWE-22 (Limitation incorrecte d'un nom de chemin à un répertoire restreint, « Path Traversal »)
Composant : fastcore.xtras.untar_dir() → fastcore.xtras._unpack() → shutil.unpack_archive()
Runtime à haut risque : Python < 3.14 (Python 3.14 fait du filtre tar data sûr le comportement par défaut)
Signalé par : Rahul Karne
CNA : VulnCheck
fastcore.xtras.untar_dir(fname, dest) est un utilitaire documenté qui extrait une archive « dans dest ».
En interne, il transmet l'archive à shutil.unpack_archive() de la bibliothèque standard sans filtre d'extraction, sans validation des chemins des membres de l'archive et sans aucune vérification de confinement après extraction.
Par conséquent, une archive TAR dont les membres contiennent des séquences de traversée ../ ou des chemins absolus peut écrire des fichiers en dehors du répertoire dans lequel l'appelant a demandé à fastcore d'extraire.
Un appelant qui s'attend raisonnablement à ce que untar_dir(fname, dest) confine tout à dest obtient à la place une écriture de fichier arbitraire partout où le processus peut écrire.
Écriture de fichier arbitraire sous l'autorité du processus appelant untar_dir().
Selon ce qui est écrasé, cela peut entraîner :
La preuve de concept démontre la chaîne complète écriture → import → exécution de code de bout en bout, entièrement à l'intérieur d'un répertoire de démonstration inoffensif.
fastcore est une dépendance fondamentale dans tout l'écosystème fast.ai, notamment :
fastainbdevghapiIl reçoit des dizaines de millions de téléchargements par mois.
untar_dir() est la primitive d'extraction derrière le flux de travail courant « télécharger une archive, puis la décompresser », qui suit le même schéma général que untar_data de fastai.
Tout chemin de code qui fournit à untar_dir() une archive dont le contenu n'est pas entièrement fiable est exposé.
Le code vulnérable se trouve dans fastcore/xtras.py :
def _unpack(fname, out):
import shutil
shutil.unpack_archive(str(fname), str(out)) # <-- no filter, no validation
ls = out.ls()
return ls[0] if len(ls) == 1 else out
def untar_dir(fname, dest, rename=False, overwrite=False, uid=-1, gid=-1):
"untar `file` into `dest` ..."
import tempfile, shutil
dest = Path(dest)
with tempfile.TemporaryDirectory() as d:
out = Path(d) / remove_suffix(Path(fname).stem, '.tar')
out.mkdir()
...
src = _unpack(fname, out) # extraction escapes `out` here
...
shutil.move(str(src), dest)
...
return dest
untar_dir() crée un répertoire d'extraction temporaire interne :
<tempdir>/<random>/<archive-stem>/
Il appelle ensuite _unpack(), qui transmet directement l'archive à :
shutil.unpack_archive()
Pour les archives TAR, cela appelle finalement tarfile.extractall().
Sur Python < 3.14, le comportement d'extraction par défaut est fully_trusted, ce qui signifie que les membres de traversée peuvent être honorés.
Par exemple, un membre d'archive tel que :
../../../some/other/dir/file
est écrit relativement au répertoire d'extraction et peut donc s'en échapper.
Rien dans fastcore :
filter="data" là où c'est pris en charge....untar_dir() traite shutil.unpack_archive() comme s'il s'agissait d'un extracteur d'archive sûr et isolé.
Ce n'est pas le cas.
La documentation de la bibliothèque standard de Python avertit que l'extraction d'archives non fiables peut créer des fichiers en dehors de la destination demandée et recommande un filtrage d'extraction sûr pour les archives TAR.
fastcore ne passe ni filtre d'extraction approprié ni n'implémente de garde de confinement équivalente pour les versions de Python qu'il prend en charge.
Il n'avertit pas non plus les appelants de untar_dir() de ce comportement sensible à la sécurité.
Les membres TAR peuvent contenir des chaînes de chemin arbitraires.
Sur les versions de Python affectées, tarfile.extractall() ne neutralise pas automatiquement les séquences de traversée .. par défaut.
Écrire un membre de traversée relativement au répertoire d'extraction temporaire de fastcore est donc suffisant pour s'échapper de ce répertoire et écrire partout où le processus a la permission d'écrire.
Une primitive fiable est la traversée relative telle que :
../../../target/file
C'est la technique utilisée par la preuve de concept.
L'entrée dangereuse est le contenu de l'archive, et non le chemin de destination choisi par le développeur.
Les archives sont fréquemment obtenues depuis des emplacements externes ou semi-fiables, notamment :
Tout service ou outil qui télécharge une archive depuis une source non fiable ou semi-fiable et la traite ensuite à l'aide de untar_dir() peut donc être atteignable.
Le PoC inclut également une variante qui sert l'archive forgée depuis un point de terminaison HTTP contrôlé par l'attaquant pour démontrer ce chemin de livraison.
Une exploitation réussie nécessite :
Que l'application appelle fastcore.xtras.untar_dir() sur une archive dont le contenu est influencé par l'attaquant, telle qu'une archive téléversée, téléchargée ou autrement non fiable.
Que le runtime utilise Python < 3.14, ou une autre configuration où un filtre d'extraction TAR sûr n'est pas appliqué.
Pour l'escalade d'une écriture de fichier arbitraire vers une exécution de code, que le processus importe ou charge ultérieurement un fichier depuis un emplacement atteignable par la traversée, tel que :
L'écriture de fichier arbitraire et la modification de configuration/données ne nécessitent que les conditions 1 et 2.
poc_fastcore_cna_demo.py invoque l'API réelle fastcore.xtras.untar_dir().
La démonstration est intentionnellement inoffensive.
Chaque fichier créé par le PoC reste à l'intérieur d'un unique répertoire de démonstration spécifié à l'aide de :
--demo-root
Le PoC ne modifie pas de fichiers système réels et n'accède pas à des services cloud ni à des identifiants.