Последствия безопасности для запуска perl -ne '…' *

27

Видимо, работает:

perl -n -e 'some perl code' *

Или

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

(то же самое с -pвместо -n)

Или

perl -e 'some code using <>' *

часто встречается в однострочниках, размещенных на этом сайте, имеет последствия для безопасности. В чем дело? Как этого избежать?

Стефан Шазелас
источник

Ответы:

33

В чем проблема

Во-первых, как и для многих утилит, у вас возникнет проблема с именами файлов, начинающимися с -. Пока в:

sh -c 'inline sh script here' other args

Другие аргументы передаются в inline sh script; с perlэквивалентом,

perl -e 'inline perl script here' other args

Другие аргументы проверяются на наличие дополнительных опций для perl , а не для встроенного скрипта. Так, например, если -eBEGIN{do something evil}в текущем каталоге есть файл ,

perl -ne 'inline perl script here;' *

(с или без -n) будет делать что-то злое.

Как и для других утилит, обходной путь для этого заключается в использовании маркера конца опций ( --):

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

Но даже тогда это все еще опасно, и это зависит от <>оператора, используемого -n/ -p.

Проблема объяснена в perldoc perlopдокументации.

Этот специальный оператор используется для чтения одной строки (одна запись, по умолчанию записи являются строками) ввода, причем этот ввод поступает из каждого переданного аргумента @ARGV.

В:

perl -pe '' a b

-pподразумевает while (<>)цикл вокруг кода (здесь пустой).

<>будет сначала открывать a, читать записи по одной строке, пока файл не исчерпан, а затем открыть b...

Проблема в том, что для открытия файла используется первая небезопасная форма open:

open ARGV, "the file as provided"

С этой формой, если аргумент

  • "> afile", он открывается afileв режиме записи,
  • "cmd|", он работает cmdи читает его вывод.
  • "|cmd"Вы открыли поток для записи на вход cmd.

Так, например:

perl -pe '' 'uname|'

Не выводит содержимое названного файла uname|(кстати, совершенно правильное имя файла), но вывод unameкоманды.

Если вы бежите:

perl -ne 'something' -- *

И кто-то создал файл с именем rm -rf "$HOME"|(опять же совершенно правильное имя файла) в текущем каталоге (например, потому что этот каталог был доступен для записи для других пользователей, или вы извлекли хитрый архив, или вы выполнили какую-то хитрую команду, или была использована другая уязвимость в другом программном обеспечении), тогда у вас большие проблемы. Области, где важно знать об этой проблеме, - это инструменты, автоматически обрабатывающие файлы в общественных местах /tmp(или инструменты, которые могут вызываться такими инструментами).

Файлы называются > foo, foo|, |fooявляются проблемой. Но в меньшей степени < fooи fooс начальными или конечными пробелами ASCII (включая пробел, табуляцию, символ новой строки, cr ...), а также это означает, что эти файлы не будут обработаны или будут неправильными.

Также помните, что некоторые символы в некоторых многобайтовых наборах символов (как ǖв BIG5-HKSCS) заканчиваются байтом 0x7c, кодировкой |.

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

Так что в регионах, использующих эту кодировку,

 perl -pe '' ./nǖ

Попробовал бы запустить ./n\x88команду , как perlбы не пытаться интерпретировать это имя файла локали пользователя!

Как исправить / обойти

AFAIK, вы ничего не можете сделать, чтобы изменить это небезопасное поведение по умолчанию perlодин раз и для всей системы.

Во-первых, проблема возникает только с символами в начале и конце имени файла. Итак, пока perl -ne '' *или perl -ne '' *.txtесть проблема,

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

не потому , что все аргументы теперь начинаются с ./и заканчиваются .txt(так не -, <, >, |, космос ...). В целом, это хорошая идея , чтобы Приставка шарики с ./. Это также позволяет избежать проблем с файлами, вызываемыми -или начинающимися со -многих других утилит (и здесь, это означает, что вам больше не нужен --маркер end-of-options ( )).

Использование -Tдля включения taintрежима помогает в некоторой степени. Он прервет команду , если такой вредоносный файл встречается (только для >и |случаях, не <или пробелы , хотя).

Это полезно при использовании таких команд в интерактивном режиме, поскольку это предупреждает вас о том, что происходит что-то хитрое. Это может быть нежелательно при выполнении какой-либо автоматической обработки, поскольку это означает, что кто-то может сделать эту обработку неудачной, просто создав файл.

Если вы хотите обработать каждый файл, независимо от их имени, вы можете использовать в ARGV::readonly perlмодуль на CPAN ( к сожалению , как правило , не устанавливается по умолчанию). Это очень короткий модуль, который делает:

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
   };
};

По сути, он очищает @ARGV, превращаясь, " foo|"например, в "< ./ foo|\0".

Вы можете сделать то же самое в BEGINинструкции в вашей perl -n/-pкоманде:

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

Здесь мы упрощаем это в предположении, ./что используется.

Побочным эффектом этого (и ARGV::readonly), однако, является то, что $ARGVв your code hereпоказывает, что завершающий символ NUL.

Обновление 2015-06-03

perlv5.21.5 и выше имеют новый <<>>оператор, который ведет себя так, <>за исключением того, что он не будет выполнять эту специальную обработку. Аргументы будут рассматриваться только как имена файлов. Итак, с этими версиями вы можете написать:

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

(не забывайте --и не используйте ./*), не опасаясь перезаписи файлов или выполнения неожиданных команд.

-n/ -pвсе еще используй опасную <>форму. И остерегайтесь символических ссылок, которые все еще используются, так что это не обязательно означает, что их можно безопасно использовать в ненадежных каталогах.

Стефан Шазелас
источник
2
ты работал над этим весь день, я держу пари отлично сработано.
mikeserv
2
хорошее обновление для perl, но странно, что разработчики perl не добавили опции -P и -N, чтобы использовать его (не могут изменить существующие -p и -n, потому что некоторые скрипты могут полагаться на небезопасное поведение)
КАС
9

В дополнение к ответу @ Stéphane Chazelas нам не нужно беспокоиться об этой проблеме, если мы используем параметр -iкомандной строки:

$ perl -pe '' 'uname|'
Linux

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

Потому что при использовании -iопции perlиспользуется stat для проверки состояния файла перед его обработкой:

$ 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
источник
1
Не существует ли возможного состояния гонки между statпроверкой и эффективной обработкой perl, выполняемой сразу после?
Totor
@Totor: я думаю нет.
cuonglm
Это не о stat. Это просто -iдля редактирования файлов на месте, поэтому не имеет смысла принимать аргументы, отличные от реальных путей к файлам, поэтому -iспециальная обработка не выполняется.
Стефан Шазелас