Безпечне програмування на Perl h2>
Дмитро Громов p>
Як
уникнути передачі призначених для користувача змінних оболонці ОС при виклику exec () і
system ()? p>
В
Perl ви можете запускати зовнішні програми різними шляхами. Ви можете
перехоплювати висновок зовнішніх програм, використовуючи зворотні лапки: p>
$ date
= `/ Bin/date`; p>
Ви
можете відкривати "тунель" (pipe) до програми: p>
open (SORT, "|/usr/bin/sort |
/ usr/bin/uniq "); p>
Ви
можете запускати зовнішні програми і чекати закінчення їх виконання через
system (): p>
system "/ usr/bin/sort <
foo.in "; p>
або
ви можете запускати зовнішні програми без повернення керування з допомогою exec (): p>
exec "/ usr/bin/sort <
foo.in "; p>
Всі
ці вислови є небезпечними якщо використовують дані, введені
користувачем, які можуть містити метасимволи. Для system () і exec ()
синтаксична існує можливість, що дозволяє запускати зовнішні програми
напряму, без звернення до оболонки ОС. Якщо ви передаєте зовнішньої програмі
аргументи, що представляють собою не рядок, а список, то Perl не буде
використовувати оболонку, і метасимволи не викличуть небажаних побічних
ефектів. Наприклад: p>
system
"/ usr/bin/sort", "foo.in"; p>
Ви
можете використовувати цю особливість для того, щоб відкрити тунель, не
звертаючись до оболонки ОС. Викликаючи open в магічній послідовності символів
| -, Ви запускаєте копію Perl і відкриваєте тунель (pipe) до цієї копії. Дочірня
копія Perl потім негайно запускати зовнішні програми, використовуючи список
аргументів для exec (). p>
open (SORT ,"|-") | | exec
"/ usr/bin/sort", $ uservariable; p>
while $ line (@ lines) ( p>
print SORT $ line, "n"; p>
) p>
close SORT; p>
Для
читання з тунелю без звернення до оболонки можна використовувати схожий спосіб, за
послідовністю -|: p>
open (GREP ,"-|") | | exec
"/ usr/bin/grep", $ userpattern, $ filename; p>
while () ( p>
print "match: $_"; p>
) p>
close GREP; p>
Це
ті форми open (), які необхідно завжди використовувати у випадках, коли в
іншій ситуації ви використовували б перенаправлення open (piped open). p>
Ще
більш хитра можливість дозволяє вам запускати зовнішні програми і обманювати
їх відносно їх власної назви. Це корисно при використанні
програм, дії яких залежать від того, з використанням якого імені вони
запущені. p>
Ось
синтаксис: p>
system
$ настоящее_імя "ложное_імя", "аргумент1", "аргумент2" p>
Наприклад: p>
$ shell = "/ bin/sh" p>
system $ shell
"-sh", "-norc" p>
Цей
приклад запускає sh - оболонку операційної системи - з ім'ям "-sh",
що змушує її діяти інтерактивно. Заметте, що справжнє ім'я програми
повинно зберігатися у вигляді змінної, і що між ім'ям змінної і початком
списку аргументів немає коми. p>
Можна
записати цю команду більш компактно: p>
system ( "/ bin/sh")
"-sh", "-norc" p>
Що
таке "перевірки заразність" (taint checks) в Perl? Як їх включити? P>
Як
ми бачили, один з найбільш часто зустрічаються проблем з безпекою при
програмуванні CGI - передача оболонці ОС для користувача змінних без їх
перевірки. Perl пропонує механізм перевірки "заразність", який не
дозволяє цього робити. Будь-яка змінна, яка проініціірована даних протягом
межами програми (включаючи дані із середовища, стандартного вводу та командної
рядка) розглядається як "заразна", і не може бути більше
використана за межами програми. Зараза може розповсюджуватися. Якщо ви використовуєте
заражену змінну для присвоєння значення іншої змінної, другий
мінлива також виявляється заражена. Заражені змінні не можуть бути
використані для виклику eval (), system (), exec () або piped open (). Якщо ви
спробуєте це зробити, Perl припиняє роботу і виводить попередження. Perl
також відмовиться працювати, якщо ви спробуєте викликати зовнішню програму, не
встановивши явно значення змінної PATH. p>
В
версії 4 мови Perl перевірка включається при використанні спеціальної версії
інтерпретатора, який має назву "taintperl": p>
#!/usr/local/bin/taintperl p>
В
версії 5 - використовуйте прапор-T при запуску інтерпретатора: p>
#!/usr/local/bin/perl
-T p>
Нижче
описано як "знезаражувати" (untaint) змінні. p>
Для
більш повного обговорення питання можна звернутися до CGI/Perl Taint Mode FAQ
(автор - Gunther Birzniek). p>
OK,
я ввімкнув перевірку заразність, як ви рекомендували. Тепер мій скрипт
припиняє роботу з повідомленням "Insecure $ ENV (PATH) at line XX" при
кожному запуску! p>
Навіть
якщо ви не довіряєте змінної PATH при запуску зовнішніх програм, існує
можливість того, що це робить зовнішня програма. Тому слід завжди
включати такий рядок на початку вашого скрипта, якщо ви використовуєте taint
checks: p>
$ ENV ( 'PATH') =
'/ bin:/usr/bin:/usr/local/bin'; p>
Відредагуйте
її так, щоб перерахувати директорії, в яких ви хочете шукати. Думка про
вклюяеніі поточного директорія (".") до складу змінної PATH є
поганою ідеєю. p>
Як
"знезаразити (untaint) змінну? p>
Після
того, як мінлива заражена, Perl не дасть вам можливості використовувати її в
функціях system (), exec (), piped open, eval (), зворотних лапках, або будь-який
функції, яка впливає на що-небудь за межами програми (наприклад - unlink).
Ви не можете цього зробити навіть якщо ви перевірили змінну на утримання метасимволів
або використовували команду tr///або s///для видалення метасимволів. Єдиний
спосіб знезаразити змінну - використовувати операцію пошуку по масці і
витяг збігається підрядки. Наприклад, якщо змінна повинна містити
адреса електронної пошти, то витягнути знезаражену копію адреси можна в такий
так: p>
$ mail_address = ~/(w [w-.]*)@([ w-.]+)/; p>
$ untainted_address
= "$ 1 @ $ 2"; p>
Така
маска дозволить виділити адресу у формі "кому @ куди", де елементи
"кому" і "куди" можуть включати літери, крапки і тире. Більше
того, "кому" не може починатися з тире, що використовується в багатьох
програмах як службовий символ командного рядка. p>
Я
видаляю метасимволи із змінної, але Perl продовжує думати, що вона заражена! p>
Дивись
вище відповідь на це питання. Єдиний спосіб знезаразити змінну --
застосувати пошук за маскою. p>
Дійсно
Чи небезпечна операція пошуку $ foo = ~/$ user_variable /? p>
Часто
завдання скрипта CGI на Perl полягає в отриманні від користувача списку ключових
слів і використання їх в операціях пошуку по масці для знаходження співпадаючих
імен файлів (або чого - небудь в цьому роді). Саме по собі це не небезпечно. Небезпечна
оптимізація, яку деякі програми Perl використовують для прискорення пошуку.
При використанні змінної в операції пошуку, вираз компілюється всякий
раз при виконанні операції. Для уникнення перекомпілірованія, що займає
час, можна використовувати спеціальний прапор - o, що призведе до того, що
вираз буде відкомпілювати тільки один раз: p>
foreach (@ files) ( p>
m/$ user_pattern/o; p>
) p>
Тепер,
проте, Perl буде ігнорувати будь-які зміни до змінної, що призведе до
неправильної роботи циклів такого роду: p>
foreach $ user_pattern
(@ user_patterns) ( p>
foreach (@ files) ( p>
print if m/$ user_pattern/o; p>
) p>
) p>
Для
подолати цю проблему програмісти, які пишуть на Perl, часто використовують такий
трюк: p>
foreach $ user_pattern
(@ user_patterns) ( p>
eval "foreach (@ files) (print
if m/$ user_pattern/o; }"; p>
) p>
Проблема
тут полягає в тому, що в операторі eval () використовується для користувача
змінна. Якщо змінна не піддається ретельній перевірці, то можна
змусити eval () виконати довільний код на Perl. Для розуміння того, чим
це загрожує, подумайте, що відбудеться у випадку, якщо змінна буде мати
таке значення: "/; system 'rm *'; /" p>
Перевірки
заразність (див. вище) дозволяють спіймати потенційну небезпеку в цій галузі.
Ви можете вибирати між відмовою від такого роду оптимізації, або ретельно
знезараженням змінної перед використанням. Корисна можливість у Perl5
полягає у використанні Q і E для коментування метасимволів так, щоб вони
не були використані: p>
print if m/Q $ user_patternE/o; p>
Мій
скрипт CGI вимагає великі привілеї, ніж він отримує як користувач nobody.
Як мені змінити ідентифікатор користувача? P>
Перш
за все, чи дійсно це необхідно? Надання більших прав збільшує
ризик і дозволяє зламаному скрипту завдати більше шкоди. Якщо ви хочете
надати скрипту права користувача root, то спершу ДУЖЕ добре подумайте. p>
Ви
можете змусити скрипт виконуватися з правами його власника шляхом установки
біта s: p>
chmod
u + s foo.pl p>
Ви
можете надати йому права групи, до якої належить власник, встановивши
біт s в полі групи: p>
chmod
g + s foo.pl p>
Однак,
багато систем Unix містять лазівку, що дозволяє зламувати такі скрипти. Це
стосується тільки скриптів, а не компілює програм. У таких системах
спроба запуску скрипта на Perl, для якого були виставлені s біти, приведе
до появи повідомлення про помилку з боку самого Perl. p>
На
таких системах ви маєте дві можливості: p>
Можна
виправити ядро так, щоб заборонити установку цих бітів для файлів скриптів.
Perl тим не менше буде правильно визначати ці біти і встановлювати
код користувача. Докладну інформацію про це можна знайти в Perl faq: p>
ftp://rtfm.mit.edu/pub/usenet-by-group/comp.lang.perl/ p>
Ви
можете помістити скрипт в оболонку, напмсанную на C. Зазвичай це виглядає так: p>
# include p>
void main () ( p>
execl ( "/ usr/local/bin/perl", "foo.pl", "/ local/web/cgi-bin/foo.pl", NULL); p>
) p>
Після
компілювання програми, виставте s біти. Програма буде виконуватися з
правами власника, запускати інтерпретатор Perl і виконувати скрипт,
що міститься у файлі "foo.pl". p>
Крім
того, можна запускати сам сервер з правами користувача, достатніми для
виконання необхідних дій. Якщо ви використовуєте сервер CERN, то у вас є
можливість запускати сервер з різними правами для різних скриптів. Див
документацію CERN для отримання подальшої інформації. p>
Список літератури h2>
Для
підготовки даної роботи були використані матеріали з сайту http://phpcell.webhost.ru/
p>