Protection de la commande shell avec une variable chaîne

9

Dans un langage de programmation, j'exécute une simple commande shell

cd var; echo > create_a_file_here

avec var étant une variable qui contient une chaîne (espérons-le) d'un répertoire à l'endroit où je veux créer le fichier "create_a_file_here". Maintenant, si quelqu'un voit cette ligne de code, il est possible de l'exploiter en affectant par exemple:

var = "; rm -rf /"

Les choses peuvent devenir assez laides. Une façon d'éviter le cas ci-dessus serait peut-être de rechercher dans la chaîne dans var des caractères spéciaux comme ';' avant d'exécuter la commande shell, mais je doute que cela couvre tous les exploits possibles.

Est-ce que quelqu'un connaît un bon moyen de s'assurer que "cd var" ne change qu'un répertoire et rien d'autre?

ES___
la source
4
Selon la façon dont vous pouvez invoquer le shell, vous pouvez également passer varcomme argument. Par exemple , appeler shavec des arguments -c, 'cd "$1"; echo > create_a_file_here', 'sh', vartravaux et n'a pas besoin de changements à var. L' 'sh'argument est transmis sous la forme $0.
ipsec
1
Quel langage de programmation? Utilisez-vous POSIX shou créez-vous votre propre langage de programmation avec une syntaxe similaire mais qui se développe varau lieu de vous obliger à écrire cd "$var"? Ou est-ce bashavec shopt -s cdable_vars? Oh, je pense que vous voulez dire qu'un autre programme bifurque un shell pour exécuter ces commandes. Alors citez juste var, mais assurez-vous qu'il n'inclut pas de caractère de citation lui-même ...
Peter Cordes
@PeterCordes Si vous parlez de Bash, une variable entre guillemets doubles qui contient un guillemet double est très bien. par exemple des s='"'; echo "$s"impressions ".
wjandrea
@WJAndrea: oui, mais il n'y a aucune citation "d'atout" qui ne peut pas être vaincue lors de la construction d'une affectation de variable à partir d'une entrée non fiable. Oh, solution: faites var=untrusted stringdans le programme parent, tout varcomme une variable d'environnement qui est déjà définie lors de l'appel sh. Ensuite, il vous suffit de le citer chaque fois que vous le développez, ce qui est possible de manière fiable. Ah, je vois que cette idée fait déjà partie de la réponse de Stéphane>. <
Peter Cordes

Réponses:

9

Si je comprends bien, varc'est une variable dans votre langage de programmation.

Et dans votre langage de programmation, vous demandez à un shell d'interpréter une chaîne qui est la concaténation de "cd ", le contenu de cette variable et "; echo > create_a_file_here".

Si alors, oui, si le contenu de varn'est pas étroitement contrôlé, c'est une vulnérabilité d'injection de commande.

Vous pouvez essayer de citer correctement le contenu de la variable¹ dans la syntaxe du shell afin qu'il soit garanti d'être passé comme un seul argument au cdbuiltin.

Une autre approche consisterait à transmettre le contenu de cette variable d'une autre manière. Un moyen évident serait de passer cela dans une variable d'environnement. Par exemple, en C:

char *var =  "; rm -rf /";
setenv("DIR", var, 1);
system("CDPATH= cd -P -- \"$DIR\" && echo something > create_a_file_here");

Cette fois, le code que vous demandez au shell d'interpréter est fixe, nous devons toujours l'écrire correctement dans la syntaxe du shell (ici supposé être un shell compatible POSIX):

  • l'expansion des variables du shell doit être indiquée pour éviter le split + glob
  • vous avez besoin -Ppour cdfaire simplechdir()
  • vous devez --marquer la fin des options pour éviter les problèmes de vardémarrage -(ou +dans certains shells)
  • Nous avons défini CDPATHla chaîne vide au cas où elle se trouverait dans l'environnement
  • Nous n'exécutons la echocommande qu'en cas de cdsuccès.

Il y a (au moins) un problème restant: s'il l' varest -, il ne se place pas dans le répertoire appelé -mais dans le répertoire précédent (tel que stocké dans $OLDPWD) et il OLDPWD=- CDPATH= cd -P -- "$DIR"n'est pas garanti de le contourner. Vous auriez donc besoin de quelque chose comme:

system(
  "case $DIR in\n"
  " (-) CDPATH= cd -P ./-;;\n"
  " (*) CDPATH= cd -P -- \"$DIR\";;\n"
  "esac && ....");

¹ Notez que faire un system(concat("cd \"", var, "\"; echo..."));n'est pas la voie à suivre, vous ne feriez que déplacer le problème.

Par exemple, un var = "$(rm -rf /)"serait toujours un problème.

Le seul moyen fiable de citer du texte pour des shells de type Bourne est d'utiliser des guillemets simples et de prendre également en charge les guillemets simples qui peuvent apparaître dans la chaîne. Par exemple, tournez a char *var = "ab'cd"à char *escaped_var = "'ab'\\''cd'". C'est, remplacer tout 'à '\''et envelopper le tout à l' intérieur '...'.

Cela suppose encore que cette chaîne entre guillemets n'est pas utilisé dans les apostrophes inverses, et vous auriez encore besoin de la --, -P, &&, CDPATH=...

Stéphane Chazelas
la source
12

Solution simple: n'appelez pas le shell depuis votre programme. Du tout.

Votre exemple ici est trivial, changer le répertoire et créer des fichiers devrait être facile dans n'importe quel langage de programmation. Mais même si vous avez besoin d'exécuter une commande externe, il n'est généralement pas nécessaire de le faire via le shell.

Ainsi, par exemple en Python, au lieu d'exécuter os.system("somecmd " + somearg), utilisez subprocess.run(["somecmd", somearg]). En C, au lieu de system(), utilisez fork()et exec()(ou trouvez une bibliothèque qui le fait).

Si vous devez utiliser le shell, citez les arguments de la ligne de commande ou passez-les dans l'environnement, comme dans la réponse de Stéphane . De plus, si vous vous inquiétez des caractères spéciaux, la bonne solution consiste à ne pas filtrer (liste noire) les caractères potentiellement dangereux, mais à ne garder que les caractères connus pour être sûrs (liste blanche).

N'autorisez que les personnages dont vous connaissez les fonctions, de cette façon il y a moins de risque de manquer quelque chose. Le résultat final pourrait être que vous décidez seulement d'autoriser [a-zA-Z0-9_], mais cela pourrait suffire pour faire le travail. Vous pouvez également vérifier que votre environnement local et votre jeu d'outils n'incluent pas de lettres accentuées comme äet ödans cela. Ils ne sont probablement pas considérés comme spéciaux par aucun shell, mais encore une fois, il vaut mieux être sûr qu'ils passent ou non.

ilkkachu
la source
1
Et assurez-vous que tout ce que vous utilisez pour faire la comparaison [a-zA-Z0-9]n'inclut pas des choses comme àdont le codage pourrait également être mal interprété par certains shells (comme bash) dans certains paramètres régionaux.
Stéphane Chazelas
@ StéphaneChazelas (par intérêt :) sont-ils (et juste en dessous des localités affectées?)
Wilf
10

Dans un langage de programmation, il devrait y avoir de meilleures façons de faire que d'exécuter des commandes shell. Par exemple, le remplacement cd varpar l'équivalent de votre langage de programmation chdir (var);devrait garantir que toute supercherie avec la valeur de varse traduit uniquement par une erreur "Répertoire introuvable" plutôt que par des actions involontaires et éventuellement malveillantes.

Vous pouvez également utiliser des chemins absolus plutôt que de changer de répertoire. Concaténez simplement le nom du répertoire, la barre oblique et le nom de fichier que vous souhaitez utiliser.

En C, je pourrais faire quelque chose comme:

char filepath[PATH_MAX];  /* alternative constant: MAXPATHLEN */

/* Join directory name in var and the filename, guarding against exceeding PATH_MAX */
snprintf (filepath, PATH_MAX, "%s/%s", var, "create_a_file_here");

/* create an empty file/truncate an existing one */
fclose (fopen (filepath, "w") );

Votre langage de programmation peut sûrement faire quelque chose de similaire?

telcoM
la source
Merci pour votre réponse, mais malheureusement je dois utiliser la solution de contournement avec les commandes bash. J'ai testé en citant la variable - comme l'a suggéré muru - et cela semble fonctionner!
ES___