Стандартная, рекомендуемая к установке сборка Symfony «из коробки» обеспечивает защиту от большинства угроз, актуальных для веба на сегодняшний день. В данной статье я сделаю обзор основных механизмов, делающих разработку на Symfony безопасной.
Автор: Удальцов Валентин,
студент кафедры информационной безопасности
“Высшей школы экономики”
Среди задач, решаемых современными PHP бэкэнд-фреймворками можно выделить три основные:
Symfony – второй по популярности PHP-фреймворк. Он построен вокруг архитектурной парадигмы Model-View-Controller, использует шаблонизатор Twig, Doctrine Object Relation Mapper, предоставляет мощный Dependency Injection Container, включает в себя парсер конфигурации из форматов XML и YAML, конструктор легко валидируемых форм, инструменты для тестирования, кеширования, работы с мультиязычностью, а также продуманную Security-компоненту для работы с аутентификацией и авторизацией пользователей.
Стандартная, рекомендуемая к установке сборка Symfony «из коробки» обеспечивает защиту от большинства угроз, актуальных для веба на сегодняшний день. В данной статье я сделаю обзор основных механизмов, делающих разработку на Symfony безопасной.
Базовый вопрос шаблонизации - это экранирование переменных спецсимволов HTML, то есть преобразование используемых языком разметки спецсимволов в безопасные эквивалентные конструкции.
Вывод в шаблоне введенных пользователем данных без правильного экранирования порождает угрозу XSS-атаки. У злоумышленника появляется возможность эксплуатировать вредоносный код на стороне других клиентов.
<?= $input ?>
<?= htmlspecialchars($input) ?> или <?php class Html { public static function e($var) { return htmlspecialchars($input); } } ?> <?= Html::e($input) ?> <?= Html::e($input2) ?>
Второй подход существенно сократит код, сделает его более наглядным, но не решит фундаментальной проблемы. Разработчик по-прежнему может забыть написать небезопасный код. Базовое решение проблемы безопасности связано с достаточно большими издержками.
Symfony использует шаблонизатор Twig. Помимо того, что Twig предоставляет огромное количества удобных инструментов для организации шаблонов, он задает определенные правила вывода переменных. В частности, при стандартных настройках Twig по умолчанию экранирует спецсимволы:
{{ input }} # выведет экранированную переменную {{ input|raw }} # выведет неэкранированную переменную
SQL-инъекции возможны при отсутствии экранирования переменных при подстановке данных в код запроса.
<?php $mysqli->query("SELECT * FROM users WHERE name = '$input'");
<?php $input = $mysqli->real_escape_string($input); $mysqli->query("SELECT * FROM users WHERE name = '$input'");
Помимо того, что базовое решение опять сопряжено с высокими издержками на написание дополнительного кода, оно оставляет разработчику большой простор для ошибки при формировании самой фразы запроса. В определенных ситуациях неправильно организованный код может породить валидные, но нежелательные запросы, которые нарушат целостность или доступность информации.
Для работы с базой данных Symfony использует сторонний проект Doctrine.
Главная компонента, Doctrine DBAL (Database Abstraction Layer), дает возможность быстро и удобно подставлять экранированные данные в тело запроса.
<?php $sql = 'SELECT * FROM users WHERE name = :name'; $stmt = $conn->prepare($sql); $stmt->bindValue('name', $input); $stmt->execute();
При помощи этой библиотеки можно также конструировать запрос, используя ООП:
<?php $queryBuilder ->select('id', 'email') ->from('users') ->where('name = :name') ->setParameter('name', $input);
Такой подход исключает появление в запросе неожиданных конструкций. В случае нарушения каких-либо структурных правил построения запроса библиотека выдает ошибку на уровне PHP, а не на уровне синтаксического анализатора SQL. Это изолирует данные от ошибочных запросов.
В стандартную сборку Symfony также интегрирована библиотека Doctrine ORM, которая позволяет работать не с самим SQL-запросом или его конструктором, а непосредственно с PHP-объектами. Классы моделей размечаются определенным образом (например, при помощи аннотаций в phpDoc), в результате чего свойства объектов проецируются на колонки таблиц в бд. Взаимодействие между объектом и строкой в бд (создание, редактирование, удаление) происходит автоматически через отлаженные механизмы, что помимо повышения скорости разработки сводит к минимуму возможность ошибки.
<?php use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table(name="user") */ class User { /** * @ORM\Column(type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\Column(type="string", length=100) */ private $name; }
Неотъемлемой частью веб-приложения являются формы. PHP является слабо типизированным языком, поэтому серверная валидация и приведение входящих данных к типам является особо острым вопросом.
Существует множество подходов к проверке входящих данных. Все они используют простые функции вродеis_numeric
, in_array
, filter_var
, preg_match
для проверок на тип или соответствие шаблону. Главная сложность состоит в выработке цельного универсального подхода к валидации форм, который будет игнорировать лишние параметры, максимально контролировать нужные и возвращать после обработки чистые безопасные данные или ошибку.
Валидация в Symfony глубоко интегрирована в сам механизм конструирования форм. Например, при объявлении поля типа выпадающий список
(или группы радиокнопок) обязательным является указание списка опций. Получив заполненную форму, фреймворк автоматически проверяет данные на соответствие предлагавшимся опциям.
<?php $builder->add('gender', ChoiceType::class, array( 'choices' => array( 'Мужской' => 'male', 'Женский' => 'female', ), )); # gender=apple не пройдет
Кроме того, Symfony позволяет размечать модели (свойства которых населяются входящими данными после обработки запроса) различными валидаторами, а затем за пару строк кода получать массив ошибок.
<?php use Symfony\Component\Validator\Constraints as Assert; class User { /** * @Assert\NotBlank * @Assert\Length(min=3) */ private $name; /** * @Assert\Email */ private $email; /** * @Assert\NotBlank * @Assert\Length(min=7) */ private $password; } $user = new User(); # ... $validator = $container->get('validator'); $errors = $validator->validate($author); if (count($errors) > 0) { # ... }
В Symfony по умолчанию включена защита от CSRF-атак. Во все формы автоматически добавляется CSRF token
, уникальный для каждого пользователя. При получении ответа, фреймворк первым делом сверяет полученный токен с токеном из сессии.
Symfony делает акцент на разделении двух ключевых в безопасности понятий – аутентификации и авторизации. Напомню, в чем их принципиальное различие.
Аутентификация – процедура проверки подлинности. Её главная и, по сути, единственная задача состоит в том, чтобы идентифицировать пользователя.
Веб приложение является запросно-ответной системой. В связи с этим в контексте веб-приложения под идентификацией можно понимать установление однозначной связи между текущим запросом и неким объектом пользователя (например, строкой в базе данных в таблице user
).
Визуально аутентификация происходит только на странице ввода логина и пароля. Однако по факту, в той части веб-приложения, которая в принципе предусматривает аутентификацию, она происходит при каждом запросе к серверу. Связано это опять-таки с запросно-ответным принципом работы протокола HTTP. При базовом сценарии после отправки ответа аутентифицированному пользователю соединение разрывается и нет никакого иного способа идентифицировать автора следующего запроса, кроме как аутентифицировать его снова. Разница в том, что после первой успешной парольной аутентификации последующие процедуры (при соблюдении определенных условий) используют другие механизмы, не требующие ввода пароля (например, проверку токена в Cookie).
Из вышесказанного следует, что в предусматривающей аутентификацию части приложения любой запрос должен быть аутентифицирован. Для этого по умолчанию всем пользователем ставится, например, метка anonymous
, которая временно является их идентификатором в системе.
Пример конфигурации:
security: firewalls: public: anonymous: ~ admin: pattern: ^/admin
firewall
), к которой относится запрашиваемый ресурсПосле опознания клиента система должна определить, имеет ли пользователь право получить ответ по сформированному им запросу или нет. Механизм авторизации предусматривает наличие матрицы отображения из множества пользователей в множество ресурсов. Соответственно, имея идентификатор ресурса (полученный после обработки запроса), идентификатор пользователя (полученный в результате аутентификации) и матрицу соответствий, система отвечает на вопрос: «Имеет ли данный пользователь доступ к данному ресурсу?».
Пример конфигурации:
security: access_control: - { path: ^/admin, roles: ROLE_ADMIN }
Symfony использует мандатный принцип контроля доступа. Каждый пользователь имеет своё место в иерархии ролей; для каждого ресурса можно указать, какие роли имеют к нему доступ.
Соответственно, процесс авторизации в Symfony делится на следующие подпроцессы:
Безусловно, Symfony позволяет модифицировать и расширять оба механизма в разных его частях. Например, можно организовать несколько независимых или связанных защищенных зон, реализовать неограниченное количество провайдеров пользователей (имплементировав соответствующие интерфейсы), использовать различные способы аутентификации.
Отдельное внимание в Symfony уделяется криптографии. Через конфигурацию для класса пользователя можно определить любой тип шифрования пароля:
security: encoders: Symfony\Component\Security\Core\User\User: algorithm: bcrypt cost: 12
Я рассмотрел основные инструменты фреймворка Symfony, позволяющие решить главные вопросы безопасности веб-приложения. Стандартная сборка Symfony предусматривает защиту от XSS- и CSRF-атак, SQL-инъекций, включает инструменты быстрой и понятной валидации форм, механизмы аутентификации и авторизации.
Важно понимать, что помимо реализации самих механизмов, немалое значение имеет общая архитектура фреймворка, которая обязывает разработчика следовать определенным канонам проектирования.
Несмотря на большое количество достойных внимания PHP-фреймворков, я знаю людей, которые до сих пор "изобретают свои велосипеды". К сожалению, работая над большими проектами с запутанной бизнес-логикой, невозможно успешно балансировать между написанием инструментов разработки и имплементацией этой логики.
На мой взгляд, гораздо правильнее изучить какой-нибудь достаточно популярный веб-фреймворк (Symfony, Yii, Laravel, Zend и т.д.) и сосредоточиться на самом проекте. Это позволит одни махом решить большую часть потенциальных проблем с безопасностью и существенно сузить круг возможных ошибок.
Собираем и анализируем опыт профессионалов ИБ