mirror of
https://github.com/yiisoft/yii2.git
synced 2025-11-01 20:19:42 +08:00
234 lines
18 KiB
Markdown
234 lines
18 KiB
Markdown
Лучшие практики безопасности
|
||
============================
|
||
|
||
Ниже мы рассмотрим основные принципы безопасности и опишем, как избежать угроз при разработке на Yii.
|
||
|
||
Основные принципы
|
||
-----------------
|
||
|
||
Есть два основных принципа безопасности, независимо от того, какое приложение разрабатывается:
|
||
|
||
1. Фильтрация ввода.
|
||
2. Экранирование вывода.
|
||
|
||
|
||
### Фильтрация ввода
|
||
|
||
Фильтрация ввода означает, что входные данные никогда не должны считаться безопасными и вы всегда должны проверять,
|
||
являются ли полученные данные допустимыми. Например, если мы знаем, что сортировка может быть осуществлена только
|
||
по трём полям `title`, `created_at` и `status`, и поле может передаваться через ввод пользователем, лучше проверить
|
||
значение там, где мы его получили:
|
||
|
||
```php
|
||
$sortBy = $_GET['sort'];
|
||
if (!in_array($sortBy, ['title', 'created_at', 'status'])) {
|
||
throw new Exception('Invalid sort value.');
|
||
}
|
||
```
|
||
|
||
В Yii, вы, скорее всего, будете использовать [валидацию форм](input-validation.md), чтоб делать такие проверки.
|
||
|
||
|
||
### Экранирование вывода
|
||
|
||
Экранирование вывода означает, что данные в зависимости от контекста должны экранироваться, например в контексте
|
||
HTML вы должны экранировать `<`, `>` и похожие специальные символы. В контексте JavaScript или SQL будет другой набор
|
||
символов. Так как ручное экранирование чревато ошибками, Yii предоставляет различные утилиты для экранирования в
|
||
различных контекстах.
|
||
|
||
Как избежать SQL-иньекций
|
||
-------------------------
|
||
|
||
SQL-иньекции происходят, когда текст запроса формируется склеиванием не экранированных строк, как показано ниже:
|
||
|
||
```php
|
||
$username = $_GET['username'];
|
||
$sql = "SELECT * FROM user WHERE username = '$username'";
|
||
```
|
||
|
||
Вместо того, чтобы подставлять корректное имя пользователя, злоумышленник может передать вам в приложение что-то вроде
|
||
`'; DROP TABLE user; --`.
|
||
В результате SQL будет следующий:
|
||
|
||
```sql
|
||
SELECT * FROM user WHERE username = ''; DROP TABLE user; --'
|
||
```
|
||
|
||
Это валидный запрос, который сначала будет искать пользователей с пустым именем, а затем удалит таблицу `user`.
|
||
Скорее всего будет сломано приложение и будут потеряны данные (вы ведь делаете регулярное резервное копирование?).
|
||
|
||
Большинство запросов к базе данных в Yii происходит через [Active Record](db-active-record.md), который правильно
|
||
использует подготовленные запросы PDO внутри. При использовании подготовленных запросов невозможно манипулировать
|
||
запросом как это показано выше.
|
||
|
||
Тем не менее, иногда нужны [сырые запросы](db-dao.md) или [построитель запросов](db-query-builder.md). В этом случае
|
||
вы должны использовать безопасные способы передачи данных. Если данные используются для сравнения со значением
|
||
столбцов предпочтительнее использовать подготовленные запросы:
|
||
|
||
```php
|
||
// 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();
|
||
```
|
||
|
||
Если данные используются в качестве имён столбцов или таблиц, то лучший путь - это разрешить только предопределённый
|
||
набор значений:
|
||
|
||
```php
|
||
function actionList($orderBy = null)
|
||
{
|
||
if (!in_array($orderBy, ['name', 'status'])) {
|
||
throw new BadRequestHttpException('Only name and status are allowed to order by.')
|
||
}
|
||
|
||
// ...
|
||
}
|
||
```
|
||
|
||
Если это невозможно, то имена столбцов и таблиц должны экранироваться. Yii использует специальный синтаксис
|
||
для экранирования для всех поддерживаемых баз данных:
|
||
|
||
```php
|
||
$sql = "SELECT COUNT([[$column]]) FROM {{table}}";
|
||
$rowCount = $connection->createCommand($sql)->queryScalar();
|
||
```
|
||
|
||
Вы можете получить подробную информацию о синтаксисе в [Экранирование имён таблиц и столбцов](db-dao.md#quoting-table-and-column-names).
|
||
|
||
|
||
Как избежать XSS
|
||
----------------
|
||
|
||
XSS или кросс-сайтинговый скриптинг становится возможен, когда не экранированный выходной HTML попадает в браузер.
|
||
Например, если пользователь должен ввести своё имя, но вместо `Alexander` он вводит `<script>alert('Hello!');</script>`, то
|
||
все страницы, которые его выводят без экранирования, будут выполнять JavaScript `alert('Hello!');`, и в результате
|
||
будет выводиться окно сообщения в браузере. В зависимости от сайта, вместо невинных скриптов с выводом всплывающего
|
||
hello, злоумышленниками могут быть отправлены скрипты, похищающие личные данные пользователей сайта,
|
||
либо выполняющие операции от их имени.
|
||
|
||
В Yii избежать XSS легко. На месте вывода текста необходимо выбрать один из двух вариантов:
|
||
|
||
1. Вы хотите вывести данные в виде обычного текста.
|
||
2. Вы хотите вывести данные в виде HTML.
|
||
|
||
Если вам нужно вывести простой текст, то экранировать лучше следующим образом:
|
||
|
||
```php
|
||
<?= \yii\helpers\Html::encode($username) ?>
|
||
```
|
||
|
||
Если нужно вывести HTML, вам лучше воспользоваться HtmlPurifier:
|
||
|
||
```php
|
||
<?= \yii\helpers\HtmlPurifier::process($description) ?>
|
||
```
|
||
|
||
Обратите внимание, что обработка с помощью HtmlPurifier довольно тяжела, так что неплохо бы задуматься о кешировании.
|
||
|
||
Как избежать CSRF
|
||
-----------------
|
||
|
||
CSRF - это аббревиатура для межсайтинговой подмены запросов. Идея заключается в том, что многие приложения предполагают,
|
||
что запросы, приходящие от браузера, отправляются самим пользователем. Это может быть неправдой.
|
||
|
||
Например, сайт `an.example.com` имеет URL `/logout`, который, используя простой GET, разлогинивает пользователя. Пока
|
||
это запрос выполняется самим пользователем - всё в порядке, но в один прекрасный день злоумышленники размещают код
|
||
`<img src="https://an.example.com/logout">` на форуме с большой посещаемостью. Браузер не делает никаких отличий
|
||
между запросом изображения и запросом страницы, так что когда пользователь откроет страницу с таким тегом `img`, браузер отправит GET запрос на указанный адрес, и пользователь будет разлогинен.
|
||
|
||
Вот основная идея. Можно сказать, что в разлогировании пользователя нет ничего серьёзного, но с помощью этой уязвимости, можно выполнять опасные операции. Представьте, что существует страница https://an.example.com/purse/transfer?to=anotherUser&amount=2000, обращение к которой с помощью GET запроса, приводит к перечислению 2000 единиц валюты со счета авторизированного пользователя на счет пользователя с логином anotherUser. Учитывая, что браузер для загрузки контента отправляет GET запросы, можно подумать, что разрешение на выполнение такой операции только POST запросом на 100% обезопасит от проблем. К сожалению, это не совсем правда. Учитывайте, что вместо тега <img>, злоумышленник может внедрить 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-адресами.
|
||
|
||
Далее по теме читайте:
|
||
|
||
- <https://owasp.org/www-project-.net/articles/Exception_Handling.md>
|
||
- <https://owasp.org/www-pdf-archive/OWASP_Top_10_2007.pdf> (A6 - Information Leakage and Improper Error Handling)
|
||
|
||
|
||
Использование безопасного подключения через TLS
|
||
-----------------------------------------------
|
||
|
||
Yii предоставляет функции, которые зависят от куки-файлов и/или сессий PHP. Они могут быть уязвимыми, если Ваше соединение
|
||
скомпрометированно. Риск снижается, если приложение использует безопасное соединение через TLS (часто называемое как [SSL](https://ru.wikipedia.org/wiki/TLS)).
|
||
|
||
Инструкции по настройке смотрите в документации к Вашему веб-серверу. Вы также можете проверить примеры конфигураций
|
||
предоставленные проектом H5BP:
|
||
|
||
- [Nginx](https://github.com/h5bp/server-configs-nginx)
|
||
- [Apache](https://github.com/h5bp/server-configs-apache).
|
||
- [IIS](https://github.com/h5bp/server-configs-iis).
|
||
- [Lighttpd](https://github.com/h5bp/server-configs-lighttpd).
|
||
|
||
|
||
Безопасная конфигурация сервера
|
||
-------------------------------
|
||
|
||
Цель этого раздела - выявить риски, которые необходимо учитывать при создании
|
||
конфигурации сервера для обслуживания веб-сайта на основе Yii. Помимо перечисленных здесь пунктов есть и
|
||
другие параметры, связанные с безопасностью, которые необходимо учитывать, поэтому не рассматривайте этот раздел как завершенный.
|
||
|
||
### Как избежать атаки типа `Host`-header
|
||
|
||
Классы типа [[yii\web\UrlManager]] и [[yii\helpers\Url]] могут использовать [[yii\web\Request::getHostInfo()|запрашиваемое имя хоста]]] для генерации ссылок. Если веб-сервер настроен на обслуживание одного и того же сайта независимо от значения заголовка `Host`, эта информация может быть ненадежной и может быть подделана пользователем, отправляющим HTTP-запрос. В таких ситуациях Вы должны либо исправить конфигурацию своего веб-сервера, чтобы обслуживать сайт только для указанных имен узлов, либо явно установить или отфильтровать значение, установив свойство [[yii\web\Request::setHostInfo()|hostInfo]] компонента приложения `request`.
|
||
|
||
Дополнительные сведения о конфигурации сервера смотрите в документации Вашего веб-сервера:
|
||
|
||
- Apache 2: <https://httpd.apache.org/docs/trunk/vhosts/examples.html#defaultallports>
|
||
- Nginx: <https://www.nginx.com/resources/wiki/start/topics/examples/server_blocks/>
|
||
|
||
Если у Вас нет доступа к конфигурации сервера, Вы можете настроить фильтр [[yii\filters\HostControl]] уровня приложения для защиты от такого рода атак:
|
||
|
||
```php
|
||
// Файл конфигурации веб-приложения
|
||
return [
|
||
'as hostControl' => [
|
||
'class' => 'yii\filters\HostControl',
|
||
'allowedHosts' => [
|
||
'example.com',
|
||
'*.example.com',
|
||
],
|
||
'fallbackHostInfo' => 'https://example.com',
|
||
],
|
||
// ...
|
||
];
|
||
```
|
||
|
||
> Note: Вы всегда должны предпочесть конфигурацию веб-сервера для защиты от `атак заголовков узла` вместо использования фильтра.
|
||
[[yii\filters\HostControl]] следует использовать, только если настройка конфигурации сервера недоступна.
|