下記において、一般的なセキュリティの指針を復習し、Yii を使ってアプリケーションを開発するときに脅威を回避する方法を説明します。
どのようなアプリケーションが開発されているかに関わらず、セキュリティに関しては二つの大きな指針が存在します。
- 入力をフィルタする。
- 出力をエスケープする。
入力をフィルタするとは、入力値は決して安全なものであると見なさず、取得した値が実際に許可さていれる値に含まれるか否かを常にチェックしなければならない、ということを意味します。
例えば、並べ替えが三つのフィールド title
、created_at
および status
によって実行され、フィールドの名前がユーザの入力によって提供されるものであることを知っている場合、取得した値を受信するその場でチェックする方が良い、ということです。
基本的な PHP の形式では、次のようなコードになります。
$sortBy = $_GET['sort'];
if (!in_array($sortBy, ['title', 'created_at', 'status'])) {
throw new Exception('sort の値が不正です。');
}
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
テーブルを削除します。
おそらく、ウェブサイトは破壊されて、データは失われることになります (定期的なバックアップは設定済みですよね、ね? )。
Yii においては、ほとんどのデータベースクエリは、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('name と status だけを並べ替えに使うことが出来ます。')
}
// ...
}
それが不可能な場合は、テーブル名とカラム名をエスケープしなければなりません。 Yii はそういうエスケープのための特別な文法を持っており、それを使うと、サポートされている全てのデータベースに対して同じ方法でエスケープすることが出来ます。
$sql = "SELECT COUNT([[$column]]) FROM {{table}}";
$rowCount = $connection->createCommand($sql)->queryScalar();
この文法の詳細は、テーブルとカラムの名前を引用符で囲む で読むことが出来ます。
XSS すなわちクロスサイトスクリプティングは、ブラウザに HTML を出力する際に、出力が適切にエスケープされていないと発生します。
例えば、ユーザ名を入力できるフォームで Alexander
の代りに <script>alert('Hello!');</script>
と入力した場合、ユーザ名をエスケープせずに出力している全てのページでは、JavaScript alert('Hello!');
が実行されて、ブラウザにアラートボックスがポップアップ表示されます。
ウェブサイト次第では、そのようなスクリプトを使って、無害なアラートではなく、あなたの名前を使ってメッセージを送信したり、さらには銀行取引を実行したりすることが可能です。
XSS の回避は、Yii においてはとても簡単です。一般に、二つのケースがあります。
- データを平文テキストとして出力したい。
- データを HTML として出力したい。
平文テキストしか必要でない場合は、エスケープは次のようにとても簡単です。
<?= \yii\helpers\Html::encode($username) ?>
HTML である場合は、HtmlPurifier から助けを得ることが出来ます。
<?= \yii\helpers\HtmlPurifier::process($description) ?>
HtmlPurifier の処理は非常に重いので、キャッシュを追加することを検討してください。
CSRF は、クロスサイトリクエストフォージェリ (cross-site request forgery) の略称です。 多くのアプリケーションは、ユーザのブラウザから来るリクエストはユーザ自身によって発せられたものだと仮定しているけれども、その仮定は間違っているかもしれない ... というのが CSRF の考え方です。
例えば、an.example.com
というウェブサイトが /logout
という URL を持っており、この URL を単純な GET でアクセスするとユーザをログアウトさせるようになっているとします。
ユーザ自身によってこの URL がリクエストされる限りは何も問題はありませんが、ある日、悪い奴が、ユーザが頻繁に訪れるフォーラムに <img src="http://an.example.com/logout">
というリンクを含むコンテントを何とかして投稿することに成功します。
ブラウザは画像のリクエストとページのリクエストの間に何ら区別を付けませんので、ユーザがそのような img
タグを含むページを開くと an.example.com
からログアウトされてしまうことになる訳です。
これは基本的な考え方です。ユーザがログアウトされるぐらいは大したことではない、と言うことも出来るでしょう。 しかし、POST を送信することも、それほど難しくはありません。
CSRF を回避するためには、次のことを守らなければなりません。
- HTTP の規格、すなわち、GET はアプリケーションの状態を変更すべきではない、という規則に従うこと。
- Yii の CSRF 保護を有効にしておくこと。
デフォルトでは、サーバのウェブルートは、index.php
がある web
ディレクトリを指すように意図されています。
共有ホスティング環境の場合、それをすることが出来ずに、全てのコード、構成情報、ログをサーバのウェブルートの下に置かなくてはならないことがあり得ます。
そういう場合には、web
以外の全てに対してアクセスを拒否することを忘れないでください。
それも出来ない場合は、アプリケーションを別の場所でホストすることを検討してください。
デバッグモードでは、Yii は極めて多くのエラー情報を出力します。これは確かに開発には役立つものです。
しかし、実際の所、これらの饒舌なエラー情報は、攻撃者にとっても、データベース構造、構成情報の値、コードの断片などを曝露してくれる重宝なものです。
本番でのアプリケーションにおいては、決して index.php
の YII_DEBUG
を true
にして走らせてはいけません。
本番環境では Gii を決して有効にしてはいけません。 Gii を使うと、データベース構造とコードに関する情報を得ることが出来るだけでなく、コードを Gii によって生成したもので書き換えることすら出来てしまいます。
デバッグツールバーは本当に必要でない限り本番環境では使用を避けるべきです。 これはアプリケーションと構成情報の全ての詳細を曝露することが出来ます。 どうしても必要な場合は、あなたの IP だけに適切にアクセス制限されていることを再度チェックしてください。