4 フォロワー

セキュリティのベストプラクティス

以下では、一般的なセキュリティ原則をレビューし、Yiiを使用してアプリケーションを開発する際に脅威を回避する方法について説明します。これらの原則のほとんどは、Yii固有のものではなく、Webサイトやソフトウェア開発全般に適用されるため、これらの背後にある一般的な考え方についての詳細な読み物へのリンクも示します。

基本原則

どのアプリケーションを開発する場合でも、セキュリティに関しては2つの主要な原則があります。

  1. 入力のフィルタリング。
  2. 出力のエスケープ。

入力のフィルタリング

入力のフィルタリングとは、入力は決して安全とは見なすべきではなく、取得した値が許可されている値の中にあるかどうかを常に確認する必要があるということです。たとえば、ソートは3つのフィールドtitlecreated_at、およびstatusによって実行可能であり、フィールドはユーザー入力によって提供される可能性があるとします。取得した値を、受け取った場所で確認するのが適切です。基本的なPHPの観点から見ると、次のようになります。

$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テーブルをドロップします。これにより、Webサイトの破損とデータ損失が発生する可能性が高くなります(定期的なバックアップを設定しましたか?)。

Yiiでは、ほとんどのデータベースクエリはActive Recordを介して行われ、内部的にはPDOのプリペアドステートメントが適切に使用されます。プリペアドステートメントの場合、上記で説明したようにクエリを操作することはできません。

それでも、時にはrawクエリクエリビルダーが必要になることがあります。この場合、データの受け渡しには安全な方法を使用する必要があります。データがカラムの値に使用される場合は、プリペアドステートメントを使用することが推奨されます。

// 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!');が実行され、ブラウザにアラートボックスが表示されます。ウェブサイトによっては、無害なアラートの代わりに、このようなスクリプトがあなたの名前を使用してメッセージを送信したり、銀行取引を実行したりする可能性があります。

Yiiでは、XSSの回避は非常に簡単です。一般的に2つのケースがあります。

  1. データをプレーンテキストとして出力したい場合。
  2. データをHTMLとして出力したい場合。

プレーンテキストのみが必要な場合は、次のように簡単にエスケープできます。

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

HTMLである必要がある場合は、HtmlPurifierの助けを借りることができます。

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

HtmlPurifierの処理は非常に負荷が高いため、キャッシュを追加することを検討してください。

このトピックに関する詳細な読み物

CSRFの回避

CSRFはクロスサイトリクエストフォージェリの略です。多くのアプリケーションは、ユーザーのブラウザから送信されたリクエストはユーザー自身が行ったものと想定しています。この想定は誤っている可能性があります。

たとえば、ウェブサイトan.example.comには、単純なGETリクエストを使用してアクセスするとユーザーをログアウトさせる/logout URLがあります。ユーザー自身がリクエストする限りはすべて問題ありませんが、ある日、悪意のある者が、ユーザーが頻繁に訪問するフォーラムに<img src="https://an.example.com/logout">を投稿しました。ブラウザは、画像のリクエストとページのリクエストを区別しないため、ユーザーがこのような操作された<img>タグを含むページを開くと、ブラウザはそのURLにGETリクエストを送信し、ユーザーはan.example.comからログアウトされます。

これがCSRF攻撃の基本的な仕組みです。ユーザーをログアウトさせることは深刻なことではないと言うかもしれませんが、これは単なる例であり、このアプローチを使用してできることは他にもたくさんあります。たとえば、支払いを行ったり、データを変更したりすることです。一部のウェブサイトにhttps://an.example.com/purse/transfer?to=anotherUser&amount=2000というURLがあるとします。GETリクエストを使用してアクセスすると、認証されたユーザーアカウントからユーザーanotherUserに2000ドルが送金されます。ブラウザは常にGETリクエストを送信して画像を読み込むことを知っているので、そのURLでPOSTリクエストのみを受け入れるようにコードを変更できます。残念ながら、これは私たちを救いません。なぜなら、攻撃者は<img>タグの代わりに、そのURLにPOSTリクエストを送信できるJavaScriptコードを配置できるからです。

このため、YiiはCSRF攻撃から保護するための追加のメカニズムを適用します。

CSRFを回避するには、常に次のことを行う必要があります。

  1. HTTP仕様に従います。つまり、GETはアプリケーションの状態を変更してはなりません。詳細については、RFC2616を参照してください。
  2. 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();
          }
    }
}

警告: CSRFを無効にすると、どのサイトからでもあなたのサイトにPOSTリクエストを送信できるようになります。この場合、IPアドレスや秘密トークンの確認など、追加の検証を実装することが重要です。

注: バージョン2.0.21以降、YiiはsameSiteクッキー設定をサポートしています(PHPバージョン7.3.0以上が必要)。sameSiteクッキー設定を設定しても、すべてのブラウザがまだこの設定をサポートしているわけではないため、上記は廃止されません。詳細については、セッションとクッキーのsameSiteオプションを参照してください。

このトピックに関する詳細な読み物

任意のオブジェクトのインスタンス化の回避

Yiiの構成は、Yii::createObject($config)を介して新しいオブジェクトをインスタンス化するためにフレームワークによって使用される連想配列です。これらの配列は、インスタンス化のためのクラス名を指定します。このクラス名が信頼できないソースに由来していないことを確認することが重要です。そうでない場合、特定のクラスのロードを悪用して悪意のあるコードを実行できる脆弱性である安全でないリフレクションにつながる可能性があります。さらに、ベースのComponentクラスなど、フレームワーククラスから派生したオブジェクトに動的にキーを追加する必要がある場合は、ホワイトリストアプローチを使用してこれらの動的プロパティを検証することが不可欠です。フレームワークが__set()マジックメソッド内でYii::createObject($config)を使用する可能性があるため、この予防措置が必要です。

ファイル公開の回避

デフォルトでは、サーバーのウェブルートはindex.phpがあるwebディレクトリを指すように設定されています。共有ホスティング環境の場合、それを実現できない可能性があり、サーバーのウェブルートにすべてのコード、設定、およびログが含まれてしまいます。

その場合は、web以外のすべてへのアクセスを拒否することを忘れないでください。それができない場合は、別の場所でアプリケーションをホストすることを検討してください。

本番環境でのデバッグ情報とツールの回避

デバッグモードでは、Yiiは開発に役立つ非常に詳細なエラーを表示します。問題は、これらの詳細なエラーが攻撃者にとっても役立つことです。なぜなら、これらのエラーはデータベース構造、構成値、およびコードの一部を明らかにする可能性があるからです。index.phpYII_DEBUGtrueに設定された状態で本番環境のアプリケーションを実行しないでください。

本番環境でGiiやデバッグツールバーを有効にしないでください。データベース構造、コードに関する情報を取得したり、Giiで生成されたものでコードを書き換えたりするために使用される可能性があります。

デバッグツールバーは、本当に必要な場合を除き、本番環境での使用は避ける必要があります。アプリケーションと構成の詳細がすべて公開されます。どうしても必要な場合は、アクセスがあなたのIPのみに適切に制限されていることを二度確認してください。

このトピックに関する詳細な読み物

TLS経由でのセキュア接続の使用

Yiiは、クッキーやPHPセッションに依存する機能を提供します。これらの機能は、接続が侵害された場合に脆弱になる可能性があります。アプリがTLS(SSLとも呼ばれます)を介したセキュアな接続を使用している場合、リスクは軽減されます。

構成方法については、Webサーバーのドキュメントを参照してください。H5BPプロジェクトが提供する構成例も確認できます。

注: TLSが構成されている場合、(セッション)クッキーはTLS経由でのみ送信されることを推奨します。これは、セッションやクッキーにsecureフラグを設定することで実現できます。詳細については、セッションとクッキーのセキュアフラグを参照してください。

セキュアなサーバー構成

このセクションの目的は、Yiiベースのウェブサイトを提供するためのサーバー構成を作成する際に考慮する必要があるリスクを強調することです。ここで説明するポイントに加えて、考慮する必要のあるセキュリティ関連の構成オプションが他にもある可能性があるため、このセクションが完全であるとは考えないでください。

Hostヘッダー攻撃の回避

yii\web\UrlManageryii\helpers\Urlのようなクラスは、リンクを生成するために、現在リクエストされているホスト名を使用する場合があります。WebサーバーがHostヘッダーの値に関係なく同じサイトを提供するように構成されている場合、この情報は信頼できない可能性があり、HTTPリクエストを送信するユーザーによって偽造される可能性があります。このような状況では、指定されたホスト名に対してのみサイトを提供するようにWebサーバーの構成を修正するか、requestアプリケーションコンポーネントのhostInfoプロパティを設定して値を明示的に設定またはフィルタリングする必要があります。

サーバー構成の詳細については、Webサーバーのドキュメントを参照してください。

サーバー構成にアクセスできない場合は、このような攻撃から保護するために、アプリケーションレベルでyii\filters\HostControlフィルターを設定できます。

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

注: 「ホストヘッダー攻撃」からの保護には、常にフィルターの使用ではなく、Webサーバー構成を優先する必要があります。yii\filters\HostControlは、サーバー構成の設定が利用できない場合にのみ使用する必要があります。

SSLピア検証の構成

次のようなSSL証明書検証の問題を解決する方法について、一般的な誤解があります。

cURL error 60: SSL certificate problem: unable to get local issuer certificate

または

stream_socket_enable_crypto(): SSL operation failed with code 1. OpenSSL Error messages: error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed

多くのソースが、SSLピア検証を無効にすることを誤って示唆しています。それは、中間者攻撃を可能にするため、決して行うべきではありません。代わりに、PHPを適切に構成する必要があります。

  1. https://curl.haxx.se/ca/cacert.pemをダウンロードします。
  2. php.iniに次を追加します: openssl.cafile="/path/to/cacert.pem" curl.cainfo="/path/to/cacert.pem".

cacert.pemファイルは最新の状態に保つ必要があることに注意してください。

タイプミスを見つけましたか、またはこのページは改善が必要だと思いますか?
GitHubで編集する !