Compte tenu du script ci-dessous, comment puis-je m'assurer que l'argument contient uniquement un nom de fichier valide à l'intérieur /home/charlesingalls/
et non un chemin ( ../home/carolineingalls/
) ou un caractère générique, etc.?
Je veux seulement que le script puisse supprimer un seul fichier du répertoire codé en dur donné. Ce script s'exécutera en tant qu'utilisateur privilégié.
#!/bin/bash
rm -f /home/charlesingalls/"$1"
/
. Les caractères génériques ne sont pas interprétés entre guillemets.-r
avecrm
.rm -r
sert à la suppression récursive d'un répertoire et de tous les fichiers et répertoires situés en dessous. Il n'est utile que lors de la suppression de répertoires. Plus généralement, ne faites pas de fret culte. c'est-à-dire ne copiez pas simplement des choses qui semblent utiles dans votre ligne de commande ou votre script sans comprendre ce qu'elles font ou comment elles fonctionnent. Les dieux de l'avion qui apportent la cargaison magique peuvent se mettre en colère et supprimer tous vos fichiers.Réponses:
Si vous voulez seulement supprimer un fichier dans
/home/charlesingalls
(et non un fichier dans un sous-répertoire), c'est facile: vérifiez simplement que l'argument ne contient pas de/
.Cela s'exécute
rm
même si l'argument est.
ou..
ou vide, mais dans ce cas, ilrm
ne parviendra pas à supprimer un répertoire sans danger.Les caractères génériques ne sont pas pertinents ici car aucune expansion de caractères génériques n'est effectuée.
Ceci est sûr même en présence de liens symboliques: si le fichier est un lien symbolique, le lien symbolique (qui est dedans
/home/charlesingalls
) est supprimé et la cible de ce lien n'est pas affectée.Notez que cela suppose qu'il
/home/charlesingalls
ne peut pas être déplacé ou modifié. Cela devrait être correct si le répertoire est codé en dur dans le script, mais s'il est déterminé à partir de variables, la détermination peut ne plus être valide au moment où larm
commande s'exécute.Sur la base des informations supplémentaires selon lesquelles l'argument est un nom d'hôte virtuel, vous devriez faire une liste blanche plutôt qu'une liste noire: vérifiez que le nom est un nom d'hôte virtuel raisonnable, plutôt que d'interdire simplement les barres obliques. Je vérifie que le nom commence par une lettre ou un chiffre minuscule et qu'il ne contient pas de caractères autres que des lettres minuscules, des chiffres, des points et des tirets.
la source
.
Cette réponse suppose qu'il
$1
est autorisé d'inclure des sous-répertoires. Si vous êtes intéressé par le cas le plus simple où$1
devrait être un simple nom de répertoire, consultez l'une des autres réponses.Les caractères génériques ne sont pas développés lorsqu'ils sont entre guillemets. Comme
$1
c'est entre guillemets, les caractères génériques ne sont pas un problème.Les deux
../
et les liens symboliques peuvent masquer l' emplacement réel d'un fichier. Ci-dessous sont des tests pour déterminer si le fichier est vraiment, pas seulement en apparence, sous le chemin que nous voulons.Systèmes plus récents: utilisation
realpath
Quant à savoir si le fichier est vraiment
/home/charlesingalls/
ou non, vous pouvez utiliserrealpath
:Ce qui précède s'exécute
exit 1
si le fichier spécifié par se$1
trouve ailleurs que sous le répertoire/home/charlesingalls/
.realpath
canonise l'ensemble du chemin, en éliminant les liens symboliques et../
.realpath
fait partie de GNU coreutils et devrait être disponible sur n'importe quel système Linux.realpath
nécessite GNU coreutils 8.15 (janvier 2012) ou mieux .Exemples
Pour montrer comment realpath suit
../
pour déterminer l'emplacement réel d'un fichier (par exemple, l'-q
option grep est omise pour que la sortie réelle de grep soit visible):Pour montrer comment il suit les liens symboliques:
Systèmes plus anciens: utilisation
readlink -e
readlink
est également capable de cononicaliser un chemin, en suivant à la fois les liens symboliques et../
:En utilisant les mêmes fichiers d'exemple:
En plus d'être disponibles sur les anciens systèmes GNU, des versions de
readlink
sont disponibles sur BSD.la source
coreutils
n'a pasrealpath
-f
("tous les composants sauf le dernier doivent exister") et les exemples utilisent-e
("tous les composants doivent exister"), ce qui est un peu déroutant.rm
agit sur l'argument lui-même, pas sur sa cible.grep -q
pour évitergrep
de produire des lignes correspondantes. Vous obtenez toujours le statut de sortie&&
et||
travaillez toujours exactement comme vous en avez l'habitude.Si vous voulez interdire complètement les chemins, le moyen le plus simple est de tester si la variable contient une barre oblique (
/
). En bash:Cela bloquera tous les chemins, y compris
foo/bar
. Vous pouvez tester à la..
place, mais cela laisserait la possibilité de liens symboliques pointant vers des répertoires en dehors du chemin cible.Si vous souhaitez uniquement autoriser la suppression d'un seul fichier, je ne pense pas que vous devriez utiliser
rm -r
.En outre, selon ce que vous faites, vous pouvez utiliser les autorisations de fichiers du système pour autoriser uniquement la suppression des fichiers que l'utilisateur peut supprimer lui-même. Quelque chose comme ça:
Bien que @Gilles ait commenté, cela a un problème de citation: il échouera s'il
$1
contient une seule citation, donc la variable doit être testée pour cela en premier (avec par exempleif [[ "$1" = *\'* ]] ; then fail...
ou plutôt en mettant en liste blanche un jeu de caractères sensible), ou le nom de fichier transmis une variable d'environnement avec par exemplela source
su
commande est interrompue car la citation est incorrecte. Vous exécutez une commande avec l'argument interpolé comme un extrait de shell. Par exemple, si l'argument est$(touch foo)
alors votre code s'exécutetouch foo
.'
par'\''
), soit la passer par un autre canal tel qu'une variable d'environnement (ce que je ferais ici:)file_to_remove="$1" su -c 'rm "/home/charlesingalls/$file_to_remove"'
. Il s'avère que le nom de fichier est censé être un nom d'hôte virtuel, donc rejeter tous les caractères spéciaux serait également correct ici.