Улучшение защиты веб-приложений с помощью среды Zend Framework 2Источник: wwwibmcom
Веб-приложения уязвимы для атак различного характера, включая SQL-инъекции, XSS-атаки, CSRF-атаки, спам, раскрытие паролей методом грубой силы и т. д. Тем не менее написанное на PHP веб-приложение можно с легкостью защитить от большинства распространенных атак с помощью компонентов безопасности, предоставляемых средой Zend Framework 2. В данной статье демонстрируется использование этих компонентов для повышения безопасности веб-приложений с помощью таких методик, как валидация ввода в формы, фильтрация данных, вводимых ботами, отклонение спама в комментариях и регистрация необычных событий. ВведениеПри создании нового веб-сайта или веб-приложения разработчик не всегда может уделить достаточно времени обдумыванию вопросов безопасности. Сегодня в распоряжении злоумышленников огромное количество способов "прогрызания дыр" в целевое веб-приложение, поэтому при написании программного кода приложения необходимо неукоснительно соблюдать наилучшие методики обеспечения безопасности, чтобы гарантировать защищенность пользователей. Если разработчик применяет язык PHP, эта задача облегчается благодаря среде Zend Framework, которая предоставляет несколько готовых к использованию компонентов для улучшения защиты создаваемого веб-приложения. В этой статье я описываю пять подобных компонентов, которые позволяют разработчику укрепить защиту своего веб-приложения посредством таких механизмов, как валидация вводимой информации, экранирование спецсимволов в выводимой информации, регистрация событий в журнале, тестирование на наличие спама и фильтрация информации от ботов. При совместном использовании эти компоненты служат первой линией обороны вашего приложения от атак злоумышленников. Перейдем к основному материалу статьи. Настройка базового приложенияПрежде чем переходить к программному коду, необходимо упомянуть несколько предупреждений и допущений. На всем протяжении этой статьи предполагается, что ее читатель хорошо владеет базовыми принципами разработки приложений с использованием среды Zend Framework 2.x; понимает взаимодействие между действиями (action), контроллерами (controller) и представлениями (view), а также знаком с реализацией пространства имен в PHP 5.3. Кроме того, предполагается, что в распоряжении читателя имеется действующая среда разработки Apache/PHP, а его веб-сервер Apache сконфигурирован для поддержки виртуального хостинга и перезаписи URL посредством htaccess-файлов. Если вы не знакомы с этими вопросами, обратитесь к дополнительной информации, ссылки на которую приведены в разделе Ресурсы данной статьи. Zend Framework - это слабо связанная среда, поэтому ее компоненты можно использовать как на автономной основе, так и в рамках MVC-реализации этой среды. С учетом того, что различные проекты имеют различные потребности, в данной статье демонстрируется использование этой среды в обоих сценариях. Кроме того, в разделе Материалы для загрузки приведены ссылки на примеры работающего кода. Для начала настройте стандартное приложение Zend Framework 2.x, которое будет служить контекстом для кода, демонстрируемого в этой статье. Для этого загрузите и используйте модуль ZFTool, как показано ниже. shell> php zftool.phar create project example shell> cd example shell> php composer.phar install
Альтернативный подход состоит в том, чтобы в ручном режиме загрузить и извлечь содержимое скелетного приложения Zend Framework в каталог на вашей системе, а затем с помощью Composer загрузить необходимые зависимости. В документации по Zend Framework содержатся дополнительные указания по применению этого подхода; ссылка на документацию приведена в разделеРесурсы . Теперь в вашей конфигурации Apache следует задать новый виртуальный хост для этого приложения, например, Рисунок 1. Начальная страница Скелетное приложения Zend Framework 2
И, наконец, воспользуйтесь инструментом ZFTool для создания нового контроллера (controller) в модуле Application. Этот контроллер содержит примеры кода для данной статьи. shell> php zftool.phar create controller Example Application example/
Теперь, когда все базовые условия соблюдены, перейдем к непосредственному рассмотрению программного кода. Предотвращение инъекционных атак посредством валидации вводимых данныхОбщее правило разработки веб-приложения состоит в том, чтобы никогда не доверять "вслепую" входящей информации, предоставленной пользователем. Фильтрация и валидация входящих данных - это важнейшие методики противодействия инъекционным атакам, которые в обязательном порядке должны применяться ко всей подозрительной входной информации, полученной веб-приложением. Среда Zend Framework предоставляет компонент Zend\InputFilter для фильтрации и валидации входных данных, а также обширный набор валидаторов для типовых вариантов использования. Компонент Zend\InputFilter лучше всего работает с компонентом Zend\Form, но, как будет показано позднее, ее также можно использовать и для валидации информации, вводимой в отдельную форму. Для начала создайте пользовательскую форму с различными полями ввода посредством расширения компонента Zend\Form (см.листинг 1). Я использую эту форму для демонстрации нескольких ключевых характеристик компонента Zend\InputFilter. Листинг 1. Объект формы<?php namespace Application\Form; use Zend\Form\Element; use Zend\Form\Form; use Zend\Form\Fieldset; use Application\Entity\Listing; use Zend\Stdlib\Hydrator\ClassMethods; class ListingForm extends Form { public function __construct() { parent::__construct('listing_form'); $this->setHydrator(new ClassMethods()) ->setObject(new Listing()); $this->add(array( 'name' => 'item_name', 'options' => array( 'label' => 'Item name', ), 'attributes' => array( 'size' => '30' ), 'type' => 'Zend\Form\Element\Text', )); $this->add(array( 'name' => 'seller_name', 'options' => array( 'label' => 'Seller name', ), 'attributes' => array( 'size' => '50' ), 'type' => 'Zend\Form\Element\Text', )); $this->add(array( 'name' => 'seller_email', 'options' => array( 'label' => 'Seller email address', ), 'attributes' => array( 'size' => '50' ), 'type' => 'Zend\Form\Element\Email', )); $this->add(array( 'name' => 'item_min_price', 'options' => array( 'label' => 'Price (min)', ), 'attributes' => array( 'size' => '5' ), 'type' => 'Zend\Form\Element\Text', )); $this->add(array( 'name' => 'item_max_price', 'options' => array( 'label' => 'Price (max)', ), 'attributes' => array( 'size' => '5' ), 'type' => 'Zend\Form\Element\Text', )); $this->add(array( 'name' => 'validity', 'options' => array( 'label' => 'Available until', 'render_delimiters' => false, 'min_year' => date('Y'), 'max_year' => date('Y') + 5 ), 'type' => 'Zend\Form\Element\DateSelect', )); $this->add(array( 'name' => 'submit', 'attributes' => array( 'value' => 'Submit' ), 'type' => 'Zend\Form\Element\Submit', )); } }
В листинге 1 настраивается объект Рисунок 2. Форма ввода данных о товаре
Когда пользователь представляет сведения о товаре, необходимо проверить введенные им данные, чтобы убедиться в их валидности и в отсутствии в них вредоносного кода. Именно этим занимаются компоненты Zend\Filter и Zend\Validate.
Отдельные фильтры и валидаторы можно связывать в цепочки с целью тестирования определенного входного значения; значение будет считаться валидным только после успешного прохождения всех валидаторов и фильтров в цепочке. Компонент Zend\InputFilter позволяет группировать фильтры и валидаторы, что упрощает одновременную валидацию множества вводимых данных, например, нескольких данных, отправленных посредством формы. В листинге 2 на основе компонента Zend\InputFilter создается новый объект ListingFilter, который содержит все фильтры и валидаторы, необходимые для тестирования входящих данных, представленных посредством ListingForm. Листинг 2. Валидаторы и фильтры информации, вводимой в форму<?php namespace Application\Filter; use Zend\InputFilter\InputFilter; class ListingFilter extends InputFilter { public function __construct() { $this->add(array( 'name' => 'item_name', 'required' => true, 'filters' => array( array('name' => 'StripTags'), array('name' => 'StringTrim'), ), 'validators' => array( array( 'name' => 'StringLength', 'options' => array( 'min' => 1, 'max' => 100, ), ), ), )); $this->add(array( 'name' => 'item_min_price', 'required' => true, 'filters' => array( array('name' => 'StripTags'), array('name' => 'StringTrim'), ), 'validators' => array( array( 'name' => 'Zend\I18n\Validator\Float', ), array( 'name' => 'GreaterThan', 'options' => array( 'min' => 0 ) ), ), )); $this->add(array( 'name' => 'item_max_price', 'required' => true, 'filters' => array( array('name' => 'StripTags'), array('name' => 'StringTrim'), ), 'validators' => array( array( 'name' => 'Zend\I18n\Validator\Float', ), array( 'name' => 'GreaterThan', 'options' => array( 'min' => 0 ) ), ), )); $this->add(array( 'name' => 'seller_email', 'required' => true, 'filters' => array( array('name' => 'StripTags'), array('name' => 'StringTrim'), ), 'validators' => array( array( 'name' => 'EmailAddress', ), ), )); $this->add(array( 'name' => 'seller_name', 'required' => true, 'filters' => array( array('name' => 'StripTags'), array('name' => 'StringTrim'), ), )); } }
Конструктор объекта ListingFilter с помощью метода В вашем распоряжении имеется обширный ассортимент из более чем 40 фильтров и 80 валидаторов; кроме того, вам никто не запрещает создавать собственные фильтры и валидаторы (вскоре я продемонстрирую соответствующий пример). В листинге 2 используются фильтр Zend\Filter\StripTags, который удаляет из введенной информации все HTML-элементы, и фильтр Zend\Filter\StringTrim, который удаляет из введенной информации необязательные пробелы. Выбор валидаторов осуществляется на основе характера входящей информации. Соответственно в листинге 2 используются валидатор Zend\Validator\EmailAddress как наиболее подходящий для вводимой информации После задания формы и фильтра необходимо связать их в рамках действия контроллера (controller action) (см. листинг 3). Листинг 3. Обработчик формы<?php namespace Application\Controller; use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\ViewModel; use Application\Form\ListingForm; use Application\Filter\ListingFilter; use Application\Entity\Listing; class ExampleController extends AbstractActionController { public function validateAction() { // generate form and bind to object $form = new ListingForm(); $listing = new Listing(); $form->bind($listing); // set input filters $form->setInputFilter(new ListingFilter()); $request = $this->getRequest(); // validate and display form input if ($request->isPost()) { $form->setData($request->getPost()); if ($form->isValid()) { print_r($listing); exit; } } // render view return new ViewModel(array('form' => $form)); } }
В листинге 3 производится инициализация объекта В листинге 4 содержится скрипт отображения, который визуализирует форму. Листинг 4. Скрипт для отображения формы<h2>Add Listing</h2> <?php // prepare form and set action $form = $this->form; $form->prepare(); $form->setAttribute('action', '/application/example/validate'); $form->setAttribute('method', 'post'); echo $this->form()->openTag($form); ?> <div><?php echo $this->formRow($this->form->get('item_name')); ?></div> <div><?php echo $this->formRow($this->form->get('item_min_price')); ?></div> <div><?php echo $this->formRow($this->form->get('item_max_price')); ?></div> <div><?php echo $this->formRow($this->form->get('seller_name')); ?></div> <div><?php echo $this->formRow($this->form->get('seller_email')); ?></div> <div><?php echo $this->formRow($this->form->get('validity')); ?></div> <div><?php echo $this->formRow($this->form->get('submit')); ?></div> <?php echo $this->form()->closeTag($form); ?>
На рис. 3 показан процесс валидации с использованием компонентов Zend\Form и Zend\InputFilter, которые совместно обнаруживают невалидную введенную информацию. На этом рисунке также показан результат успешного представления формы. Рисунок 3. Валидация и визуализация формы
Сущность Listing в листинге 3 используется только для удобства. Сущности, введенные при посредстве объекта ListingForm, связаны с пользовательским объектом Listing, который представляет каждое введенное значение Listing как объект и предоставляет getter- и setter-методы для упрощения доступа к свойствам объекта. Для краткости код сущности Listing в статье не приводится, однако его можно найти в архиве кода, прилагаемом к статье. Если вас не устраивают готовые валидаторы, можно создать собственный валидатор - расширив валидатор Zend\Validator\AbstractValidator (для сложных вариантов использования) или используя Zend\Validator\Callback для исполнения пользовательской функции валидации (для более простых вариантов использования). В качестве иллюстрации рассмотрим листинг 5, в котором в объект ListingFilter введен обратный вызов валидации для проверки условия, согласно которому максимальная цена всегда выше, чем минимальная цена. Листинг 5. Обратный вызов пользовательской функции валидации<?php namespace Application\Filter; use Zend\InputFilter\InputFilter; class ListingFilter extends InputFilter { public function __construct() { // other inputs and validators $this->add(array( 'name' => 'item_max_price', 'required' => true, 'filters' => array( array('name' => 'StripTags'), array('name' => 'StringTrim'), ), 'validators' => array( array( 'name' => 'Zend\I18n\Validator\Float', ), array( 'name' => 'GreaterThan', 'options' => array( 'min' => 0 ) ), array( 'name' => 'Callback', 'options' => array( 'messages' => array( \Zend\Validator\Callback::INVALID_VALUE => 'The maximum price is less than the minimum price', ), 'callback' => function($value, $context=array()) { $maxPrice = $value; $minPrice = $context['item_min_price']; $isValid = $maxPrice >= $minPrice; return $isValid; }, ), ), ), )); } }
В листинге 5 функция обратного вызова проверяет текущий контекст, извлекает значение Рисунок 4. Валидация формы с зависимыми полями
Конечно, компоненты Zend\InputFilter, Zend\Filter и Zend\Validate можно использовать и автономно. В качестве иллюстрации рассмотрим листинг 6, который содержит простую форму с полями для имени, возраста и номера кредитной карты. Листинг 6. Форма<html> <head></head> <body> <form method="post" action="register.php"> <div> Name <br/> <input type="text" name="name" size="30" /> </div> <div> Age <br/> <input type="text" name="age" size="3" /> </div> <div> Credit card number <br/> <input type="text" name="cnum" size="25" /> </div> <div> <input type="submit" name="submit" value="Submit"> </div> </form> </body>
В листинге 7 настраивается валидация для формы, показанной в листинге 6. Листинг 7. Обработчик формы с фильтрами и валидаторами<?php require_once 'Zend/Loader/StandardAutoloader.php'; $autoloader = new Zend\Loader\StandardAutoloader(array( 'fallback_autoloader' => true, )); $autoloader->register(); use Zend\InputFilter\InputFilter; use Zend\InputFilter\Input; use Zend\Validator; use Zend\I18n; $name = new Input('name'); $name->getFilterChain() ->attachByName('StripTags') ->attachByName('StringTrim'); $name->getValidatorChain() ->addValidator(new Validator\StringLength(array( 'min' => '1', 'max' => '100' ))); $age = new Input('age'); $age->getFilterChain() ->attachByName('StripTags') ->attachByName('StringTrim'); $age->getValidatorChain() ->addValidator(new Validator\GreaterThan(array( 'min' => '0' ))) ->addValidator(new I18n\Validator\Float()); $cnum = new Input('cnum'); $cnum->getFilterChain() ->attachByName('StripTags') ->attachByName('StringTrim'); $cnum->getValidatorChain() ->addValidator(new Validator\CreditCard()); $inputFilter = new InputFilter(); $inputFilter->add($name) ->add($age) ->add($cnum) ->setData($_POST); if ($inputFilter->isValid()) { print_r($inputFilter->getValues()); } else { echo "The form is not valid.<br/>"; foreach ($inputFilter->getInvalidInput() as $invalidInput) { echo $invalidInput->getName() . ': ' . implode(',',$invalidInput->getMessages()) . '<br/>'; } }
В листинге 7 сначала настраивается автозагрузчик Zend Framework, который загружает компоненты среды Zend Framework по мере необходимости. Обратите внимание: чтобы все это работало, библиотеки Zend Framework должны быть указаны в переменной include вашей PHP-среды. После конфигурирования автозагрузчика следующий шаг состоит в создании объектов Zend\Filter\Input (по одному для каждой входной переменной) и в подключении к ним фильтров и валидаторов. В листинге 7 используются многие из тех же фильтров и валидаторов, которые присутствовали в листинге 2; примечательным дополнением является валидатор Zend\Validator\CreditCard, который упрощает проверку номера кредитной карты на внутреннюю непротиворечивость. После описания входных объектов они группируются в объект Zend\InputFilter и заполняются данными из суперглобальной переменной На рис. 5 показан результат ввода в форму валидной и невалидной информации. Рисунок 5. Валидация формы и визуализация результатов
Фильтрация запросов ботов с помощью CAPTCHAРазмещенные в публичном Интернете формы также уязвимы для т. н. ботов (bot), способных заполнять формы. Боты в автоматическом режиме отправляют тысячи форм, как правило, с целью внесения спамового контента в базу данных вашего приложения. Кроме того, боты используются для взлома паролей пользователей, посредством ввода множества строковых комбинаций в формы авторизации (атака методом грубой силы). Простейший способ остановить деятельность ботов - добавить к своей форме механизм под названием CAPTCHA. В состав среды Zend Framework входит компонент Zend\Captcha, предназначенный именно для этой цели. Компонент Zend\Captcha позволяет добавить к вашей форме механизм CAPTCHA на основе figlet-текста или изображения, а также поддерживает веб-сервис reCAPTCHA для дистанционной генерации CAPTCHA. Обычно компонент Zend\Captcha наилучшим образом работает с компонентом Zend\Form(см. листинг 8). Листинг 8. Объект формы с CAPTCHA на основе изображения<?php namespace Application\Form; use Zend\Form\Element; use Zend\Form\Form; use Zend\Captcha\Image; use Zend\Captcha\AdapterInterface; class MessageForm extends Form { protected $captcha; public function __construct() { parent::__construct('message_form'); $this->captcha = new Image(array( 'expiration' => '300', 'wordlen' => '7', 'font' => 'data/fonts/arial.ttf', 'fontSize' => '20', 'imgDir' => 'public/captcha', 'imgUrl' => '/captcha' )); $this->add(array( 'name' => 'name', 'options' => array( 'label' => 'Name', ), 'attributes' => array( 'size' => '50' ), 'type' => 'Zend\Form\Element\Text', )); $this->add(array( 'name' => 'email', 'options' => array( 'label' => 'Email address', ), 'attributes' => array( 'size' => '50' ), 'type' => 'Zend\Form\Element\Email', )); $this->add(array( 'name' => 'message', 'options' => array( 'label' => 'Message', ), 'attributes' => array( 'rows' => '10', 'cols' => '50' ), 'type' => 'Zend\Form\Element\Textarea', )); $this->add(array( 'name' => 'captcha', 'options' => array( 'label' => 'Verification', 'captcha' => $this->captcha, ), 'type' => 'Zend\Form\Element\Captcha', )); $this->add(array( 'name' => 'submit', 'attributes' => array( 'value' => 'Submit' ), 'type' => 'Zend\Form\Element\Submit', )); } }
В листинге 8 создается простая контактная форма с полями для ввода имени, адреса электронной почты и верификации с использованием CAPTCHA. Контент CAPTCHA генерируется компонентом Zend\Captcha\Image, который принимает несколько конфигурационных параметров (длина CAPTCHA-слова, шрифт CAPTCHA-слова и каталог, в котором хранится сгенерированное CAPTCHA-слово). Обратите внимание, что компонент Zend\Captcha\Image использует для генерации CAPTCHA-изображения PHP-расширение GD. Компонент Zend\Captcha автоматически настраивает необходимые валидаторы, поэтому вам остается использовать его в рамках действия контроллера (листинг 9). Листинг 9. Обработчик формы<?php namespace Application\Controller; use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\ViewModel; use Application\Form\MessageForm; use Application\Filter\MessageFilter; class ExampleController extends AbstractActionController { public function captchaAction() { $form = new MessageForm(); $form->setInputFilter(new MessageFilter()); $request = $this->getRequest(); if ($request->isPost()) { $form->setData($request->getPost()); if ($form->isValid()) { print_r($form->getData()); exit; } } return new ViewModel(array('form' => $form)); } }
На рис. 6 показана CAPTCHA на основе изображения в действии. Рисунок 6. Форма с CAPTCHA на основе изображения
Скелетное приложение по умолчанию не загружает компонент ZendService\ReCaptcha автоматически, таким образом, если вы предпочитаете использовать сервис reCAPTCHA, то вам придется обновить конфигурацию Composer для приложения и добавить эту зависимость, как показано ниже. { // other directives "require": { "php": ">=5.3.3", "zendframework/zendframework": "2.2.*", "zendframework/zendservice-recaptcha": "2.*" } }
После этого снова запустите Composer для загрузки необходимых файлов и их установки в ваше приложение. В листинге 10 показан измененный код формы, использующий сервис reCAPTCHA, а на рис. 7 показана сама измененная форма. Обратите внимание, что в процессе конфигурирования объекта Zend\Captcha\ReCaptcha вам необходимо указать свои открытые и закрытые ключи сервиса reCAPTCHA. На случай, если у вас нет этих ключей, в разделе Ресурсы приведена ссылка на веб-сайт сервиса reCAPTCHA, где вы сможете получить их бесплатно. Листинг 10. Форма с интеграцией сервиса reCAPTCHA<?php namespace Application\Form; use Zend\Form\Element; use Zend\Form\Form; use Zend\Captcha\ReCaptcha; use Zend\Captcha\AdapterInterface; class MessageForm extends Form { protected $captcha; public function __construct() { parent::__construct('message_form'); $this->captcha = new Recaptcha(array( 'privKey' => 'YOUR-PRIVATE-KEY', 'pubKey' => 'YOUR-PUBLIC-KEY', )); // other elements }
Рисунок 7. Форма с reCAPTCHA
На случай, если вы предпочитаете использовать компонент Zend\Captcha на автономной основе, в листинге 11 предлагается пример с использованием адаптера Figlet. Листинг 11. Форма и обработчик формы с CAPTCHA на основе figlet-текста<?php require_once 'Zend/Loader/StandardAutoloader.php'; $autoloader = new Zend\Loader\StandardAutoloader(array( 'fallback_autoloader' => true, )); $autoloader->register(); use Zend\InputFilter\InputFilter; use Zend\InputFilter\Input; use Zend\Validator; use Zend\Captcha; $captcha = new Captcha\Figlet(array( 'name' => 'captcha', 'expiration' => '300', 'wordlen' => '7', )); if ($_POST) { $name = new Input('name'); $name->getFilterChain() ->attachByName('StripTags') ->attachByName('StringTrim'); $name->getValidatorChain() ->addValidator(new Validator\StringLength(array( 'min' => '1', 'max' => '100' ))); $message = new Input('message'); $message->getFilterChain() ->attachByName('StripTags') ->attachByName('StringTrim'); $message->getValidatorChain() ->addValidator(new Validator\StringLength(array( 'min' => '1' ))); $verification = new Input('captcha'); $verification->getValidatorChain() ->addValidator($captcha); $inputFilter = new InputFilter(); $inputFilter->add($name) ->add($message) ->add($verification) ->setData($_POST); if ($inputFilter->isValid()) { print_r($inputFilter->getValues()); } else { echo "The form is not valid.<br/>"; foreach ($inputFilter->getInvalidInput() as $invalidInput) { echo $invalidInput->getName() . ': ' . implode(',',$invalidInput->getMessages()) . '<br/>'; } } } else { $id = $captcha->generate(); ?> <html> <head></head> <body> <form method="post" action="message.php"> <div> Name <br/> <input type="text" name="name" size="30" /> </div> <div> Message <br/> <textarea name="message" rows="10" cols="50"></textarea> </div> <div> Verification <br/> <pre><?php echo $captcha->getFiglet()->render( $captcha->getWord()); ?></pre> <input type="hidden" name="captcha[id]" value="<?php echo $id; ?>"> <input type="text" name="captcha[input]" /> </div> <div> <input type="submit" name="submit" value="Submit" /> </div> </form> </body> <?php } ?>
В листинге 11 настраивается автозагрузчик Zend Framework и инициализируется новый объект Zend\Captcha\Figlet. В листинге используется метод На рис. 8 показана форма с визуализированным контентом CAPTCHA. Рисунок 8. Форма с CAPTCHA на основе figlet-текста
Блокирование спама с помощью сервиса AkismetЕще один способ блокировки ввода спама в форму состоит в валидации вводимой информации с помощью хорошо известного веб-сервиса Akismet. Компонент ZendService\Akismet предоставляет сервисный объект, который позволяет с легкостью подключаться к сервису Akismet и с его помощью определять, являются ли конкретные входные данные спамом или полезной информацией. Скелетное приложение по умолчанию не загружает компонент ZendService\Akismet автоматически, поэтому вам необходимо обновить конфигурацию Composer своего приложения и добавить эту зависимость, как показано ниже. { // other directives "require": { "php": ">=5.3.3", "zendframework/zendframework": "2.2.*", "zendframework/zendservice-akismet": "2.*" } }
В листинге 12 демонстрируется использование сервиса Akismet посредством добавления пользовательского обратного вызова для валидации показанной в листинге 8 формы MessageForm с помощью Akismet. Обратите внимание, что в процессе конфигурирования объекта ZendService\Akismet вам необходимо указать свой API-ключ Akismet. На случай, если у вас нет этого ключа, в разделе Ресурсыприведена ссылка на веб-сайт сервиса Akismet, где вы можете получить его бесплатно. Листинг 12. Пользовательский валидационный обратный вызов с интеграцией сервиса Akismet<?php namespace Application\Filter; use Zend\InputFilter\InputFilter; use ZendService\Akismet\Akismet; class MessageFilter extends InputFilter { public function __construct() { // other filters and validators $this->add(array( 'name' => 'message', 'required' => true, 'filters' => array( array('name' => 'StripTags'), array('name' => 'StringTrim'), ), 'validators' => array( array( 'name' => 'Callback', 'options' => array( 'messages' => array( \Zend\Validator\Callback::INVALID_VALUE => 'The message is spam', ), 'callback' => function($value, $context=array()) { $akismet = new Akismet('YOUR-API-KEY', 'http://example.localhost'); $data = array( 'user_ip' => $_SERVER['REMOTE_ADDR'], 'user_agent' => $_SERVER['HTTP_USER_AGENT'], 'comment_type' => '', 'comment_author' => $context['name'], 'comment_author_email' => $context['email'], 'comment_content' => $value ); return ($akismet->isSpam($data)) ? false : true; }, ), ), ) )); } }
Пользовательский валидационный обратный вызов в листинге 12 инициализирует новый объект ZendService\Akismet, передавая ему API-ключ Akismet и URL-адрес приложения. Затем создается массив данных, соответствующий требованиям Akismet. Эти данные включают IP-адрес и браузер пользователя, отправляющего контент, а также имя пользователя и адрес его электронной почты. Этот массив передается в метод На рис. 9 эта валидация показана в действии. Рисунок 9. Валидация формы с помощью сервиса Akismet
Противодействие XSS-атакам посредством экранирования спецсимволов в выходной информацииМежсайтовый скриптинг (Cross-Site Scripting, XSS) - одно из наиболее распространенных направлений атак на веб-приложения. Однако неукоснительное применение экранирования спецсимволов (escaping) и кодирования позволяет сделать приложение полностью неуязвимым к этим атакам. Предпочтительным инструментом в этой сфере является компонент Zend\Escaper, который предлагает пять методов для экранирования спецсимволов в выходной информации в зависимости от того, где эта информация будет присутствовать на визуализируемой странице. Существуют методы для экранирования спецсимволов/кодирования контента в содержимом HTML-страницы, атрибутах HTML-элемента, скриптовых элементах, стилевых элементах и URI. В качестве иллюстрации рассмотрим листинг 13, в котором отображаются данные, полученные в результате отправки формы на веб-странице. Эти данные содержат типичный вектор XSS-атаки, упомянутый на веб-сайте сообщества OWASP; выходные данные отображаются с экранированием спецсимволов и без него, чтобы проиллюстрировать, как применение Zend\Escaper сводит атаку на нет. Листинг 13. Выходные данные с экранированием спецсимволов<?php // предполагается, что данные поступили из запроса $data =<<<'EOT' ';alert(String.fromCharCode(88,83,83))//';alert(String.fromCharCode(88,83,83))//"; alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//-- ></SCRIPT>">'><SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT> EOT; ?> <h2>Escaped Result</h2> <?php echo $this->escapeHtml($data); ?> <h2>Unescaped Result</h2> <?php echo $data; ?>
На рис. 10 показана выходная информация. Как можно увидеть на этом рисунке, вывод данных в браузере без экранирования спецсимволов приводит к демонстрации предупреждения - классический пример XSS-атаки. Рисунок 10. XSS-атака с выходной информацией без экранирования спецсимволов
В дополнение к методу
В приложении среды Zend Framework хелперы представлений (view helper) для всех предыдущих методов автоматически доступны в скриптах представления. В автономном приложении компонент Zend\Escaper необходимо инстанциировать перед использованием (см. листинг 14). Листинг 14. Выходные данные с экранированием спецсимволов<?php require_once 'Zend/Loader/StandardAutoloader.php'; $autoloader = new Zend\Loader\StandardAutoloader(array( 'fallback_autoloader' => true, )); $autoloader->register(); use Zend\Escaper\Escaper; if ($_POST) { $escaper = new Escaper('utf-8'); echo $escaper->escapeHtml($_POST['message']); }
Журналирование событий и ошибок приложенияЖурналы играют важную роль в обеспечении безопасности приложения. Они хранят полезную информацию об использовании вашего приложения, а также могут быть полезны для идентификации или выявления предшествующих нарушений защиты и для сохранения контрольного следа деятельности пользователя. Компонент Zend\Log предоставляет полную среду для регистрации событий приложения, позволяя регистрировать события с файлами, базами данных, адресами электронной почты и отладчиками браузеров. Он поддерживает нескольких выходных форматов (включая пользовательские форматы) и несколько уровней регистрации, что обеспечивает возможность точного контроля над временем и порядком создания журналов. В листинге 15 показан простой пример компонента Zend\Log в действии. Листинг 15. Регистрация событий приложения<?php namespace Application\Controller; use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\ViewModel; use Zend\Log\Writer\Stream; use Zend\Log\Logger; use Zend\Log\Formatter\Xml; class ExampleController extends AbstractActionController { // other methods public function logAction() { $logger = new Logger(); $writer = new Stream('data/log.xml'); $formatter = new Xml(); $writer->setFormatter($formatter); $logger->addWriter($writer); $logger->info('New product added by user'); return false; } }
Действие контроллера в листинге 15 начинается с инициализации нового экземпляра Zend\Log\Logger; это основная точка управления для взаимодействия со средой журналирования. Этот объект передается объекту Zend\Log\Writer\Stream, который используется для записи журнальных данных в PHP-потоки или в URL-адреса файловой системы; в этом случае он инициализируется с маршрутом к выходному журнальному файлу. Существуют и другие средства записи (writer): объект Zend\Log\Writer\Db можно использовать для записи в любую PDO-совместимую базу данных, объект Zend\Log\Writer\Mail - для пересылки журнальных данных по электронной почте, объект Zend\Log\Writer\MongoDB - для записи в базу данных MongoDB, объект Zend\Log\Writer\FirePHP либо объект Zend\Log\Writer\ChromePHP - для записи в браузерную консоль FirePHP или ChromePHP. В свою очередь, объект Zend\Log\Writer\Stream передает объект Zend\Log\Formatter\Xml, который берет на себя форматирование журнальных данных (например, в XML). Как и в случае со средствами записи, средства форматирования (formatter) существуют и для других целей, включая таблицы базы данных, FirePHP и ChromePHP; вы также можете создать собственные средства форматирования. После конфигурирования цепочки "средства журналирования - средства записи - средства форматирования" вам достаточно лишь сгенерировать событие, подлежащее регистрации в журнале. Эта задача с легкостью решается с помощью метода На рис. 11 показан пример XML-записи в журнале. Рисунок 11. XML-запись в журнале
Кроме того, компонент Zend\Log можно использовать для регистрации ошибок приложения с целью последующего анализа и исправления. Для иллюстрации рассмотрим листинг 16, в котором компонент Zend\Log используется в качестве автономного компонента с определяемым пользователем обработчиком PHP-ошибок с целью автоматической регистрации ошибок приложения. Листинг 16. Регистрация ошибок в журнале<?php require_once 'Zend/Loader/StandardAutoloader.php'; $autoloader = new Zend\Loader\StandardAutoloader(array( 'fallback_autoloader' => true, )); $autoloader->register(); use Zend\Log\Writer\Stream; use Zend\Log\Logger; use Zend\Log\Formatter\Xml; error_reporting(0); set_error_handler('userErrorHandler'); function userErrorHandler($errno, $errstr, $errfile, $errline) { $writer = new Stream('error.log'); $logger = new Logger(); $logger->addWriter($writer); $logger->err("$errstr: $errfile: $errline: " . json_encode($_REQUEST)); } // trigger errors echo 5/0; some_undef_func(); ?>
В листинге 16 функция На рис. 12 показан пример записи журнала, сгенерированный текстом в листинге 16: Рисунок 12. Стандартная запись в журнале
ЗаключениеКак показывают показанные выше примеры, среда Zend Framework 2 включает в себя различные компоненты, которые облегчают защиту веб-приложения от инъекций и от XSS-атак, а также регистрацию событий и ошибок приложения в журнале. Это компоненты легко использовать в любом варианте применения среды Zend Framework - в виде MVC-реализации или в виде автономного PHP-приложения. В вашем распоряжении имеются все необходимые инструменты, чтобы приступить к защите своего приложения немедленно - так не теряйте ни минуты! Загрузка
|