[Предполагаемый заголовок: Испортить так просто, не так ли?]
Перевод Anton Petrusevich <casus@mail.ru> и Alex Ott <ott@phtd.tpu.edu.ru>
Если вы читали мою колонку в течении некоторого времени, то вы вероятно заметили, что я упоминаю о ``taint-режиме'', обычно коротко, при описании строки такого типа:
#!/usr/bin/perl -Tw
что включает выдачу предупреждений (ключ -w
) и ``режим
taint'' (ключ -T
). Но что такое режим taint?
Taint режим является свойством безопасности для Perl, и включает в
себя два уровня выполнения. В первую очередь, при включенном taint
режиме, выполнение некоторых операций запрещено. Одной из них является
то, что $ENV{PATH}
не может содержать каталоги доступные для
записи всеми пользователями при запуске дочернего процесса (например с
помощью обратных кавычек или функции system
). Когда ваша
программа предпринимает небезопасное действие, то программа сразу же
прерывается (через вызов die
), до того, как действие получит
возможность выполнить возможное нарушение безопасности. Вы можете
включить код, который сам будет проверять все действия, но вы можете
заставить Perl выполнять проверки для обеспечения устойчивости и уровня
``наилучшего выполнения'', который вы можете не создать или явно
включить.
Второй уровень взаимодействия является более интересным и уникальным для Perl (среди всех популярных языков программирования, которые я знаюa), на котором Perl отслеживает ``ненадежность'' каждого скалярного значения программы. Любой объект полученный из внешних источников (аргументы командной строки, переменные среды, информация о локализации, некоторые системные вызовы и все операции ввода из файлов) отмечается как ``испорченный''.
Например, следующие операции создают испорченные данные:
$t1 = <STDIN>; $t2 = $ENV{USER}; $t3 = $ARGV[2]; @t4 = <*.txt>;
В каждом из этих примеров, данные получаются ``из внешнего мира'', и поэтому трактуются как потенциальное опасные. После того, как данные отмечены как испорченные, порча распространяется на любые данные полученные из испорченных данных:
$t5 = $t4[0]; $t6 = "/home/$t2"; chomp($t1); @x = ("help", "me", $t3, "please");
Заметьте, что порча осуществляется на уровне скаляров. Так, что
испорченным является только элемент $x[2]
, а не весь массив
@x
.
После того, как данные помечена как ``грязные'', то будет
блокирована любая попытка использования данных подверженных внешнему
воздействию, вызывая немедленное завершение процесса через оператор
die
с нарушением режима taint. Например, запуск команды
rename
, когда либо имя исходного файла, либо имя файла
назначения является ``загрязненным'', рассматривается угрожающим. Однако
разрешается нормальное использование команды:
rename $x[0], $x[1];
Но не операция, которые используют ``испорченные'' данные (вспоминая
о том, что $x[2]
была испорчена ранее):
rename $x[0], $x[2];
Это значит, что данные, которые были получены из внешнего мира, также не могут тривиально воздействовать на внешний мир. Почему это так важно?
Хорошо, обычным использованием режима taint является безопасное
выполнение программ, которые действуют от лица других
пользователей. Например. программы с установленным битом пользователя
или группы на время выполнения берут привелегии владельца программы,
позволяя обычным пользователям для определенного набора задач роли
работать в роли администратора (или другого пользователя). Или при
выполнении программ CGI, выполняющихся под правами web-сервера (обычно
это права пользователя nobody
), позволяя работать с правами
пользователя используя запрос любого пользователя и обычно без прямого
доступа к серверу, а только через web-сервер.
В обоих случаях, важным является то, что проверяются входные данные, так что пользователю, запускающему программу, не позволяется нарушить привелегии пользователя, под которым запускается программа, для выполнения неразрешенных действий.
Например, может быть очень опасным переименование файла, основываясь на данных, полученных из формы CGI:
use CGI qw(param); ... my $source = param('source'); my $dest = param('destination'); rename $source, $dest;
Возможно, что автор этого скрипта CGI верит, что поскольку форма
содержит только радио-кнопки или всплывающее меню, которые снов
определяют данные, то у него будет безопасная программа. В
действительности, человек со склонностью к разрушению, может легко
запустить этот скрипт с произвольными данными в качестве
source
и destination
, и возможно переименовать любой
файл, к которому имеет доступ пользователь, под которым запущен скрипт!
При включенном режиме taint, параметры CGI (полученные при чтении
стандартного потока ввода или через переменные среды) помечаются как
``грязные'' и поэтому операция rename
даст сбой до нанесения
потенциального ущерба. (Для включения режима taint для скриптов
CGI, просто включите ключ -T
в строку #!
, как это
показывалось ранее). И это точно будут самые безопасные вещи, которые
будут делать здесь.
но иногда существуют ситуации, когда входные данные легально должны
воздействовать на внешний мир. Вот следующее свойство, с которым режим
taint приходит. Как некоторое исключение, результат запоминания в
регулярном выражении (доступ к которому обычно происходит через
нумерованую переменную, такие как $1
и $2
и так
далее) никогда не отмечается как ``грязный'', даже если сравнение
производится с ``загрязненными данными. При правильном использовании
это дает нам ``хорошо защищенные ворота в заборе''. Например:
my $source = param('source'); unless ($source =~ /^(gilligan|skipper|professor)$/) { die "unexpected source $source\n"; } $safe_source = $1;
Здесь ожидается, что значение $source
будет одним из набора
gilligan
, skipper
или professor
. Если это не
так, то программа завершит выполнение до копирования данных в
переменную $safe_source
. (Заметьте, что скобки в регулярном
выражении выполняют двойную работу, обеспечивая правильное
предшевтсвование относительно вертикальных линий и значениями начала и
конца строки, а также в качестве побочного явления определяют первую
переменную на которую производится ссылка.Иногда вы становитесь
счастливыми).
Теперь значение переменной $safe_source
является допустимым
для использования в вышеприведенной операции rename
, поскольку
оно получается из регулярного выражения, а не напрямую из входных
данных. В действительности, мы даже можем снова присвоить ее переменной
$source
(что является часто применяемым действием):
$source = $1; # source now untainted
Конечно, мы должны выполнить аналогичную операцию над переменной
$destination
, для того, чтобы завершить работу.
Так, что если кто-то попытается задать неправильное значение для
исходного параметра, например такое как ginger
, то программа
прекратит свое выполнение, и при включенном и при выключенном режиме
taint, но в режиме taint программа работает, поскольку мы
добавили дополнительный код для выполнения сравнения с регулярным
выражением, для чего нам нужно было выяснить какие могут быть возможные
значения строки.
И теперь мы переходим к следующей точке: обычно мы не можем выполнять явную проверку относительно известного списка значений. Очень часто, данные являются значением указанным пользователем, и которые необходимо подвести под общее описание, но также снова, регулярные выражения являются достаточно хорошими для соответствия многим вещам.
Например, допустим, что $source
получаем из текстового
поля, а не из списка выбора, разрешая таким образом ввод привольных
данных. Как мы теперь можем передать ее оператору rename
?
Хорошо, сначала мы решим, какой должна быть нормальная
строка. Например, давайте введем ограничение, чтобы имена файлов
содержали только символы попадающие соответствующие классу символов
\w
, включая символ точки (также разрешая использование точки в
качестве первого символа имени файла). Это будет выглядеть вот так:
$source = param('source'); $source =~ /^(\w[\w.]*)$/ or die; $source = $1;
Так что снова, если строка не соответствует образцу, то программа
прекращает выполнение. И только в том случае, если мы не прекратили
работу, мы продолжим использование переменной $1
, которая была
проверена на соответствие ожидаемой формы имени.
Заметьте, что очень важно проверить результат соответствия
регулярному выражению, поскольку переменная $1
(а также
остальные переменные) получает значение, только когда произошло
соответствие регулярному выражению. В противном случае мы получим
результат предшествующего сравнения, и это будет действительно плохой
новостью для нас:
## bad code do not use ## $param('source') =~ /^(\w[\w.]*)$/; $source = $1; ## bad code do not use ##
Немного более компактным способом записи вышеприведенного кода может быть следующий код:
my ($source) = param('source') =~ /^(\w[\w.]*)$/ or die "bad source";
Здесь, я неявно использую переменную $1
как список,
возвращенный соответствием регулярному выражению, объявляю переменную
для хранения результата и выполняю проверку ошибок, все это в одном
компактном выражении.
Шаблон регулярного выражения должен быть таким, каким вы захотите,
Например, если вы используете шаблон /(.*)/s
, то вы эффектно
удалитесь из под действия режима taint, для отдельных данных,
оставляя потенциальную возможность для взламывания вашей программы с
помощью непредусмотренных вами способов.
Так, что я надеюсь, что это дало вам немного понимания того, как
использовать режим taint, а также объяснение того, почему это
полезно. Если эта статья 'taint достаточно для вас, то я
предполагаю. что вы прочитаете справочную страницу perlsec
(возможно используя команду perldoc perlsec
). Встретимся в
следующий раз, наслаждайтесь вашим новыми знаниями о безопасности.