Контакты

Для связи с нами можно использовать:
irc://irc.chatnet.ru:#gfs
icq://546460
email://cobalt[@]gfs-team.ru

Все материалы предоставлены только с ознакомительной целью
ГлавнаяСтатьиКонкурсPHP Web Firewall (2-е место)
© Zixter 01.02.2008

Скачать исходник к статье
Intro

В наши дни ни один сайт не обходится без скриптов. В большинстве своем все скрипты написаны на PHP, и зачастую пишут их не профессионалы, поэтому в таких скриптах вероятность нахождения уязвимости достаточно высока. В подавляющем большинстве скриптов уязвимость сводится к php/sql injection, либо XSS. Причины таких багов почти всегда кроются в неправильной или недостаточно тщательной обработке входных данных. В этой статье я расскажу как можно контролировать входные данные, не изменяя самого кода скрипта.

WebFirewall->concept

Идея Web Firewall в чем-то схожа с IDS - Intrusion Detection System. Ее суть заключается в том, что между клиентом и сервером находится некий посредник, который обрабатывает и анализирует весь входящий на сервер трафик. В случае нахождения в трафике недопустимых или подозрительных данных IDS предпринимает определенные действия по ликвидации возможной опасности. В IDS как правило используются различные интеллектуальные алгоритмы распознавания атак, но в нашей системе будет в основном простая фильтрация входящих данных, поэтому правильней будет назвать ее фаерволом. Алгоритм работы Web Firewall (WF) будет следющим:

1) WF выполняется в скрипте до запуска основного функционального кода.
2) WF анализирует все переданные скрипту переменные, с помощью заранее установленных ограничений на их содержимое.
3) Если содержимое какой-то переменной не будет соответствовать определенному для этой переменной правилу, то WF будет выполнять действия по защите скрипта (например, экстренное завершение скрипта и запись в лог о попытке проникновения).

При использовании такого фаервола нам не нужно будет исправлять код всего скрипта для исключения уязвимости. Достаточно знать имена всех входящих переменных и их возможное содержимое, после чего просто подключить фаервол в начале скрипта.

WebFirewall->coding->concept

Для написания фаервола мы будем использовать PHP 5. Сам фаервол будет представлять собой класс с соответствующими функциями. Плюсом при использовании классов является более гибкая настройка и использование WF, например, можно создать сразу несколько WF и распараллелить задачи защиты между ними.

Все входящие данные разделим на 3 группы (по методу загрузки):

1) GET-данные, которые хранятся в массиве $_GET
2) POST-данные, хранятся в массиве $_POST
3) COOKIE-данные, хранятся в $_COOKIE

Теперь рассмотрим возможные виды нарушения безопасности. Можно составить такой список:

1) Переменная пришла не тем методом. Например, если предполагается что переменная $foo должна приходить только из COOKIE, а она пришла в POST или GET данных, то такая ситуация будет считаться нарушением безопасности.

2) Переменная содержит недопустимые данные. Например, если в переменной, которая должна принимать целочисленные значения нам пытаются подсунуть что-то типа "?1? OR 1=1? /*", то WF должен пресечь это.

3) Переменная содержит слишком большие данные. Аналогично пункту 2, WF должен пресекать работу с переменными, длина которых превышает допустимый предел.

4) Пришла непонятно какая переменная, которой вобще не должно быть. Этот вид используется в основном с включенными REQGISTER_GLOBALS, когда через запрос можно менять внутренние переменные скрипта, либо когда скрипту присылают допустимые переменные, но нужные не в данный момент, а далее по ходу работы скрипта.

Итак, все эти 4 пункта могут быть потенциальными опаностями для любых из трех групп данных - GET, POST и COOKIE.

Действия WF в случае обнаружения нарушения защиты будут следующими: фаервол будет вызывать заранее определенную для каждого вида нарушения функцию, параметром в которую будет передаваться имя переменной, правило которой было нарушено.

WebFirewall->coding->code

Итак, создадим класс WebFirewall. Из переменных, доступных в любом месте скрипта, у нас будут два массива - $vars и $ignored.

В $ignored будем добавлять те переменные, которые вобще не будут обрабатываться фаерволом. Нужно это вот для чего: так как у нас под контролем должны быть абсолютно все входящие переменные, то мы не можем просто не добавить какую-то переменную в список фильтруемых, иначе она будет выловлена как "лишняя" (вариант 4 видов нарушения безопасности).

Каждый элемент массива $vars будет описывать одну переменную. Выглядеть он будет так :

$this->vars[?varname?] => array( array(?type1?, ?type2?, ...), $maxlength )

Здесь varname - имя переменной, type1, type2 - типы данных, которые может содержать эта переменная, $maxlength - максимальная длина переменной. У нас будет 4 типа содержимого переменных:

?num? - переменная является числом(состоит только из цифр);
?alnum? - переменная состоит из цифр и букв;
?char? - длина переменной равна 1;
?print? - переменная состоит из печатаемых символов;

Наконец, для расширения функциональности, если первый символ типа переменных будет ?~?, например ?~type~?, то скрипт будет сравнивать содержимое этой переменной со строкой ?~type~? как регулярное выражение. Вообще все вышеперечисленные типы содержимого можно записать в виде регулярного выражения, но так как их использование достаточно ресурсоемко, некоторые типы лучше записать простыми функциями.

Теперь добавим флаги переменной, с помощью которых фаервол сможет контролировать, каким методом пришла эта переменная:

?in_get? - переменная может приходить только GET методом
?in_post? - переменная может приходить только POST методом
?in_cookie? - переменная может приходить только через COOKIE

Например, следующая строка:

$this->vars[?id?] => array( array(?num?, ?~6~?, ?in_get?), 5 )

означает, что переменная id должна иметь длину не более 5 символов, состоять только из цифр и содержать цифру 6. Кроме того эта переменная может приходить только через GET. Пример правильного применения такого правила: http://somehost.com/index.php?id=363

Теперь перейдем к конструктору класса. При создании, классу передаются 4 имени функции, которые обрабатывают 4 вида нарушений.


class WebFirewall {


public $vars = array(),   // список переменных
       $ignore = array(); // список игнорируемых переменных
       
       
// обработчики нарушения безопасности        
private $hnd_nonex, // хендлер переменной, которой нет ни в $vars ни в $ignored     
        $hnd_overflow,  // хендлер слишком длинной переменной 
        $hnd_type,   // хендлер переменной с неправильным содержимым    
        $hnd_method; // хендлер переменной, пришедшей не тем методом, который 
                     // требуется.
     
public function __construct($h_nonex = false, $h_type = false, $h_overflow = false,
                             $h_method = false) 
{    

    // устанавливаем обработчики переменных
    $this->hnd_nonex = $h_nonex;     
    $this->hnd_overflow = $h_overflow; 
    $this->hnd_type = $h_type;     
    $this->hnd_method = $h_method; 
} 


/**

Здесь остальные функции

*/       
           
}        

Если в конструктор вместо обработчика нарушения передать false, то при вызове метода проверки в фаерволе просто будет возвращать false.

Теперь напишем процедуру анализа переменной. В ней мы просматриваем все простейшие типы в правиле для переменной и сравниваем эту переменную с каждым типом. Затем проверяем максимальную длину переменной, если нужно. Эта процедура универсальна для GET, POST и COOKIE методов, поэтому проверять переменную на корректный метод мы будем не в этой процедуре, а параллельно ей - в другой.


private function VarCheck($name, &$value) 
{

    foreach ($this->vars[$name][0] as $type) {
    
        switch ($type) {

            case ?num?: if (!$this->is_num($value)) {
                            $this->PanicVarBadType($name); // недопустимый тип
                            return false;
                            
                        }
                        break;

            /*
            
            case .... - здесь сравниваем с другими типами
            
            
            */

            // если тип начинается с ~, то сравниваем как регулярное выражение.
            default: if ($type[0] == ?~? && !preg_match($type,$value)) {
            
                         $this->PanicVarBadType($name);
                         return false;

                     }
        }
    }
    
    if (($this->vars[$name][1] != 0) && 
    strlen($value) > $this->vars[$name][1]){
      
       $this->PanicVarMaxLength($name); // слишком большая длина переменной
       
       return false;
    } 
    return true;
}

Здесь функции $this->PanicVarBadType() и $this->PanicVarBadPlace() вызывают обработчики соответствующих нарушений:



// вызов хендлера слишком длинной переменной
private function PanicVarMaxLength($varname) 
{
    if ( $this->hnd_overflow ) { // если хэндлер установлен, вызываем функцию
      call_user_func($this->hnd_overflow, $varname);
    }
}

Функции $this->PanicVarBadType() и $this->PanicVarBadPlace() аналогичны.

Собственно функция запуска работы всего WF будет состоять из последовательного вызова проверок GET, POST, COOKIE переменных.


public function CheckVars() 
{
    $this->CheckGetVars();    // проверяем все GET переменные
    $this->CheckPostVars();   // проверяем все POST переменные
    $this->CheckCookieVars(); // проверяем все COOKIE переменные

}

Каждая из этих трех функций проверяет переменные, пришедшие соответствующим методом, сначала с помощью функции $obj->VarCheck(), а затем проверяет, не пришла ли эта переменная другими методами, если указан соответствующий флаг (?in_get?, ?in_post? или ?in_cookie?):


public function CheckGetVars() 
{
    foreach ($_GET as $k=>$v) { // перебираем все GET переменные
    
      // если переменная в списке игнорирования, пропускаем ее
      if (in_array($k, $this->ignore)) { 
          break;
      }
      
      // если переменной нет в списке правил, она лишняя
      if (!array_key_exists($k, $this->vars)) {
          $this->PanicVarNotExist($k);
          break;
      }  

      // переменная также пришла 
      if (in_array( ?in_post?, $this->vars[$k][0]) ||
          in_array( ?in_cookie?, $this->vars[$k][0])) {
          $this->PanicVarBadPlace($k);
      }
      $this->VarCheck($k, $v);

    }

    return true;

}

Итак, общий алгоритм работы Web Firewall таков:

1) Составляем правила для переменных;
2) Пишем обработчики нарушений;
3) Создаем объект класса WebFirewall;
4) Устанвливаем в этом объекте созданные ранее правила и обработчики;
5) Запускаем проверку переменных.

WebFirewall->using

Посмотрим на работу фаервола в действии. Для начала опишем массив правил для входящих переменных:


$firewall_rules  = array( 
        
        ?news?        => array( array(?alnum?)   , 50),
        ?comment?     => array( array(?~.*~?, ?in_post?), 1000),
        

);

Здесь переменная news должна содержать только буквы и цифры и не может быть длиннее 50 символов. Переменная comment может содержать любые символы, не должна быть длиннее 1000 символов и может передаваться только методом POST.

Напишем функции-обработчики нарушений. Для простоты они будут просто завершать работу скрипта с выдачей соответсвующего сообщения:


// обработчик лишней переменной
function Firewall_nonex($varname) 
{
    exit(?<font size="+1" color="red">Hacking attempt - ?.$varname.?<br />excess 
          var</font>?);   
}

Остальные 3 функции аналогичны - Firewall_type(), Firewall_overflow(), Firewall_place().

Теперь создадим и запустим наш фаервол:


// создаем класс
$firewall = new WebFirewall(?Firewall_nonex?,  ?Firewall_type?, 
                            ?Firewall_overflow?, ?Firewall_place?);

// устанавливаем правила
$firewall->vars = $firewall_rules;

// запускаем проверку
$firewall->CheckVars();

echo ?all ok?;

Поскольку наши функции при несоблюдении правила завершают скрипт, то сообщение ?all ok? появится только в случае правильности всех данных.

Вот примеры запросов, которые не пройдут проверку:

index.php?news=1234@ // содержит символ @ ( не буква и не цифра)
index.php?comment=my_comment // данные переданы GET методом
index.php?foo=3 // foo - лишняя переменная, которой нет в правилах

Примеры запросов, в которых все в порядке:

index.php?news=123
index.php?news=zixter

WebFirewall->the_end

Итак, используя этот класс, мы можем в любом скрипте подключить файл с этим классом и описанными правилами и обработчиками, отфильтровать все неправильные данные и возможно записать факт обнаружения попытки взлома в логи. Как уже было сказано, в сам скрипт не надо вносить никаких изменений (за исключением добавления строчки include "wf.php"), поэтому такую систему возможно эффективно и гибко использовать в движках малого и среднего размера, или в собственных скриптах для повышения общей безопасности.

Zixter [ http://pcr.cc ], 31.12.07




© Zixter 01.02.2008

e-Commerce Partners Network
Ник:

Текст:
P Br B I Qute



Код: обновить
Последние комментарии
19.11.2017 01:19:37 Dmitriyvah написал:
byncecopesy SlaltPog Kt ...
Пишем guestbook
18.11.2017 23:02:45 GlebNes написал:
hem unuby Osteotsematte ...
Пишем guestbook
18.11.2017 18:42:56 ViktorTap написал:
Zof edifam Weque ...
Пишем guestbook
Реклама

Тут должна была быть ваша реклама, но мы потеряли глиняную табличку с ее текстом. SapeId: 665044

Rambler's Top100