Безопасность

[/b]

[b]Предисловие

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

Уровни безопасности
Многие программисты задумываясь о безопасности системы представляют единый, универсальный механизм, позволяющий защитить всю систему путем вызова (на пример) функции toDoWell или метода $security->work(), но к сожалению, такой подход не оправдывает себя, в результате получается комок грязного кода, который не защищает систему совсем. Подобная проблема имеет те же корни, что и все комки грязного кода в программировании - божественный объект. Данный антипаттерн говорит, что не следует использовать один механизм для решения несвязанных задач, даже если во всех задачах присутствует слово - безопасность.

Давайте, предварительно, разделим возможные опасности, которые грозят системе, на части:
1. Аппаратный отказ - выход из строя серверов, линий связи, DDoS и т.д. Никакой скрипт вам тут не поможет, нужно либо правильно настраивать сервер, либо обращаться за помощью к администраторам. Об этой проблеме в этой статье мы говорить не будем;
2. Подмена данных - отсутствие верификации (фильтрации) поступающих от пользователя данных, что часто приводит к xss, sql inj и другим не приятным проблемам;
3. Доступ - получение пользователем права выполнять операцию, которую ему выполнять нельзя, на пример изменять пароль другого пользователя;
4. Видимость - данный вид опасности наименее опасный, он скорее служит для улучшения восприятия информации пользователем. Сюда относится показ разным пользователям разной информации, на пример администратор видит рычаги управления системой, а пользователь только упрощенный интерфейс без лишних кнопок.

Последние три проблемы могут и должны быть решены программными средствами. Начнем с начала.

Доступность
Если мы взглянем на клиент-серверную систему (web сайт) как на клиент-серверную систему, то заметим, что здесь участвует два механизма: клиент - который передает данные; и сервер - который эти данные принимает и обрабатывает. Передаваемые от клиента серверу данные назовем командой. Так, команда addUser Baskka 123 переданная на сервер позволяет создать нового пользователя с логином Bashka и паролем 123, а команда removeUser Bashka удалить этого пользователя. Имя команды и передаваемые с ней параметры назовем семантикой команды. Зная семантику команды, мы можем определить данные, которые эта команда должна принимать, и данные, которые эта команда принимать не должна. На пример команда addUser в первом параметре должна принимать логин пользователя, при этом необходимо заранее определить, какие символы могут входить в логин, а какие нет. На пример мы запретим пользователям регистрировать учетные записи с символами, отличными от A-Za-z_0-9. Чем меньше символов могут использовать пользователи (особенно системных), тем защищеннее получится система. Попробуйте вставить sql inj или xss используя только A-Za-z0-9 ;) Назовем доступный набор символов параметра его маской верификации. Верификацией же назовем процесс проверки данных на соответствие маске верификации. Если данные содержат символы, которых нет в маске верификации, верификация считается не пройденной.

Сольем полученное:
1. Команда - обращение к серверу;
2. Параметры команды - передаваемые серверу данные;
3. Маска верификации параметра - символы, которые могут входить в параметр;
4. Верификация параметра - проверка параметра на предмет наличия в нем символов, отсутствующих в маске верификации.

Запишем команду в более расширенном виде:
Login = [A-Za-z_0-9]
Pass = [A-Za-z_0-9*?]
addUser Login Pass
Видно, что команда принимает только определенные данные. Если мы попробуем передать в нее данные недопустимого типа, система вернет ошибку без вреда для себя.

Как реализовать этот подход на практике? В моем случае все реализовано следующим образом:
Существуют классы данных: Integer, String, Float, Alias, FileName, FileAddress и т.д. Каждый класс включает в себя метод isReestablish, который принимает данные и выполняет их верификации в соответствии со своей маской. На пример маской верификации для Boolean является: true|false
Каждый модуль имеет свой контроллер, при чем пользователь может вызывать только методы этих контроллеров. Каждый метод контроллера имеет параметры (как и любой другой метод класса), при чем эти параметры строготипизированы, на пример:
class Controller{
public function addUser(Login $login, Pass $pass){
...
}
}
Когда пользователь передает в систему запрос, он попадает в центральный контроллер. В запросе присутствует следующая информация: имя модуля, имя метода, параметры. Центральный контроллер находит нужный пользователю модуль, получает его контроллер, затем получает метод контроллера и проходя по всем параметрам метода определяет какому типу должны соответствовать данные. Если в процессе этой проверки оказывается, что метод ожидает одни параметры, а пользователь передал другие (путем вызова методы isReestablish), то центральный контроллер возвращает ошибку.

Заметьте, мне не нужно выполнять ручную проверку входящих данные через if и preg_match, все делается автоматически. Так же я могу повторно использовать маски верификации в других контроллерах, а не переписывать код верификации. Безопасность тоже учтена, так как пользователь не сможет передать данные, которые не ожидает метод модуля. Миссия выполнена.

Доступ
Вернемся к клиент-серверной архитектуре. Кашерная (читать - годная) система чаще всего имеет модульную архитектуру (или пакетную), при этом каждый модуль отвечает за свои задачи и обрабатывает свои команды. Очевидно, что не все пользователи должны иметь доступ к имеющимся в системе модулям (как минимум не ко всем командам модуля), потому встает вопрос - как запретить пользователю доступ к модулю?

Существует несколько способов ограничения доступа и все они сводятся к одному - давать или запрещать доступ одному пользователю к одной операции. Выглядит это примерно так: Bashka !addUser,removeUser - пользователю Bashka запрещен доступ к операциям addUser и removeUser. Конечно процесс разграничения доступа пользователям к командам превратиться в муку, если не объединить правила в роли: User !addUser,removeUser; Admin - роли User не доступные команды создания и удаления пользователей, а для админа запретов нет. Теперь достаточно дать определенные роли определенным пользователям и дело сделано! Если пользователь передаст команду серверу, тот должен проверить, разрешена ли обработка команды сервером или установлен запрет.

Как реализовать этот подход на практике? В моем случае все реализовано следующим образом:
Существуют модули Users и Access. Первый отвечает за учет пользователей системы, второй за разграничения прав. Модуль Access включает такие сущности как Role и Rule. Первая определяет роли, которые можно назначить пользователю, вторая права доступа - что пользователю делать нельзя (имя модуля и имя метода, который нельзя вызывать данному пользователю). Так, если мы создадим Rule вида module:Users,action:addUser и расширим ей Role User, то все пользователи с данной ролью не смогут вызывать метод addUser модуля Users.

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

Видимость
Что будет, если пользователь откроет сайт и увидит кнопку "Настроить" или "RemoveAllUser"? Думаю он на нее нажмет! Не важно что система никак на это не отреагирует (пользователю ведь не дан доступ к ненужным методам модулей?!), само наличие лишних кнопок напрягает. Потому необходимо реализовать механизм сокрытия ненужных компонентов интерфейса от пользователя. Реализуется это несколькими способами, самый простой из которых - проверять какие роли определены для пользователи и в зависимости от этого скрывать или показывать компоненты экрана. Пользователь конечно сможет окольными путями увидеть запрещенные компоненты, но это никак не навредит системе, так как на уровне сервера доступ к командам пользователю закрыт.

Как реализовать этот подход на практике? К сожалению описать подробно не успею, потому предложите вариант в комментариях ;)

Послесловие
Защищать систему можно многими способами, но запомните - каждая опасность это отдельная задача, которую должен решать свой механизм.
Удачи!

URL: https://visavi.net/articles/490