Лучшие практики безопасности

Ниже мы рассмотрим основные принципы безопасности и опишем, как избежать угроз при разработке на Yii.

Основные принципы

Есть два основных принципа безопасности, независимо от того, какое приложение разрабатывается:

  1. Фильтрация ввода.
  2. Экранирование вывода.

Фильтрация ввода

Фильтрация ввода означает, что входные данные никогда не должны считаться безопасными и вы всегда должны проверять, являются ли полученные данные допустимыми. Например, если мы знаем, что сортировка может быть осуществлена только по трём полям title, created_at и status, и поле может передаваться через ввод пользователем, лучше проверить значение там, где мы его получили:

$sortBy = $_GET['sort'];
if (!in_array($sortBy, ['title', 'created_at', 'status'])) {
	throw new Exception('Invalid sort value.');
}

В Yii, вы, скорее всего, будете использовать валидацию форм, чтоб делать такие проверки.

Экранирование вывода

Экранирование вывода означает, что данные в зависимости от контекста должны экранироваться, например в контексте HTML вы должны экранировать <, > и похожие специальные символы. В контексте JavaScript или SQL будет другой набор символов. Так как ручное экранирование чревато ошибками, Yii предоставляет различные утилиты для экранирования в различных контекстах.

Как избежать SQL-иньекций

SQL-иньекции происходят, когда текст запроса формируется склеиванием не экранированных строк, как показано ниже:

$username = $_GET['username'];
$sql = "SELECT * FROM user WHERE username = '$username'";

Вместо того, чтобы подставлять корректное имя пользователя, злоумышленник может передать вам в приложение что-то вроде '; DROP TABLE user; --. В результате SQL будет следующий:

SELECT * FROM user WHERE username = ''; DROP TABLE user; --'

Это валидный запрос, который сначала будет искать пользователей с пустым именем, а затем удалит таблицу user. Скорее всего будет сломано приложение и будут потеряны данные (вы ведь делаете регулярное резервное копирование?).

Большинство запросов к базе данных в Yii происходит через Active Record, который правильно использует подготовленные запросы PDO внутри. При использовании подготовленных запросов невозможно манипулировать запросом как это показано выше.

Тем не менее, иногда нужны сырые запросы или построитель запросов. В этом случае вы должны использовать безопасные способы передачи данных. Если данные используются для сравнения со значением столбцов предпочтительнее использовать подготовленные запросы:

// query builder
$userIDs = (new Query())
    ->select('id')
    ->from('user')
    ->where('status=:status', [':status' => $status])
    ->all();

// DAO
$userIDs = $connection
    ->createCommand('SELECT id FROM user where status=:status')
    ->bindValues([':status' => $status])
    ->queryColumn();

Если данные используются в качестве имён столбцов или таблиц, то лучший путь - это разрешить только предопределённый набор значений:

function actionList($orderBy = null)
{
    if (!in_array($orderBy, ['name', 'status'])) {
        throw new BadRequestHttpException('Only name and status are allowed to order by.')
    }
    
    // ...
}

Если это невозможно, то имена столбцов и таблиц должны экранироваться. Yii использует специальный синтаксис для экранирования для всех поддерживаемых баз данных:

$sql = "SELECT COUNT([[$column]]) FROM {{table}}";
$rowCount = $connection->createCommand($sql)->queryScalar();

Вы можете получить подробную информацию о синтаксисе в Экранирование имён таблиц и столбцов.

Как избежать XSS

XSS или кросс-сайтинговый скриптинг становится возможен, когда не экранированный выходной HTML попадает в браузер. Например, если пользователь должен ввести своё имя, но вместо Alexander он вводит <script>alert('Hello!');</script>, то все страницы, которые его выводят без экранирования, будут выполнять JavaScript alert('Hello!');, и в результате будет выводиться окно сообщения в браузере. В зависимости от сайта, вместо невинных скриптов с выводом всплывающего hello, злоумышленниками могут быть отправлены скрипты, похищающие личные данные пользователей сайта, либо выполняющие операции от их имени.

В Yii избежать XSS легко. На месте вывода текста необходимо выбрать один из двух вариантов:

  1. Вы хотите вывести данные в виде обычного текста.
  2. Вы хотите вывести данные в виде HTML.

Если вам нужно вывести простой текст, то экранировать лучше следующим образом:

<?= \yii\helpers\Html::encode($username) ?>

Если нужно вывести HTML, вам лучше воспользоваться HtmlPurifier:

<?= \yii\helpers\HtmlPurifier::process($description) ?>

Обратите внимание, что обработка с помощью HtmlPurifier довольно тяжела, так что неплохо бы задуматься о кешировании.

Как избежать CSRF

CSRF - это аббревиатура для межсайтинговой подмены запросов. Идея заключается в том, что многие приложения предполагают, что запросы, приходящие от браузера, отправляются самим пользователем. Это может быть неправдой.

Например, сайт an.example.com имеет URL /logout, который, используя простой GET, разлогинивает пользователя. Пока это запрос выполняется самим пользователем - всё в порядке, но в один прекрасный день злоумышленники размещают код <img src="http://an.example.com/logout"> на форуме с большой посещаемостью. Браузер не делает никаких отличий между запросом изображения и запросом страницы, так что когда пользователь откроет страницу с таким тегом img, браузер отправит GET запрос на указанный адрес, и пользователь будет разлогинен.

Вот основная идея. Можно сказать, что в разлогировании пользователя нет ничего серьёзного, но с помощью этой уязвимости, можно выполнять опасные операции. Представьте, что существует страница http://an.example.com/purse/transfer?to=anotherUser&amount=2000, обращение к которой с помощью GET запроса, приводит к перечислению 2000 единиц валюты со счета авторизированного пользователя на счет пользователя с логином anotherUser. Учитывая, что браузер для загрузки контента отправляет GET запросы, можно подумать, что разрешение на выполнение такой операции только POST запросом на 100% обезопасит от проблем. К сожалению, это не совсем правда. Учитывайте, что вместо тега , злоумышленник может внедрить JavaScript код, который будет отправлять нужные POST запросы на этот URL.

Для того, чтоб избежать CSRF вы должны всегда:

  1. Следуйте спецификациям HTTP, например запрос GET не должен менять состояние приложения.
  2. Держите защиту CSRF в Yii включенной.

Как избежать нежелательного доступа к файлам

По умолчанию, webroot сервера указывает на каталог web, где лежит index.php. В случае использования виртуального хостинга, это может быть недостижимо, в конечном итоге весь код, конфиги и логи могут оказаться в webroot сервера.

Если это так, то нужно запретить доступ ко всему, кроме директории web. Если на вашем хостинге такое невозможно, рассмотрите возможность смены хостинга.

Как избежать вывода информации отладки и инструментов в рабочем режиме

В режиме отладки, Yii отображает довольно подробные ошибки, которые полезны во время разработки. Дело в том, что подробные ошибки удобны для нападающего, так как могут раскрыть структуру базы данных, параметров конфигурации и части вашего кода. Никогда не запускайте приложения в рабочем режиме с YII_DEBUG установленным в true в вашем index.php.

Вы никогда не должны включать Gii или Debug панель в рабочем режиме. Это может быть использованно для получения информации о структуре базы данных, кода и может позволить заменить файлы, генерируемые Gii автоматически.

Следует избегать включения в рабочем режиме панели отладки, если только в этом нет острой необходимости. Она раскрывает всё приложение и детали конфигурации. Если Вам всё-таки нужно запустить панель отладки в рабочем режиме, проверьте дважды, что доступ ограничен только вашими IP-адресами.

Далее по теме читайте:

Использование безопасного подключения через TLS

Yii предоставляет функции, которые зависят от куки-файлов и/или сессий PHP. Они могут быть уязвимыми, если Ваше соединение скомпрометированно. Риск снижается, если приложение использует безопасное соединение через TLS (часто называемое как SSL).

Инструкции по настройке смотрите в документации к Вашему веб-серверу. Вы также можете проверить примеры конфигураций предоставленные проектом H5BP:

Безопасная конфигурация сервера

Цель этого раздела - выявить риски, которые необходимо учитывать при создании конфигурации сервера для обслуживания веб-сайта на основе Yii. Помимо перечисленных здесь пунктов есть и другие параметры, связанные с безопасностью, которые необходимо учитывать, поэтому не рассматривайте этот раздел как завершенный.

Как избежать атаки типа Host-header

Классы типа yii\web\UrlManager и yii\helpers\Url могут использовать запрашиваемое имя хоста] для генерации ссылок. Если веб-сервер настроен на обслуживание одного и того же сайта независимо от значения заголовка Host, эта информация может быть ненадежной и может быть подделана пользователем, отправляющим HTTP-запрос. В таких ситуациях Вы должны либо исправить конфигурацию своего веб-сервера, чтобы обслуживать сайт только для указанных имен узлов, либо явно установить или отфильтровать значение, установив свойство hostInfo компонента приложения request.

Дополнительные сведения о конфигурации сервера смотрите в документации Вашего веб-сервера:

Если у Вас нет доступа к конфигурации сервера, Вы можете настроить фильтр yii\filters\HostControl уровня приложения для защиты от такого рода атак:

// Файл конфигурации веб-приложения
return [
    'as hostControl' => [
        'class' => 'yii\filters\HostControl',
        'allowedHosts' => [
            'example.com',
            '*.example.com',
        ],
        'fallbackHostInfo' => 'https://example.com',
    ],
    // ...
];

Примечание: Вы всегда должны предпочесть конфигурацию веб-сервера для защиты от атак заголовков узла вместо использования фильтра.

[[yii\filters\HostControl]] следует использовать, только если настройка конфигурации сервера недоступна.