以下では、一般的なセキュリティ原則をレビューし、Yiiを使用してアプリケーションを開発する際に脅威を回避する方法について説明します。これらの原則のほとんどは、Yii固有のものではなく、Webサイトやソフトウェア開発全般に適用されるため、これらの背後にある一般的な考え方についての詳細な読み物へのリンクも示します。
どのアプリケーションを開発する場合でも、セキュリティに関しては2つの主要な原則があります。
入力のフィルタリングとは、入力は決して安全とは見なすべきではなく、取得した値が許可されている値の中にあるかどうかを常に確認する必要があるということです。たとえば、ソートは3つのフィールドtitle
、created_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インジェクションは、クエリテキストが次のようにエスケープされていない文字列を連結して形成される場合に発生します。
$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またはクロスサイトスクリプティングは、HTMLをブラウザに出力する際に、出力が適切にエスケープされていない場合に発生します。たとえば、ユーザーが自分の名前を入力できる場合、Alexander
の代わりに<script>alert('Hello!');</script>
を入力すると、エスケープせずにユーザー名を出力するすべてのページでJavaScript alert('Hello!');
が実行され、ブラウザにアラートボックスが表示されます。ウェブサイトによっては、無害なアラートの代わりに、このようなスクリプトがあなたの名前を使用してメッセージを送信したり、銀行取引を実行したりする可能性があります。
Yiiでは、XSSの回避は非常に簡単です。一般的に2つのケースがあります。
プレーンテキストのみが必要な場合は、次のように簡単にエスケープできます。
<?= \yii\helpers\Html::encode($username) ?>
HTMLである必要がある場合は、HtmlPurifierの助けを借りることができます。
<?= \yii\helpers\HtmlPurifier::process($description) ?>
HtmlPurifierの処理は非常に負荷が高いため、キャッシュを追加することを検討してください。
このトピックに関する詳細な読み物
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を回避するには、常に次のことを行う必要があります。
コントローラーやアクションごとに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.php
でYII_DEBUG
がtrue
に設定された状態で本番環境のアプリケーションを実行しないでください。
本番環境でGiiやデバッグツールバーを有効にしないでください。データベース構造、コードに関する情報を取得したり、Giiで生成されたものでコードを書き換えたりするために使用される可能性があります。
デバッグツールバーは、本当に必要な場合を除き、本番環境での使用は避ける必要があります。アプリケーションと構成の詳細がすべて公開されます。どうしても必要な場合は、アクセスがあなたのIPのみに適切に制限されていることを二度確認してください。
このトピックに関する詳細な読み物
Yiiは、クッキーやPHPセッションに依存する機能を提供します。これらの機能は、接続が侵害された場合に脆弱になる可能性があります。アプリがTLS(SSLとも呼ばれます)を介したセキュアな接続を使用している場合、リスクは軽減されます。
構成方法については、Webサーバーのドキュメントを参照してください。H5BPプロジェクトが提供する構成例も確認できます。
注: TLSが構成されている場合、(セッション)クッキーはTLS経由でのみ送信されることを推奨します。これは、セッションやクッキーに
secure
フラグを設定することで実現できます。詳細については、セッションとクッキーのセキュアフラグを参照してください。
このセクションの目的は、Yiiベースのウェブサイトを提供するためのサーバー構成を作成する際に考慮する必要があるリスクを強調することです。ここで説明するポイントに加えて、考慮する必要のあるセキュリティ関連の構成オプションが他にもある可能性があるため、このセクションが完全であるとは考えないでください。
Host
ヘッダー攻撃の回避 ¶yii\web\UrlManagerやyii\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証明書検証の問題を解決する方法について、一般的な誤解があります。
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を適切に構成する必要があります。
openssl.cafile="/path/to/cacert.pem" curl.cainfo="/path/to/cacert.pem".
cacert.pem
ファイルは最新の状態に保つ必要があることに注意してください。
タイプミスを見つけましたか、またはこのページは改善が必要だと思いますか?
GitHubで編集する !
サインアップまたはログインしてコメントしてください。