Стандартная, рекомендуемая к установке сборка 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: ^/adminfirewall), к которой относится запрашиваемый ресурсПосле опознания клиента система должна определить, имеет ли пользователь право получить ответ по сформированному им запросу или нет. Механизм авторизации предусматривает наличие матрицы отображения из множества пользователей в множество ресурсов. Соответственно, имея идентификатор ресурса (полученный после обработки запроса), идентификатор пользователя (полученный в результате аутентификации) и матрицу соответствий, система отвечает на вопрос: «Имеет ли данный пользователь доступ к данному ресурсу?».
Пример конфигурации:
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 и т.д.) и сосредоточиться на самом проекте. Это позволит одни махом решить большую часть потенциальных проблем с безопасностью и существенно сузить круг возможных ошибок.