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

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

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

  • Вход фильтра.
  • Выход для выхода.

Вход фильтра

Входной фильтр означает, что ввод никогда не должен считаться безопасным, и вы всегда должны проверять, действительно ли имеющееся значение находится среди разрешенных. Например, если мы знаем, что сортировку можно выполнить с помощью трех полей title, created_at и status, а поле может быть предоставлено через ввод пользователя, лучше проверить значение, которое мы получили именно там, где мы его получаем. С точки зрения базового PHP это будет выглядеть следующим образом:

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

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

Выход для выхода

Выход Escape означает, что в зависимости от контекста, в котором мы используем данные, его следует экранировать, то есть в контексте 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 в браузер. Например, если пользователь может ввести свое имя и вместо Александра, он вводит предупреждение <script>alert('Hello!');</script>, каждая страница, которая выводит имя пользователя без экранирования, будет выполнять оповещение JavaScript («Hello!») ; В результате чего в браузере появляется окно предупреждения. В зависимости от веб-сайта, а не безобидного оповещения, такой скрипт может отправлять сообщения, используя ваше имя или даже совершать банковские транзакции.

Избежать XSS довольно легко в Yii. Есть, как правило, два случая:

  • Вы хотите, чтобы данные выводились как обычный текст.
  • Вы хотите, чтобы данные выводились как HTML.

Если вам нужен только простой текст, экранирование проще простого:

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

Если это должен быть HTML, мы можем получить некоторую помощь от HtmlPurifier:

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

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

Избежание CSRF

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

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

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

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

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

Иногда вам нужно отключить проверку CSRF на каждый контроллер и / или действие. Это можно сделать, установив его свойство:

namespace app\controllers;

use yii\web\Controller;

class SiteController extends Controller
{
    public $enableCsrfValidation = false;

    public function actionIndex()
    {
        // CSRF validation will not be applied to this and other actions
    }

}

Чтобы отключить проверку CSRF для каждого настраиваемого действия, выполните следующие действия:

namespace app\controllers;

use yii\web\Controller;

class SiteController extends Controller
{
    public function beforeAction($action)
    {
        // ...set `$this->enableCsrfValidation` here based on some conditions...
        // call parent method that will check CSRF if such property is `true`.
        return parent::beforeAction($action);
    }
}

Отключение проверки CSRF в автономных действиях должно выполняться методом init(). Не помещайте этот код в метод beforeRun(), потому что он не будет иметь эффекта.

<?php

namespace app\components;

use yii\base\Action;

class ContactAction extends Action
{
    public function init()
    {
        parent::init();
        $this->controller->enableCsrfValidation = false;
    }

    public function run()
    {
          $model = new ContactForm();
          $request = Yii::$app->request;
          if ($request->referrer === 'yiipowered.com'
              && $model->load($request->post())
              && $model->validate()
          ) {
              $model->sendEmail();
          }
    }
}

Как избежать воздействия файла

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

Если это так, не забудьте запретить доступ ко всему, кроме Интернета. Если это невозможно, рассмотрите возможность размещения своей заявки в другом месте.

Избегайте отладочной информации и инструментов в производстве

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

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

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

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

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

  • Nginx
  • Apache.
  • IIS.
  • Lighttpd.

Конфигурация безопасного сервера

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

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

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

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

// Web Application configuration file
return [
    'as hostControl' => [
        'class' => 'yii\filters\HostControl',
        'allowedHosts' => [
            'example.com',
            '*.example.com',
        ],
        'fallbackHostInfo' => 'https://example.com',
    ],
    // ...
];