Все ленты — последние статьи

Безопасно-ориентированное программирование в PHP5

Введение

Проблема создания безопасных скриптов всегда стояла довольно остро. Даже, если вы создаете просто домашнюю страницу для себя, вам вряд ли будет приятно, если какой-нибудь умелец ее взломает. Что говорить о крупных корпоративных и коммерческих сайтах, в данном случае взлом сайта может нанести довольно значительный финансовый ущерб. Чаще всего взломщики пользуются различными недоработками в скриптах: прежде всего недостаточной проверкой поступающей из вне (чаще всего от посетителя сайта) информацией. В данной статье я покажу, как использую современные технологии программирования можно организовать гибкую и надежную систему проверки данных, полученных от пользователя. Говоря "современные технологии программирования", я прежде всего имею ввиду шаблоны проектирования. Для понимания статьи желательно владеть PHP5 и ООП. Тем, кто заинтересуется, шаблонами проектирования, очень рекомендую книгу "Design Patterns: Elements of Reusable Object-Oriented Software" "банды четырехF".
Уязвимости

 

Какой код может быть уязвимым к атакам взломщиков? Как взломщики пользуются дырами в скриптах. Прежде всего, отмечу, что достаточно грамотно, написанный скрипт достаточно трудно взломать, более того, большинство взломов происходит из-за недостаточной бдительности и доверчивости программистов. "Проверять, проверять и еще раз проверять входные данные" — это лозунг для написание надежных и безопасных скриптов.
Имена файлов

Чаще всего входные данные поступают из заполненных форм, либо из адресной строки. Данные из форм передаются с помощью метода POST, а чтобы передать данные, использую гипертекстовую ссылку, применяют метод GET. Например, у вас на сайте размещены различные статьи и для показа статьи вы используете скрипт. Обычно информация о статьях хранится в базе данных, а специальный скрипт выдает примерно такой список:

<a href="/show.php?filename=article1.html">Статья 1</a>
<a href="/show.php?filename=article2.html">Статья 2</a>
<a href="/show.php?filename=article3.html">Статья 3</a>

Каждая ссылка указывает на скрипт show.php, который показывает статьи. А в качестве параметра ему передается имя файла. Сам скрипт show.php обычно содержит следующий код:

// Вывод верхней части страницы

echo file_get_contents($filename);

// Вывод нижней части страницы

Конечно, это самый запущенный случай: в данном коде две ошибки, с точки зрения написания безопасного кода. Во-первых, для получения внешних данных (имени файла) используется глобальная переменная $filename, вместо этого лучше использовать ассоциативные массивы $_REQUEST, $_POST, $_GET и другие. Во-вторых, $filename никак не проверяется. Например, взломщик может вместо имени файла в стоке ввода адреса указать index.php и получит текст скрипта, а такой ценной информацией, он без труда воспользуется для дальнейшего поиска недочетов в вашей системе безопасности. Выхода в принципе два: можно вместо имени файла передавать идентификатор статьи, а затем преобразовывать его в имя файла, либо тщательно проверять имя файла на допустимые имена, на слэши и обратные слэши, чтобы взломщик не смог путешествовать по вашим каталогам.
Функция system()

Бывают более патологические случаи: некоторые "программисты" используют внешние данные в функции system(), которая выполняет произвольную команду операционной системы. В таком случае до дефейса сайта один шаг. Функцию system, вообще не следует без экстренной необходимости. Но если пришлось использовать ее вместе с внешними данными, то тут надо будет осуществить очень строгую проверку на спец символы: от всех специальных символов *nix-систем до символа с кодом 0.
Базы данных

Все реже и реже для хранения данных напрямую используются файлы, и все чаще и чаще используются базы данных. В связке с PHP обычно применяют базу данных MySQL. Доступ к данным в таком случае осуществляется при помощи языка запросов SQL. В такие запросы приходится вставлять внешние данные, например, для гостевой книге в базе данных надо сохранить, как минимум, ник и сообщение пользователя. Язык SQL очень гибок, поэтому надо быть внимательным к входным данным. Кроме "вредоносных" данных, следует обрабатывать неправильный ввод пользователя, например, слишком длинную строку.
Чаты, гостевые, форумы

В данном случае нужно волноваться о также и взломе другого типа: взломщик может попытаться воспользоваться тэгами HTML, то есть нельзя допускать ввода символов "". Их надо либо экранировать, либо выводить сообщение о неправильном вводе. Если вы не учитываете это правило, в лучшем случае можете получить у себя в гостевой что-нибудь вроде:

This site was hacked by ********

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

Чтобы решить данную, проблему можно написать функции для различных проверок, но PHP5 довольно неплохо поддерживает объектно-ориентированное программирование — более мощный и гибкий подход. Итак, пусть мы решили, что, прежде всего, будем проверять размер строки. Можно написать соответствующий класс. Далее нам приходить в голову, что неплохо бы сделать проверку на отсутствие слэшей и обратных слэшей. Мысль опытного программиста на этом не остановиться, он придумает другие проверки. Как же нам их все учитывать? Нам будет нужно применять эти проверки в произвольном порядке. Давайте четко выясним, что нам нужно сделать. Нам нужно динамически добавлять новое поведение объекту, то есть различного рода проверки. Можно, конечно, придумать решение самому, но у программистов есть поговорка: "Не надо изобретать колесо (велосипед)". Лучше воспользоваться уже готовым решением — шаблоном проектирования, который используют профессиональные программисты с огромным опытом. В выше упомянутой книге "Банды четырех" (Gang of Four, GoF) есть подходящий нам шаблон проектирования — Decorator. "Decorator" переводится как "украшатель", то есть мы декорируем нашу проверку длины строки дополнительными проверками.

Component (Checker) — это просто абстрактный класс проверки, ConcreteComponent (StringChecker) — это класс для проверки длины строки, который мы будем декорировать дополнительным поведением. Decorator (Decorator) — это абстрактный класс декораторы, который нужен чтобы организовать цепочку декораторов. ConcreteDecorator (SlashChecker, BackSlashChecker, … — это классы с дополнительным поведением (в нашем случае это проверки). Теперь приведу код:

<?php

//
// Применение шаблона проектирования Decorator
// для реализации проверки входных данных в PHP5
//
// Борис Вольфсон
// E-Mail: borisvolfson at mail.ru
// HomePage: borisvofson.h11.ru
//

// класс проверки
abstract class Checker
{
// проверка строки $StringToCheck
abstract public function Check($StringToCheck);

// перевод "успешности" проверки в строку
// $IsOK == True — проверка пройдена,
// $IsOK == False — проверка не пройдена
function Result($IsOK)
{
if ($IsOK)
return "<font color='green'>Проверка пройдена</font><br>";
else
return "<font color='red'>Проверка не пройдена</font><br>";
}
} .

// проверка размера для строк
class StringChecker extends Checker
{
function Check($StringToCheck)
{
echo "Проверка размера строки: ";
echo $this->Result(strlen($StringToCheck) <= 100);
}
}

// декоратор для класса StringChecker
abstract class Decorator extends Checker
{
// переменная для построение цепочки декораторов
private $MyChecker = null;

// конструктор запоминает следующий декоратор
function __construct(Checker $MyChecker)
{
$this->MyChecker = $MyChecker;
}

// вызывается функция проверки $MyChecker
public function Check($StringToCheck)
{
if ($this->MyChecker != null)
$this->MyChecker->Check($StringToCheck);
}
}

// проверки строки на слэши
class SlashChecker extends Decorator
{
public function Check($StringToCheck)
{
echo "Проверка строки на слэши: ";
echo $this->Result(!strstr($StringToCheck, '/'));
parent::Check($StringToCheck);
}
}

// проверки строки на обратные слэши
class BackSlashChecker extends Decorator
{
public function Check($StringToCheck)
{
echo "Проверка строки на обратные слэши: ";
echo $this->Result(!strstr($StringToCheck, '\'));
parent::Check($StringToCheck);
}
}

// проверка строки на символы, отличные от цифр, т.е
// строка должна содержать одни цифры
class DigitsChecker extends Decorator
{
public function Check($StringToCheck)
{
echo "Проверка строки на символы, отличные от цифр: ";

$IsOK = True;

for ($i = 0; $i < strlen($StringToCheck); $i++)
{
if ( ($StringToCheck{$i} < '0') ($StringToCheck{$i} > '9') )
{
$IsOK = False;
}
}

echo $this->Result($IsOK);
parent::Check($StringToCheck);
}
}

$S1 = "Строка /для/ проверки";
echo "<b>Проверка строки: '$S1'</b><br>";
$Checker1 = new BackSlashChecker(new SlashChecker(new StringChecker()));
$Checker1->Check($S1);

echo "<br>";

$S2 = "1365m434\";
echo "<b>Проверка строки: '$S2'</b><br>";
$Checker2 = new DigitsChecker(new BackSlashChecker(new StringChecker()));
$Checker2->Check($S2);

?>

Этот код должен выдавать следующий результат:

Проверка строки: 'Строка /для/ проверки'
Проверка строки на обратные слэши: Проверка пройдена
Проверка строки на слэши: Проверка не пройдена
Проверка размера строки: Проверка пройдена

Проверка строки: '1365m434'
Проверка строки на символы, отличные от цифр: Проверка не пройдена
Проверка строки на обратные слэши: Проверка не пройдена
Проверка размера строки: Проверка пройдена

Класс Checker представляет собой абстрактный класс для проверки, то есть в нем объявлены функции для проверки, но использовать мы его не сможем, так как функции не реализованы, например, объявлена функция Check для проверки строки. В классе StringChecker функции Check уже реализована для проверки размера строки. В абстрактном классе Decorator добавлена переменная MyChecker для организации цепочки декораторов. Функция __construct(Checker $MyChecker) — это конструктор, который вызыется при создании объекта класса. Конструктору передается объект Checker, который будет сохранен для вызова функции Check. Функция Check класса Decorator просто вызывает $MyChecker->Check($StringToCheck). В классах для конкретных проверок нужно просто переписать функцию Check и не забыть в конце ее вызвать parent::Check($StringToCheck), то есть Check класса Decorator. Посмотрим, как эта система работает. Необходимо создать объект, который будет выполнять проверку, пусть нам надо проверить строку на то, состоит ли она из одних цифр, есть ли в ней слэши и на размер строки:

$Checker = new DigitsChecker(new BackSlashChecker(new StringChecker()));

Теперь просто вызовем метод Check:

$Checker->Check($S); // $S — строка для проверки

Проследим цепочку вызовов. Объект $Checker является объектом класса DigitsChecker, поэтому сначала будет произведена проверка на наличие символов, отличных от цифр. Но в конце этого метода стоит строка, которая вызывает родительский метод Check

parent::Check($StringToCheck);

Метод Check класса Decorator вызывет BackSlashChecker::Check, а он в свою очередь вызывет StringChecker::Check, на чем проверка и закончится.
Заключение

Применение шаблона проектирования Decorator позволило создать, достаточно гибкую и надежную систему проверки. Для создания нового типа проверки достаточно просто создать новый класс от класса декоратор и переопределить метод Check. Проверки можно применять в любой последовательности, которая определяется при создании объекта проверки. Если необходимо, чтобы последовательность проверок была обратной, то есть первой выполнялась проверка StringChecker::Check, надо просто поместить вызов метода Decorator::Check($StringToCheck) в начале методов проверки.