Conséquences pour la sécurité de l'exécution de perl -ne '…' *

27

Apparemment, en cours d'exécution:

perl -n -e 'some perl code' *

Ou

find . ... -exec perl -n -e '...' {} +

(idem avec -pau lieu de -n)

Ou

perl -e 'some code using <>' *

souvent trouvé dans les lignes simples affichées sur ce site, a des implications en matière de sécurité. Quel est le problème? Comment l'éviter?

Stéphane Chazelas
la source

Réponses:

33

Quel est le problème

Tout d'abord, comme pour de nombreux utilitaires, vous aurez un problème avec les noms de fichiers commençant par -. Pendant:

sh -c 'inline sh script here' other args

Les autres arguments sont passés au inline sh script; avec l' perléquivalent,

perl -e 'inline perl script here' other args

Les autres arguments sont analysés pour plus d'options pour perl en premier, pas pour le script en ligne. Ainsi, par exemple, s'il y a un fichier appelé -eBEGIN{do something evil}dans le répertoire courant,

perl -ne 'inline perl script here;' *

(avec ou sans -n) fera quelque chose de mal.

Comme pour les autres utilitaires, la solution consiste à utiliser le marqueur de fin d'options ( --):

perl -ne 'inline perl script here;' -- *

Mais même dans ce cas, c'est toujours dangereux et cela dépend de l' <>opérateur utilisé par -n/ -p.

Le problème est expliqué dans la perldoc perlopdocumentation.

Cet opérateur spécial est utilisé pour lire une ligne (un enregistrement, les enregistrements étant des lignes par défaut) d'entrée, où cette entrée provient de chacun des arguments transmis à son tour @ARGV.

Dans:

perl -pe '' a b

-pimplique une while (<>)boucle autour du code (ici vide).

<>va d'abord s'ouvrir a, lire les enregistrements une ligne à la fois jusqu'à ce que le fichier soit épuisé puis s'ouvrir b...

Le problème est que, pour ouvrir le fichier, il utilise la première forme non sécurisée de open:

open ARGV, "the file as provided"

Avec ce formulaire, si l'argument est

  • "> afile", il s'ouvre afileen écriture,
  • "cmd|", il s'exécute cmdet lit sa sortie.
  • "|cmd", vous avez un flux ouvert pour l'écriture à l'entrée de cmd.

Ainsi, par exemple:

perl -pe '' 'uname|'

Ne produit pas le contenu du fichier appelé uname|(un nom de fichier parfaitement valide btw), mais la sortie de la unamecommande.

Si vous courez:

perl -ne 'something' -- *

Et quelqu'un a créé un fichier appelé rm -rf "$HOME"|(encore une fois un nom de fichier parfaitement valide) dans le répertoire courant (par exemple parce que ce répertoire était autrefois accessible en écriture par d'autres, ou que vous avez extrait une archive douteuse, ou que vous avez exécuté une commande douteuse, ou une autre vulnérabilité dans un autre logiciel a été exploitée), alors vous avez de gros problèmes. Les domaines où il est important d'être conscient de ce problème sont les outils qui traitent les fichiers automatiquement dans les espaces publics comme /tmp(ou les outils qui peuvent être appelés par de tels outils).

Fichiers appelés > foo, foo|, |foosont un problème. Mais dans une moindre mesure < fooet fooavec des caractères d'espacement ASCII en tête ou en fin (y compris l'espace, la tabulation, la nouvelle ligne, cr ...), cela signifie que ces fichiers ne seront pas traités ou que le mauvais sera.

Sachez également que certains caractères de certains jeux de caractères multi-octets (comme ǖdans BIG5-HKSCS) se terminent par l'octet 0x7c, le codage de |.

$ printf ǖ | iconv -t BIG5-HKSCS | od -tx1 -tc
0000000  88  7c
        210   |
0000002

Donc, dans les paramètres régionaux utilisant ce jeu de caractères,

 perl -pe '' ./nǖ

J'essaierais d'exécuter la ./n\x88commande comme perlje n'essaierais pas d'interpréter ce nom de fichier dans les paramètres régionaux de l'utilisateur!

Comment réparer / contourner

AFAIK, vous ne pouvez rien faire pour changer ce comportement par défaut dangereux perlune fois pour toutes à l'échelle du système.

Tout d'abord, le problème se produit uniquement avec des caractères au début et à la fin du nom de fichier. Donc, alors que perl -ne '' *ou perl -ne '' *.txtsont un problème,

perl -ne 'some code' ./*.txt

est pas parce que tous les arguments commencent maintenant avec ./et fin en .txt(donc pas -, <, >, |, espace ...). Plus généralement, c'est une bonne idée de préfixer les globes avec ./. Cela évite également les problèmes avec les fichiers appelés -ou commençant par -de nombreux autres utilitaires (et ici, cela signifie que vous n'avez plus besoin du --marqueur de fin d'options ( )).

Utiliser -Tpour activer le taintmode aide dans une certaine mesure. Il abandonnera la commande si un tel fichier malveillant est rencontré (uniquement pour les cas >et |, mais pas pour les <espaces).

C'est utile lorsque vous utilisez de telles commandes de manière interactive car cela vous avertit qu'il se passe quelque chose de douteux. Cependant, cela peut ne pas être souhaitable lors d'un traitement automatique, car cela signifie que quelqu'un peut faire échouer ce traitement simplement en créant un fichier.

Si vous souhaitez traiter chaque fichier, quel que soit leur nom, vous pouvez utiliser le ARGV::readonly perlmodule sur CPAN (malheureusement généralement pas installé par défaut). C'est un module très court qui fait:

sub import{
   # Tom Christiansen in Message-ID: <24692.1217339882@chthon>
   # reccomends essentially the following:
   for (@ARGV){
       s/^(\s+)/.\/$1/;   # leading whitespace preserved
       s/^/< /;       # force open for input
       $_.=qq/\0/;    # trailing whitespace preserved & pipes forbidden
   };
};

Fondamentalement, il désinfecte @ARGV en le transformant " foo|"par exemple en "< ./ foo|\0".

Vous pouvez faire de même dans une BEGINinstruction de votre perl -n/-pcommande:

perl -pe 'BEGIN{$_.="\0" for @ARGV} your code here' ./*

Ici, nous le simplifions sur l'hypothèse qui ./est utilisée.

Un effet secondaire de cela (et ARGV::readonly) est que $ARGVdans your code heremontre ce caractère NUL final.

Mise à jour 2015-06-03

perlLes versions 5.21.5 et supérieures ont un nouvel <<>>opérateur qui se comporte comme <>ceci, sauf qu'il ne fera pas ce traitement spécial. Les arguments ne seront considérés que comme des noms de fichiers. Donc avec ces versions, vous pouvez maintenant écrire:

perl -e 'while(<<>>){ ...;}' -- *

(n'oubliez pas le --ou utilisez-le ./*cependant) sans craindre d'écraser des fichiers ou d'exécuter des commandes inattendues.

-n/ -pToujours utiliser la dangereuse <>forme cependant. Et méfiez-vous des liens symboliques sont toujours suivis, ce qui ne signifie pas nécessairement qu'il est sûr d'utiliser dans des répertoires non fiables.

Stéphane Chazelas
la source
2
vous y avez travaillé toute la journée, je parie. bien joué.
mikeserv
2
belle mise à jour de perl, mais il est étrange que les développeurs de perl n'aient pas ajouté d'options -P et -N pour l'utiliser (ne peuvent pas modifier les -p et -n existants car certains scripts peuvent s'appuyer sur le comportement non sécurisé)
cas
9

En plus de la réponse de @ Stéphane Chazelas , nous n'avons pas à nous soucier de ce problème si nous utilisons l' -ioption de ligne de commande:

$ perl -pe '' 'uname|'
Linux

$ perl -i -pe '' 'uname|'
Can't open uname|: No such file or directory.

Parce que lors de l'utilisation de l' -ioption, perlutilisé stat pour vérifier l'état du fichier avant de le traiter:

$ strace -fe trace=stat perl -pe '' 'uname|'
stat("/home/cuonglm/perl5/lib/perl5/5.20.1/x86_64-linux", 0x7fffd44dff90) = -1 ENOENT (No such file or directory)
stat("/home/cuonglm/perl5/lib/perl5/5.20.1", 0x7fffd44dff90) = -1 ENOENT (No such file or directory)
stat("/home/cuonglm/perl5/lib/perl5/x86_64-linux", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
Process 6106 attached
Linux
Process 6105 suspended
Process 6105 resumed
Process 6106 detached
--- SIGCHLD (Child exited) @ 0 (0) ---

$ strace -fe trace=stat perl -i -pe '' 'uname|'
stat("/home/cuonglm/perl5/lib/perl5/5.20.1/x86_64-linux", 0x7fffdbaf2e50) = -1 ENOENT (No such file or directory)
stat("/home/cuonglm/perl5/lib/perl5/5.20.1", 0x7fffdbaf2e50) = -1 ENOENT (No such file or directory)
stat("/home/cuonglm/perl5/lib/perl5/x86_64-linux", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
stat("uname|", 0x785f40)                = -1 ENOENT (No such file or directory)
Can't open uname|: No such file or directory.
cuonglm
la source
1
N'y a-t-il pas une condition de concurrence possible entre le statcontrôle et le traitement efficace de perl en cours juste après?
Totor
@Totor: Je pense que non.
cuonglm
Ce n'est pas question stat. C'est juste -ipour éditer des fichiers sur place, donc cela n'a pas de sens d'accepter des arguments autres que les chemins de fichiers réels, donc avec -i, ce traitement spécial n'est pas fait.
Stéphane Chazelas