Commit 84188820 by Qiang Xue

Merge pull request #6623 from softark/docs-guide-ja-security

Docs guide ja security [ci skip]
parents 91f43754 96844416
...@@ -109,10 +109,10 @@ All Rights Reserved. ...@@ -109,10 +109,10 @@ All Rights Reserved.
------------ ------------
* [認証](security-authentication.md) * [認証](security-authentication.md)
* **翻訳中** [権限付与](security-authorization.md) * [権限付与](security-authorization.md)
* **翻訳中** [パスワードを扱う](security-passwords.md) * [パスワードを扱う](security-passwords.md)
* **TBD** [Auth クライアント](security-auth-clients.md) * **TBD** [Auth クライアント](security-auth-clients.md)
* **翻訳中** [ベストプラクティス](security-best-practices.md) * [ベストプラクティス](security-best-practices.md)
キャッシュ キャッシュ
......
権限付与
========
> Note|注意: この節はまだ執筆中です。
権限付与は、ユーザが何かをするのに十分な許可を得ているか否かを確認するプロセスです。
Yii は二つの権限付与の方法を提供しています。すなわち、アクセス制御フィルタ (ACF) と、ロールベースアクセス制御 (RBAC) です。
アクセス制御フィルタ (ACF)
--------------------------
アクセス制御フィルタ (ACF) は、何らかの単純なアクセス制御だけを必要とするアプリケーションで使うのに最も適した、単純な権限付与の方法です。
その名前が示すように、ACF は、コントローラまたはモジュールにビヘイビアとしてアタッチすることが出来るアクションフィルタです。
ACF は一連の [[yii\filters\AccessControl::rules|アクセス規則]] をチェックして、現在のユーザがリクエストしたアクションにアクセスすることが出来るかどうかを確認します。
下記のコードは、[[yii\filters\AccessControl]] として実装された ACF の使い方を示すものです。
```php
use yii\filters\AccessControl;
class SiteController extends Controller
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'only' => ['login', 'logout', 'signup'],
'rules' => [
[
'allow' => true,
'actions' => ['login', 'signup'],
'roles' => ['?'],
],
[
'allow' => true,
'actions' => ['logout'],
'roles' => ['@'],
],
],
],
];
}
// ...
}
```
上記のコードにおいて、ACF は `site` コントローラにビヘイビアとしてアタッチされています。
これはアクションフィルタを使用する典型的な方法です。
`only` オプションは、ACF が `login``logout``signup` のアクションにのみ適用されるべきであることを指定しています。
`rules` オプションは [[yii\filters\AccessRule|アクセス規則]] を指定するものであり、以下のように読むことが出来ます。
- 全てのゲストユーザ (まだ認証されていないユーザ) に、'login' と 'singup' のアクションにアクセスすることを許可します。
`roles` オプションに疑問符 `?` が含まれていますが、これは「ゲスト」として認識される特殊なトークンです。
- 認証されたユーザに、'logout' アクションにアクセスすることを許可します。
`@` という文字はもう一つの特殊なトークンで、認証されたユーザとして認識されるものです。
ACF が権限のチェックを実行するときには、規則を一つずつ上から下へ、適用されるものを見つけるまで調べます。
そして、適用される規則の `allow` の値が、ユーザが権限を有するか否かを判断するのに使われます。
適用される規則が一つもなかった場合は、ユーザが権限をもたないことを意味し、ACF はアクションの継続を中止します。
デフォルトでは、ユーザが現在のアクションにアクセスする権限を持っていないと判定した場合は、ACF は以下のことだけを行います。
* ユーザがゲストである場合は、[[yii\web\User::loginRequired()]] を呼び出します。
このメソッドで、ブラウザをログインページにリダイレクトすることが出来ます。
* ユーザが既に認証されている場合は、[[yii\web\ForbiddenHttpException]] を投げます。
この動作は、[[yii\filters\AccessControl::denyCallback]] プロパティを構成することによって、カスタマイズすることが出来ます。
```php
[
'class' => AccessControl::className(),
'denyCallback' => function ($rule, $action) {
throw new \Exception('このページにアクセスする権限がありません。');
}
]
```
[[yii\filters\AccessRule|アクセス規則]] は多くのオプションをサポートしています。
以下はサポートされているオプションの要約です。
[[yii\filters\AccessRule]] を拡張して、あなた自身のカスタマイズしたアクセス規則のクラスを作ることも出来ます。
* [[yii\filters\AccessRule::allow|allow]]: これが「許可」の規則であるか、「禁止」の規則であるかを指定します。
* [[yii\filters\AccessRule::actions|actions]]: どのアクションにこの規則が適用されるかを指定します。
これはアクション ID の配列でなければなりません。
比較は大文字と小文字を区別します。
このオプションが空であるか指定されていない場合は、規則が全てのアクションに適用されることを意味します。
* [[yii\filters\AccessRule::controllers|controllers]]: どのコントローラにこの規則が適用されるかを指定します。
これはコントローラ ID の配列でなければなりません。
比較は大文字と小文字を区別します。
このオプションが空であるか指定されていない場合は、規則が全てのコントローラに適用されることを意味します。
* [[yii\filters\AccessRule::roles|roles]]: どのユーザロールにこの規則が適用されるかを指定します。
二つの特別なロールが認識されます。
これらは、[[yii\web\User::isGuest]] によって判断されます。
- `?`: ゲストユーザ (まだ認証されていないユーザ) を意味します。
- `@`: 認証されたユーザを意味します。
その他のロール名を使う場合には、RBAC (次の節で説明します) が必要とされ、判断のために [[yii\web\User::can()]] が呼び出されます。
このオプションが空であるか指定されていない場合は、規則が全てのロールに適用されることを意味します。
* [[yii\filters\AccessRule::ips|ips]]: どの [[yii\web\Request::userIP|クライアントの IP アドレス]] にこの規則が適用されるかを指定します。
IP アドレスは、最後にワイルドカード `*` を含むことが出来て、同じプレフィクスを持つ IP アドレスに合致させることが出来ます。
例えば、'192.168.*' は、'192.168.' のセグメントに属する全ての IP アドレスに合致します。
このオプションが空であるか指定されていない場合は、規則が全ての IP アドレスに適用されることを意味します。
* [[yii\filters\AccessRule::verbs|verbs]]: どのリクエストメソッド (例えば、`GET``POST`) にこの規則が適用されるかを指定します。
比較は大文字と小文字を区別しません。
* [[yii\filters\AccessRule::matchCallback|matchCallback]]: この規則が適用されるべきか否かを決定するために呼び出されるべき PHP コーラブルを指定します。
* [[yii\filters\AccessRule::denyCallback|denyCallback]]: この規則がアクセスを禁止する場合に呼び出されるべき PHP コーラブルを指定します。
下記は、`matchCallback` オプションを利用する方法を示す例です。
このオプションによって、任意のアクセス制御ロジックを書くことが可能になります。
```php
use yii\filters\AccessControl;
class SiteController extends Controller
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'only' => ['special-callback'],
'rules' => [
[
'actions' => ['special-callback'],
'allow' => true,
'matchCallback' => function ($rule, $action) {
return date('d-m') === '31-10';
}
],
],
],
];
}
// matchCallback が呼ばれる。このページは毎年10月31日だけアクセス出来ます。
public function actionSpecialCallback()
{
return $this->render('happy-halloween');
}
}
```
ロールベースアクセス制御 (RBAC)
---------------------------------------
ロールベースアクセス制御 (RBAC) は、単純でありながら強力な集中型のアクセス制御を提供します。
RBAC と他のもっと伝統的なアクセス制御スキーマとの比較に関する詳細については、[Wiki 記事](http://ja.wikipedia.org/wiki/%E3%83%AD%E3%83%BC%E3%83%AB%E3%83%99%E3%83%BC%E3%82%B9%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E5%88%B6%E5%BE%A1) を参照してください。
Yii は、[NIST RBAC モデル](http://csrc.nist.gov/rbac/sandhu-ferraiolo-kuhn-00.pdf) に従って、一般的階層型 RBAC を実装しています。
RBAC の機能は、[[yii\rbac\ManagerInterface|authManager]] [アプリケーションコンポーネント](structure-application-components.md) を通じて提供されます。
RBAC を使用することには、二つの作業が含まれます。
最初の作業は、RBAC 権限付与データを作り上げることであり、第二の作業は、権限付与データを使って必要とされる場所でアクセスチェックを実行することです。
説明を容易にするために、まず、いくつかの基本的な RBAC の概念を導入します。
### 基本的な概念
ロール (役割) は、*許可* (例えば、記事を作成する、記事を更新するなど) のコレクションです。
一つのロールを一人または複数のユーザに割り当てることが出来ます。
ユーザが特定の許可を有しているか否かをチェックするためには、その許可を含むロールがユーザに割り当てられているか否かをチェックすればよいのです。
各ロールまたは許可に関連付けられた *規則* が存在することがあり得ます。
規則とは、アクセスチェックの際に、対応するロールや許可が現在のユーザに適用されるか否かを決定するために実行されるコード断片のことです。
例えば、「記事更新」の許可は、現在のユーザが記事の作成者であるかどうかをチェックする規則を持つことが出来ます。
そして、アクセスチェックのときに、ユーザが記事の作成者でない場合は、彼/彼女は「記事更新」の許可を持っていないと見なすことが出来ます。
ロールおよび許可は、ともに、階層的に構成することが出来ます。
具体的に言えば、一つのロールは他のロールと許可を含むことが出来、許可は他の許可を含むことが出来ます。
Yii は、一般的な *半順序* 階層を実装していますが、これはその特殊形として *木* 階層を含むものです。
ロールは許可を含むことが出来ますが、許可はロールを含むことが出来ません。
### RBAC マネージャを構成する
権限付与データを定義してアクセスチェックを実行する前に、[[yii\base\Application::authManager|authManager]] アプリケーションコンポーネントを構成する必要があります。
Yii は二種類の権限付与マネージャを提供しています。すなわち、[[yii\rbac\PhpManager]] と [[yii\rbac\DbManager]] です。
前者は権限付与データを保存するのに PHP スクリプトファイルを使いますが、後者は権限付与データをデータベースに保存します。
あなたのアプリケーションが非常に動的なロールと許可の管理を必要とするのでなければ、前者を使うことを考慮するのが良いでしょう。
次のコードは、アプリケーションの構成情報で `authManager` を構成する方法を示すものです。
```php
return [
// ...
'components' => [
'authManager' => [
'class' => 'yii\rbac\PhpManager',
],
// ...
],
];
```
`authManager``\Yii::$app->authManager` によってアクセスすることが出来ます。
> Tip|ヒント: デフォルトでは、[[yii\rbac\PhpManager]] は RBAC データを `@app/rbac/` ディレクトリの下のファイルに保存します。
権限の階層をオンラインで変更する必要がある場合は、必ず、ウェブサーバのプロセスがこのディレクトリとその中の全てのファイルに対する書き込み権限を有するようにしてください。
### 権限付与データを構築する
権限付与データを構築する作業は、つまるところ、以下のタスクに他なりません。
- ロールと許可を定義する
- ロールと許可の関係を定義する
- 規則を定義する
- 規則をロールと許可に結び付ける
- ロールをユーザに割り当てる
権限付与に要求される柔軟性の程度によって、上記のタスクのやりかたも異なってきます。
権限の階層が全く変化せず、決った数のユーザしか存在しない場合は、`authManager` が提供する API によって権限付与データを一回だけ初期設定する [コンソールコマンド](tutorial-console.md#create-command) を作ることが出来ます。
```php
<?php
namespace app\commands;
use Yii;
use yii\console\Controller;
class RbacController extends Controller
{
public function actionInit()
{
$auth = Yii::$app->authManager;
// "createPost" という許可を追加
$createPost = $auth->createPermission('createPost');
$createPost->description = '記事を投稿';
$auth->add($createPost);
// "updatePost" という許可を追加
$updatePost = $auth->createPermission('updatePost');
$updatePost->description = '記事を更新';
$auth->add($updatePost);
// "author" というロールを追加し、このロールに "createPost" 許可を与える
$author = $auth->createRole('author');
$auth->add($author);
$auth->addChild($author, $createPost);
// "admin" というロールを追加し、このロールに "updatePost" 許可を与える
// 同時に、"author" ロールの持つ許可も与える
$admin = $auth->createRole('admin');
$auth->add($admin);
$auth->addChild($admin, $updatePost);
$auth->addChild($admin, $author);
// ロールをユーザに割り当てる。1 と 2 は IdentityInterface::getId() によって返される ID
// 通常はユーザモデルの中で実装する
$auth->assign($author, 2);
$auth->assign($admin, 1);
}
}
```
`yii rbac/init` によってコマンドを実行した後には、次の権限階層が得られます。
![単純な RBAC 階層](images/rbac-hierarchy-1.png "単純な RBAC 階層")
投稿者 (author) は記事を投稿することが出来、管理者 (admin) は記事を更新することに加えて投稿者が出来る全てのことが出来ます。
あなたのアプリケーションがユーザ登録を許している場合は、新しく登録されたユーザに一度ロールを割り当てる必要があります。
例えば、アドバンストアプリケーションテンプレートにおいては、登録したユーザの全てを「投稿者」にするために、`frontend\models\SignupForm::signup()` を次のように修正しなければなりません。
```php
public function signup()
{
if ($this->validate()) {
$user = new User();
$user->username = $this->username;
$user->email = $this->email;
$user->setPassword($this->password);
$user->generateAuthKey();
$user->save(false);
// 次の三行が追加されたものです
$auth = Yii::$app->authManager;
$authorRole = $auth->getRole('author');
$auth->assign($authorRole, $user->getId());
return $user;
}
return null;
}
```
動的に更新される権限付与データを持つ複雑なアクセス制御を必要とするアプリケーションについては、`authManager` が提供する API を使って、特別なユーザインタフェイス (つまり、管理パネル) を開発する必要があるでしょう。
### 規則を使う
既に述べたように、規則がロールと許可に制約を追加します。
規則は [[yii\rbac\Rule]] を拡張したクラスであり、[[yii\rbac\Rule::execute()|execute()]] メソッドを実装しなければなりません。
前に作った権限階層においては、投稿者は自分自身の記事を編集することが出来ないものでした。これを修正しましょう。
最初に、ユーザが記事の投稿者であることを確認する規則が必要です。
```php
namespace app\rbac;
use yii\rbac\Rule;
/**
* authorID がパラメータで渡されたユーザと一致するかチェックする
*/
class AuthorRule extends Rule
{
public $name = 'isAuthor';
/**
* @param string|integer $user ユーザ ID
* @param Item $item この規則が関連付けられているロールまたは許可
* @param array $params ManagerInterface::checkAccess() に渡されたパラメータ
* @return boolean 関連付けられたロールまたは許可を認めるか否かを示す値
*/
public function execute($user, $item, $params)
{
return isset($params['post']) ? $params['post']->createdBy == $user : false;
}
}
```
上の規則は、`post``$user` によって作成されたかどうかをチェックします。
次に、前に使ったコマンドの中で、`updateOwnPost` という特別な許可を作成します。
```php
$auth = Yii::$app->authManager;
// 規則を追加する
$rule = new \app\rbac\AuthorRule;
$auth->add($rule);
// "updateOwnPost" という許可を作成し、それに規則を関連付ける
$updateOwnPost = $auth->createPermission('updateOwnPost');
$updateOwnPost->description = '自分の記事を更新';
$updateOwnPost->ruleName = $rule->name;
$auth->add($updateOwnPost);
// "updateOwnPost" は "updatePost" から使われる
$auth->addChild($updateOwnPost, $updatePost);
// "author" に自分の記事を更新することを許可する
$auth->addChild($author, $updateOwnPost);
```
これで、次のような権限階層になります。
![規則を持つ RBAC 階層](images/rbac-hierarchy-2.png "規則を持つ RBAC 階層")
### アクセスチェック
権限付与データが準備できてしまえば、アクセスチェックは [[yii\rbac\ManagerInterface::checkAccess()]] メソッドを呼ぶだけの簡単な仕事です。
たいていのアクセスチェックは現在のユーザに関するものですから、Yii は、便利なように、[[yii\web\User::can()]] というショートカットメソッドを提供しています。
これは、次のようにして使うことが出来ます。
```php
if (\Yii::$app->user->can('createPost')) {
// 記事を作成する
}
```
現在のユーザが ID=1 である Jane であるとすると、`createPost` からスタートして `Jane` まで到達しようと試みます。
![アクセスチェック](images/rbac-access-check-1.png "アクセスチェック")
ユーザが記事を更新することが出来るかどうかをチェックするためには、前に説明した `AuthorRule` によって要求される追加のパラメータを渡す必要があります。
```php
if (\Yii::$app->user->can('updatePost', ['post' => $post])) {
// 記事を更新する
}
```
現在のユーザが John であるとすると、次の経路をたどります。
![アクセスチェック](images/rbac-access-check-2.png "アクセスチェック")
`updatePost` からスタートして、`updateOwnPost` を通過します。
通過するためには、`AuthorRule``execute` メソッドで `true` を返さなければなりません。
`execute` メソッドは `can` メソッドの呼び出しから `$params` を受け取りますので、その値は `['post' => $post]` です。
すべて OK であれば、John に割り当てられている `author` に到達します。
Jane の場合は、彼女が管理者であるため、少し簡単になります。
![アクセスチェック](images/rbac-access-check-3.png "アクセスチェック")
### デフォルトロールを使う
デフォルトロールというのは、*全て* のユーザに *黙示的* に割り当てられるロールです。
[[yii\rbac\ManagerInterface::assign()]] を呼び出す必要はなく、権限付与データはその割り当て情報を含みません。
デフォルトロールは、通常、そのロールが当該ユーザに適用されるかどうかを決定する規則と関連付けられます。
デフォルトロールは、たいていは、何らかのロールの割り当てを既に持っているアプリケーションにおいて使われます。
例えば、アプリケーションによっては、ユーザのテーブルに "group" というカラムを持って、個々のユーザが属する特権グループを表している場合があります。
それぞれの特権グループを RBAC ロールに対応付けることが出来るのであれば、デフォルトロールの機能を使って、それぞれのユーザに RBAC ロールを自動的に割り当てることが出来ます。
どのようにすればこれが出来るのか、例を使って説明しましょう。
ユーザのテーブルに `group` というカラムがあって、1 は管理者グループ、2 は投稿者グループを示していると仮定しましょう。
これら二つのグループの権限を表すために、それぞれ、`admin``author` という RBAC ロールを作ることにします。
このとき、次のように RBAC データをセットアップすることが出来ます。
```php
namespace app\rbac;
use Yii;
use yii\rbac\Rule;
/**
* ユーザのグループが合致するかどうかをチェックする
*/
class UserGroupRule extends Rule
{
public $name = 'userGroup';
public function execute($user, $item, $params)
{
if (!Yii::$app->user->isGuest) {
$group = Yii::$app->user->identity->group;
if ($item->name === 'admin') {
return $group == 1;
} elseif ($item->name === 'author') {
return $group == 1 || $group == 2;
}
}
return false;
}
}
$auth = Yii::$app->authManager;
$rule = new \app\rbac\UserGroupRule;
$auth->add($rule);
$author = $auth->createRole('author');
$author->ruleName = $rule->name;
$auth->add($author);
// ... $author の子として許可を追加 ...
$admin = $auth->createRole('admin');
$admin->ruleName = $rule->name;
$auth->add($admin);
$auth->addChild($admin, $author);
// ... $admin の子として許可を追加 ...
```
上記において、"author" が "admin" の子として追加されているため、規則クラスの `execute()` メソッドを実装する時には、この階層関係にも配慮しなければならないことに注意してください。
このために、ロール名が "author" である場合には、`execute()` メソッドは、ユーザのグループが 1 または 2 である (ユーザが "admin" グループまたは "author" グループに属している) ときに true を返しています。
次に、`authManager` の構成情報で、この二つのロールを [[yii\rbac\BaseManager::$defaultRoles]] としてリストします。
```php
return [
// ...
'components' => [
'authManager' => [
'class' => 'yii\rbac\PhpManager',
'defaultRoles' => ['admin', 'author'],
],
// ...
],
];
```
このようにすると、アクセスチェックを実行すると、`admin``author` の両方のロールは、それらと関連付けられた規則を評価することによってチェックされるようになります。
規則が true を返せば、そのロールが現在のユーザに適用されることになります。
上述の規則の実装に基づいて言えば、ユーザの `group` の値が 1 であれば、`admin` ロールがユーザに適用され、`group` の値が 2 であれば `author` ロールが適用されるということを意味します。
セキュリティのベストプラクティス
================================
下記において、一般的なセキュリティの指針を復習し、Yii を使ってアプリケーションを開発するときに脅威を回避する方法を説明します。
基本的な指針
------------
どのようなアプリケーションが開発されているかに関わらず、セキュリティに関しては二つの大きな指針が存在します。
1. 入力をフィルタする。
2. 出力をエスケープする。
### 入力をフィルタする
入力をフィルタするとは、入力値は決して安全なものであると見なさず、取得した値が実際に許可さていれる値に含まれるか否かを常にチェックしなければならない、ということを意味します。
つまり、並べ替えが三つのフィールド `title``created_at` および `status` によって実行され、フィールドの値がインプットによって提供されるものであることを知っている場合、取得した値を受信するその場でチェックする方が良い、ということです。
基本的な PHP の形式では、次のようなコードになります。
```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 においては、ほとんどのデータベースクエリは、PDO のプリペアドステートメントを適切に使用する [アクティブレコード](db-active-record.md) を経由して実行されます。
プリペアドステートメントの場合は、上で説明したようなクエリの改竄は不可能です。
それでも、[生のクエリ](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('名前とステータスだけを並べ替えに使うことが出来ます。')
}
// ...
}
```
それが不可能な場合は、テーブル名とカラム名をエスケープしなければなりません。
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!');` が実行されて、ブラウザにアラートボックスがポップアップ表示されます。
ウェブサイト次第では、そのようなスクリプトを使って、無害なアラートではなく、あなたの名前を使ってメッセージを送信したり、さらには銀行取引を実行したりすることが可能です。
XSS の回避は、Yii においてはとても簡単です。一般に、二つのケースがあります。
1. データを平文テキストとして出力したい。
2. データを HTML として出力したい。
平文テキストしか必要でない場合は、エスケープは次のようにとても簡単です。
```php
<?= \yii\helpers\Html::encode($username) ?>
```
HTML である場合は、HtmlPurifier から助けを得ることが出来ます。
```php
<?= \yii\helpers\HtmlPurifier::process($description) ?>
```
HtmlPurifier の処理は非常に重いので、キャッシュを追加することを検討してください。
CSRF を回避する
---------------
TBD: what's CSRF, how it works, intro
1. HTTP の規格に従うこと、すなわち、GET はアプリケーションの状態を変更すべきではない。
2. Yii の CSRF 保護を有効にしておくこと。
TBD: how CSRF protection works
ファイルの曝露を回避する
------------------------
デフォルトでは、サーバのウェブルートは、`index.php` がある `web` ディレクトリを指すように意図されています。
共有ホスティング環境の場合、それをすることが出来ずに、全てのコード、構成情報、ログをサーバのウェブルートの下に置かなくてはならないことがあり得ます。
そういう場合には、`web` 以外の全てに対してアクセスを拒否することを忘れないでください。
それも出来ない場合は、アプリケーションを別の場所でホストすることを検討してください。
実運用環境ではデバッグ情報とデバッグツールを無効にする
------------------------------------------------------
デバッグモードでは、Yii は極めて多くのエラー情報を出力します。これは確かに開発には役立つものです。
しかし、実際の所、これらの饒舌なエラー情報は、攻撃者にとっても、データベース構造、構成情報の値、コードの断片などを曝露してくれる重宝なものです。
実運用のアプリケーションにおいては、決して `index.php``YII_DEBUG``true` にして走らせてはいけません。
実運用環境では Gii を決して有効にしてはいけません。
Gii を使うと、データベース構造とコードに関する情報を得ることが出来るだけでなく、コードを Gii によって生成したもので書き換えることすら出来てしまいます。
デバッグツールバーは本当に必要でない限り実運用環境では使用を避けるべきです。
これはアプリケーションと構成情報の全ての詳細を曝露することが出来ます。
どうしても必要な場合は、あなたの IP だけに適切にアクセス制限されていることを再度チェックしてください。
セキュリティ
============
> Note|注意: この節はまだ執筆中です。
十分なセキュリティは、どのようなアプリケーションであっても、健全さを保って成功するためには欠くことが出来ないものです。
不幸なことに、理解が不足しているためか、実装の難易度が高すぎるためか、セキュリティのことになると手を抜く開発者がたくさんいます。
Yii によって駆動されるアプリケーションを可能な限り安全にするために、Yii はいくつかの優秀な使いやすいセキュリティ機能を内蔵しています。
ハッシュとパスワードの検証
--------------------------
ほとんどの開発者はパスワードを平文テキストでは保存できないということを知っていますが、パスワードを `md5``sha1` でハッシュしてもまだ安全だと思っている開発者がたくさんいます。
かつては、前述のハッシュアルゴリズムを使えば十分であった時もありましたが、現代のハードウェアをもってすれば、そのようなハッシュはブルートフォースアタックを使って非常に簡単に復元することが可能です。
最悪のシナリオ (アプリケーションに侵入された場合) であっても、ユーザのパスワードについて強化されたセキュリティを提供することが出来るように、ブルートフォースアタックに対する耐性が強いハッシュアルゴリズムを使う必要があります。
現在、最善の選択は `bcrypt` です。
PHP では、[crypt 関数](http://php.net/manual/ja/function.crypt.php) を使って `bcrypt` ハッシュを生成することが出来ます。
Yii は `crypt` を使ってハッシュを安全に生成し検証することを容易にする二つのヘルパ関数を提供します。
ユーザが初めてパスワードを提供するとき (例えば、ユーザ登録の時) には、パスワードをハッシュする必要があります。
```php
$hash = Yii::$app->getSecurity()->generatePasswordHash($password);
```
そして、ハッシュを対応するモデル属性と関連付けて、後で使用するためにデータベースに保存します。
ユーザがログインを試みたときは、送信されたパスワードは、前にハッシュされて保存されたパスワードと照合して検証されます。
```php
if (Yii::$app->getSecurity()->validatePassword($password, $hash)) {
// よろしい、ユーザをログインさせる
} else {
// パスワードが違う
}
```
擬似乱数データを生成する
------------------------
擬似乱数データはさまざまな状況で役に立ちます。
例えば、メール経由でパスワードをリセットするときは、トークンを生成してデータベースに保存し、それをユーザにメールで送信します。
そして、ユーザはこのトークンを自分がアカウントの所有者であることの証拠として使用します。
このトークンがユニークかつ推測困難なものであることは非常に重要なことです。
さもなくば、攻撃者がトークンの値を推測してユーザのパスワードをリセットする可能性があります。
Yii のセキュリティヘルパは擬似乱数データの生成を単純な作業にしてくれます。
```php
$key = Yii::$app->getSecurity()->generateRandomString();
```
暗号論的に安全な乱数データを生成するためには、`openssl` 拡張をインストールしている必要があることに注意してください。
暗号化と復号化
--------------
Yii は秘密鍵を使ってデータを暗号化/復号化することを可能にする便利なヘルパ関数を提供しています。
データを暗号化関数に渡して、秘密鍵を持つ者だけが復号化することが出来るようにすることが出来ます。
例えば、何らかの情報をデータベースに保存する必要があるけれども、(たとえアプリケーションのデータベースが第三者に漏洩した場合でも) 秘密鍵を持つユーザだけがそれを見ることが出来るようにする必要がある、という場合には次のようにします。
```php
// $data と $secretKey はフォームから取得する
$encryptedData = Yii::$app->getSecurity()->encryptByPassword($data, $secretKey);
// $encryptedData をデータベースに保存する
```
そして、後でユーザがデータを読みたいときは、次のようにします。
```php
// $secretKey はユーザ入力から取得、$encryptedData はデータベースから取得
$data = Yii::$app->getSecurity()->decryptByPassword($encryptedData, $secretKey);
```
データの完全性を確認する
------------------------
データが第三者によって改竄されたり、更には何らかの形で毀損されたりしていないことを確認する必要がある、という場合があります。
Yii は二つのヘルパ関数の形で、データの完全性を確認するための簡単な方法を提供しています。
秘密鍵とデータから生成されたハッシュをデータにプレフィクスします。
```php
// $secretKey はアプリケーションまたはユーザの秘密、$genuineData は信頼できるソースから取得
$data = Yii::$app->getSecurity()->hashData($genuineData, $secretKey);
```
データの完全性が毀損されていないかチェックします。
```php
// $secretKey はアプリケーションまたはユーザの秘密、$data は信頼できないソースから取得
$data = Yii::$app->getSecurity()->validateData($data, $secretKey);
```
todo: XSS prevention, CSRF prevention, cookie protection, refer to 1.1 guide
プロパティを設定することによって、CSRF バリデーションをコントローラ および/または アクション単位で無効にすることも出来ます。
```php
namespace app\controllers;
use yii\web\Controller;
class SiteController extends Controller
{
public $enableCsrfValidation = false;
public function actionIndex()
{
// CSRF バリデーションはこのアクションおよびその他のアクションに適用されない
}
}
```
特定のアクションに対して CSRF バリデーションを無効にするためには、次のようにします。
```php
namespace app\controllers;
use yii\web\Controller;
class SiteController extends Controller
{
public function beforeAction($action)
{
// ... ここで、何らかの条件に基づいて `$this->enableCsrfValidation` をセットする ...
// 親のメソッドを呼ぶ。プロパティが true なら、その中で CSRF がチェックされる。
return parent::beforeAction($action);
}
}
```
クッキーを安全にする
--------------------
- validation
- httpOnly is default
参照
----
以下も参照してください。
- [ビューのセキュリティ](structure-views.md#security)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment