A free and open-source book on ZF3 for beginners


11.1. Элементы безопасности форм

Мы рассмотрим использование двух предоставляемых Zend Framework 3 элементов безопасности форм: Captcha и Csrf[Zend\Form\Element\Csrf] (оба класса принадлежат пространству имен Zend\Form\Element). Добавление этих элементов на модель формы (и их визуализация в шаблоне представления) сделает вашу форму устойчивой к хакерским атакам.

11.1.1. CAPTCHA

CAPTCHA (расшифровывается как "Completely Automated Public Turing test to tell Computers and Humans Apart" - "полностью автоматизированный публичный тест Тьюринга для различения компьютеров и людей") - это тест типа «вызов - ответ», используемый веб-сайтами, чтобы определить, кем является пользователь: человеком или роботом.

Существует несколько типов CAPTCHA. Самый распространенный требует от пользователя ввести с клавиатуры буквы с искаженного изображения, показываемого на веб-странице (см. рисунок 11.1).

Рисунок 11.1. Примеры CAPTCHA Рисунок 11.1. Примеры CAPTCHA

Типичный тест CAPTCHA использует следующий алгоритм:

  1. На стороне сервера генерируется некая скрытая последовательность символов (слово).
  2. Секретное слово сохраняется в переменной сессии PHP.
  3. На основе секретного слова генерируется искаженное изображение. Затем оно отображается пользователю сайта на веб-странице.
  4. Пользователю предлагается ввести показанные на изображении символы.
  5. Если введенные пользователем символы совпадают с секретным словом, сохраненным в сессии, тест считается пройденным.

Целью теста CAPTCHA является защита форм от заполнения и отправки автоматическим процессом (так называемым роботом). Обычно такие роботы рассылают спам-сообщения на форумах, взламывают пароли на формах входа на сайт или совершают какие-либо другие вредоносные действия.

Тест CAPTCHA позволяет надежно различать людей и роботов, потому что люди легко могут распознать и воспроизвести символы с искаженного изображения, в отличие от роботов, которые этого не умеют (на данном этапе эволюции алгоритмов компьютерного зрения).

11.1.1.1. Типы CAPTCHA

В Zend Framework 3 доступно несколько типов CAPTCHA (все принадлежат компоненту Zend\Captcha):

Компонент Zend\Captcha предоставляет единообразный интерфейс для всех типов CAPTCHA (интерфейс AdapterInterface). Базовый класс AbstractAdapter реализует этот интерфейс, и все остальные алгоритмы CAPTCHA наследуются от абстрактного класса адаптера 45. Диаграмма наследования классов показана ниже на рисунке 11.2.

Рисунок 11.2. Классы адаптеров CAPTCHA Рисунок 11.2. Классы адаптеров CAPTCHA

45) Адаптер - это шаблон проектирования, который преобразовывает интерфейс класса в совместимый интерфейс, что позволяет двум (или нескольким) несовместимым интерфейсам работать вместе. Как правило, у алгоритмов CAPTCHA различаются public-методы, но, так как они все реализуют интерфейс AbstractAdapter, их все можно использовать одним и тем же способом (вызывая методы, предоставляемые базовым интерфейсом)

Как видите из рисунка 11.2, существует еще один базовый класс для всех типов CAPTCHA, который использует некое секретное слово из символов: класс AbstractWord. Этот базовый класс предоставляет методы для генерации случайной последовательности символов и для настройки опций генерации слов.

11.1.1.2. Элемент формы CAPTCHA и помощник вида

ZF3 предоставляет специальный класс элемента формы и класс помощника вида для использования полей CAPTCHA на формах.

Чтобы добавить поле CAPTCHA к модели формы, используйте класс Captcha, который принадлежит компоненту Zend\Form и "живет" в пространстве имен Zend\Form\Element.

Класс элемента Captcha может быть использован с любым алгоритмом CAPTCHA (перечислены в предыдущем разделе) из компонента Zend\Captcha. Для этой цели, класс элемента имеет метод setCaptcha(), который принимает либо экземпляр класса, реализующего интерфейс Zend\Captcha\AdapterInterface, либо массив, содержащий настройку CAPTCHA 46. Методом setCaptcha() можно добавить к элементу желаемый тип CAPTCHA.

46) Во втором случае (описание в виде массива) алгоритм CAPTCHA будет автоматически инстанцирован и инициализирован классом фабрики Zend\Captcha\Factory.

Элемент Captcha добавляется к модели формы обычным способом, через метод add(), предоставляемый базовым классом Zend\Form\Form. Как обычно, вы можете передать методу либо экземпляр класса Zend\Form\Element\Captcha, либо массив опций конфигурации для конкретного алгоритма CAPTCHA (в этом случае элемент и его алгоритм CAPTCHA будут автоматически инстанцированы и настроены классом фабрики).

Пример кода ниже показывает, как использовать второй способ (передача конфигурации в виде массива). Мы предпочитаем его, так как он требует меньше кода. Предполагается, что вы вызываете этот код внутри protected-метода addElements() модели формы:

<?php
// Добавляем поле CAPTCHA к модели формы
$this->add([
  'type'  => 'captcha',
  'name' => 'captcha',
  'options' => [
    'label' => 'Human check',
    'captcha' => [
      'class' => '<captcha_class_name>', //
      // Здесь идут опции для конкретного класса ...
    ],
  ],
]);

В этом фрагменте мы вызываем метод add(), предоставляемый базовым классом Form, и передаем ему описание элемента, который хотим добавить, в виде массива (строка 3):

Для генерации HTML-разметки для элемента, можно использовать класс помощника вида FormCaptcha (принадлежащий пространству имен Zend\Form\View\Helper). Однако, как вы могли узнать из предыдущей главы, как правило, вместо этого используется общий помощник вида FormElement, как показано в коде ниже:

<?= $this->formElement($form->get('captcha')); ?>

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

Далее мы приведем два примера, показывающие, как использовать два разных типа CAPTCHA, предоставляемых ZF3: Image и Figlet. Мы покажем, как добавить поле CAPTCHA к форме обратной связи, которую мы использовали в примерах предыдущих глав.

11.1.1.3. Пример 1: Добавление CAPTCHA типа Image к ContactForm

Для CAPTCHA типа Image требуется установленное PHP-расширение GD с поддержкой PNG и шрифтами FT.

Чтобы добавить CAPTCHA Image к модели формы, вызовите метод формы add() как показано ниже:

<?php
namespace Application\Form;
// ...

class ContactForm extends Form
{
    // ...    
    protected function addElements() 
    {
        // ...         
       
        // Add the CAPTCHA field
        $this->add([
            'type'  => 'captcha',
            'name' => 'captcha',
            'attributes' => [
            ],
            'options' => [
                'label' => 'Human check',
                'captcha' => [
                    'class' => 'Image',
                    'imgDir' => 'public/img/captcha',
                    'suffix' => '.png',
                    'imgUrl' => '/img/captcha/',
                    'imgAlt' => 'CAPTCHA Image',
                    'font'   => './data/font/thorne_shaded.ttf',
                    'fsize'  => 24,
                    'width'  => 350,
                    'height' => 100,
                    'expiration' => 600, 
                    'dotNoiseLevel' => 40,
                    'lineNoiseLevel' => 3
                ],
            ],
        ]);
    }
}

В этом фрагменте ключ массива конфигурации captcha (строка 20) содержит следующие параметры для настройки алгоритма CAPTCHA Image, присоединенного к элементу формы:

Для визуализации поля CAPTCHA добавьте следующие строки в файл шаблона представления contact-us.phtml :

<div class="form-group">
  <?= $this->formLabel($form->get('captcha')); ?>
  <?= $this->formElement($form->get('captcha')); ?>
  <?= $this->formElementErrors($form->get('captcha')); ?>
  <p class="help-block">Введите буквы, которые вы видите на изображении выше.<p>
</div>

Теперь создадим каталог APP_DIR/public/img/captcha, который будет хранить сгенерированные изображение CAPTCHA. Отрегулируем права доступа к каталогу, чтобы он был доступным для записи для веб-сервера Apache. В Linux Ubuntu это, как правило, совершается следующими командами оболочки (замените плейсхолдер APP_DIR именем каталога вашего веб-приложения):

mkdir APP_DIR/public/img/captcha

chown -R www-data:www-data APP_DIR

chmod -R 775 APP_DIR

Команда mkdir создает каталог, а команды chown и chmod соответственно назначают пользователя Apache владельцем каталога и позволяют веб-серверу записывать данные в каталог.

Если вы теперь откроете страницу "http://localhost/contactus" в своем браузере, будет сгенерировано изображение CAPTCHA, основанное на случайной последовательности букв и цифр, сохраненных в сессии. Вы должны будете увидеть что-то вроде рисунка 11.3.

Рисунок 11.3. CAPTCHA типа Image Рисунок 11.3. CAPTCHA типа Image

Когда вы заполните поля формы и нажмете кнопку Submit, введенные в поле Human check буквы будут переданы серверу как часть HTTP-запроса. Затем, при валидации формы, класс Zend\Form\Element\Captcha сравнит отправленные буквы с теми, что хранятся в PHP-сессии. Если буквы идентичны, форма считается действительной; иначе форма не проходит валидацию.

Как только визуализатор PHP обработает шаблон представления, он сгенерирует HTML-разметку для элемента CAPTCHA как показано ниже:

<div class="form-group">
  <label for="captcha">Human check</label>
  <img width="350" height="100" alt="CAPTCHA Image" 
       src="/img/captcha/df344b37500dcbb0c4d32f7351a65574.png">
  <input name="captcha[id]" type="hidden" 
         value="df344b37500dcbb0c4d32f7351a65574">
  <input name="captcha[input]" type="text">                              
  <p class="help-block">Введите буквы, которые вы видите на изображении выше.</p>
</div>

11.1.1.4. Пример 2: Добавление CAPTCHA типа FIGlet к ContactForm

Для использования на форме элемента CAPTCHA типа FIGlet, замените определение элемента формы из предыдущего примера следующим кодом:

<?php
// Добавляем поле CAPTCHA
$this->add([
	'type'  => 'captcha',
	'name' => 'captcha',
	'attributes' => [                                                
	],
	'options' => [
		'label' => 'Human check',
		'captcha' => [
			'class' => 'Figlet',
			'wordLen' => 6,
			'expiration' => 600,                     
		],
	],
]);

В этом фрагменте ключ массива конфигурации captcha (строка 10) содержит следующие параметры для настройки алгоритма CAPTCHA Figlet, присоединенного к элементу формы:

Теперь откройте страницу "http://localhost/contactus" в своем браузере. Вы должны будете увидеть страницу как на рисунке 11.4 ниже.

Рисунок 11.4. CAPTCHA типа FIGlet Рисунок 11.4. CAPTCHA типа FIGlet

Как только визуализатор PHP обработает шаблон представления, он сгенерирует HTML-разметку для элемента CAPTCHA как показано ниже:

<div class="form-group">
  <label for="captcha">Проверка пользователя</label>            
    <pre> 
 __   _    __   __   _    _      ___     _    _    __   __  
| || | ||  \ \\/ // | \  / ||   / _ \\  | || | ||  \ \\/ // 
| '--' ||   \ ` //  |  \/  ||  | / \ || | || | ||   \ ` //  
| .--. ||    | ||   | .  . ||  | \_/ || | \\_/ ||    | ||   
|_|| |_||    |_||   |_|\/|_||   \___//   \____//     |_||   
`-`  `-`     `-`'   `-`  `-`    `---`     `---`      `-`'   
                                                           
</pre>
<input name="captcha[id]" type="hidden" 
       value="b68b010eccc22e78969764461be62714">
<input name="captcha[input]" type="text">                              
<p class="help-block">Введите буквы, которые вы видите на изображении выше.</p>
</div>

11.1.2. Предотвращение подделки межсайтовых запросов (CSRF)

Подделка межсайтовых запросов (Сross Site Request Forgery - CSRF) - тип хакерской атаки, заставляющий браузер пользователя передать HTTP-запрос произвольному сайту. Через атаку CSRF вредоносный скрипт может послать неавторизованные команды от доверенного пользователя. Такая атака, как правило, совершается на страницы, содержащие формы для отправки каких-либо уязвимых данным (например, формы для перевода денег, корзины и т.д.)

Чтобы лучше понять, как работает эта атака, посмотрите на рисунок 11.5.

Рисунок 11.5. Пример CSRF-атаки Рисунок 11.5. Пример CSRF-атаки

Рисунок 11.5 демонстрирует пример CSRF-атаки на веб-сайт платежного шлюза:

  1. Вы заходите на свой аккаунт на сайте платежного шлюза https://payment.com. Заметьте, что в данном случае используется SSL-защищенное соединение (HTTPS), но оно не защищает от такого рода атак.

  2. Как правило, вы устанавливаете флажок формы входа на сайт "Remember Me" (запомнить меня), чтобы избежать слишком частого ввода своих имени пользователя и пароля. Как только вы заходите на свой аккаунт, ваш браузер сохраняет информацию о сессии в переменную cookie на вашем компьютере.

  3. На сайте платежного шлюза вы используете платежную форму https://payment.com/moneytransfer.php для покупки товаров. Обратите внимание, что эта форма позже будет использована в качестве уязвимости, что позволит осуществить CSRF-атаку.

  4. Затем вы используете этот же браузер, чтобы посетить какой-либо сайт. Предположим, это будет веб-сайт с классными картинками http://coolpictures.com. К сожалению, этот сайт заражен вредоносным скриптом, замаскированным под HTML-тег <img src="image.php">. Как только вы откроете HTML-страницу в своем браузере, она загрузит все свои изображения, тем самым выполним вредоносный скрипт image.php.

  5. Вредоносный скрипт проверяет переменную cookie и, если таковая есть, он совершает захват сессии (session riding), что позволяет ему действовать от имени залогиненного пользователя. Теперь скрипт может отправлять данные платежной формы на сайт платежного шлюза.

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

Чтобы предотвратить CSRF-атаку на форму, необходимо требовать специальный токен формы. Эта процедура описана ниже:

To prevent CSRF attacks to a form, one has to require a special token with the form, as follows:

  1. Для определенной формы генерируется случайная последовательность байт (токен) и сохраняется на стороне сервера в данных PHP-сессии.

  2. К форме добавляется скрытое поле, его значение задается токеном.

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

Если вредоносный пользователь попробует атаковать сайт через отправку формы, он не сможет поместить нужный токен в форму перед отправкой, так как токен не хранится в файлах cookie.

11.1.2.1. Пример: добавление к форме элемента CSRF

В Zend Framework 3, чтобы добавить CSRF-защиту к модели формы, используется класс элемента формы Zend\Form\Element\Csrf.

У элемента Csrf нет визуального представления (вы не увидите его на экране).

Чтобы добавить элемент CSRF к модели формы, добавьте следующие строки в метод addElements():

// Добавляем поле CSRF
$this->add([
  'type'  => 'csrf',
  'name' => 'csrf',
  'options' => [                
    'csrf_options' => [
      'timeout' => 600
    ]
  ],
]);

В этом фрагменте мы используем метод add() класса Form (строка 2), которому передается описание элемента CSRF в виде массива. Элемент будет автоматически инстанцирован и инициализирован фабрикой.

В строке 3 мы указываем имя класса для элемента CSRF. Это может быть либо полностью определенное имя класса (Zend\Form\Element\Csrf), либо псевдоним ("csrf").

В строке 4 мы задаем атрибут "name" для элемента. В этом примере мы используем имя "csrf", но вы можете использовать любое другое имя по вашему выбору.

В строке 6, внутри массива csrf_options, мы указываем опции для класса Zend\Form\Element\Csrf. Мы задаем значение опции timeout, равное 600 (строка 7), что значит, срок действия CSRF-проверки закончится спустя 600 секунд (10 минут) после создания формы.

Для визуализации поля CSRF добавьте следующую строку в файл шаблона представления .phtml:

<?= $this->formElement($form->get('csrf')); ?>

Как только визуализатор PHP обработает шаблон представления, он сгенерирует HTML-разметку для поля CSRF как показано ниже:

<input type="hidden" name="csrf" value="1bc42bd0da4800fb55d16e81136fe177"> 

Как видите из кода разметки выше, форма теперь содержит скрытое поле со случайно сгенерированным токеном. Так как атакующий скрипт не знает этот токен, он не сможет отправить его корректное значение, тем самым CSRF-атака будет предотвращена.

Что происходит при неудачной валидации элемента CSRF?

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


Top