[CakePHP3] データ漏洩していませんか?RequestHandlerの危険性
予定していたAdventCalendarの記事が間に合いそうになかったので
CakePHPのslackチャンネルで話題にもなったことを記事にしました
今回の検証バージョンはCakePHP3.5.7です。
おそらく過去バージョンも当てはまってると思います。
API実装予定でないWebサービスの場合は特に気が付きにくいので確認してみてください!
今回指摘の箇所はまだリリースされていませんが改善予定です。
しかしこのPRだけではまだ危ないです。
https://github.com/cakephp/app/pull/569
データ漏洩を体験
こんな感じのユーザ一覧ページを作成したいとします。
https://qiita.com/users
CakePHPインストール
cookbookに書いてるとおりにインストールします。
$ composer create-project --prefer-dist cakephp/app app
テーブル作成
usersテーブルを作成。
適当に5件ほどデータを挿入します。
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
created DATETIME,
modified DATETIME
);
bake
bakeでModel/Controller/Templateを作成します。
$ bin/cake bake all users
テンプレート編集
ユーザ一覧画面でユーザ名だけ表示したいのでusername以外の項目を削ります
<table cellpadding="0" cellspacing="0">
<thead>
<tr>
<th scope="col"><?= $this->Paginator->sort('username') ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td><?= h($user->username) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
ユーザ一覧が表示される画面ができます。
これで完成?
これであとはデザインを整えるだけ!とするとデータが漏洩してしまします。
データが漏洩しているところを確認してみる
実際にやってみます。
chromeのdevツール以下のようにしてajaxでリクエストを叩いてみます。
var jq = document.createElement('script');
jq.src = "http://code.jquery.com/jquery-3.2.1.min.js";
document.getElementsByTagName('head')[0].appendChild(jq);
$.ajax({
type: 'GET',
url: '/users',
dataType: 'json',
success: function(response){ console.log(response); },
error: function(req, err){ console.log(err); }
});
!!
テンプレートでは削除したはずのメールアドレスが表示されてしまいます。
原因
インストール時点でデフォルトでRequestHandlerコンポーネントが読み込まれています。
このRequestHandlerコンポーネントはAJAXリクエストを自動で判定して_serialize
でセットした値をjsonにして返却します。
public function initialize()
{
$this->loadComponent('RequestHandler');
}
またAppControllerはリクエストを判定して$this->set()
した値をすべて_serialize
にしています。
public function beforeRender(Event $event)
{
// Note: These defaults are just to get started quickly with development
// and should not be used in production. You should instead set "_serialize"
// in each action as required.
if (!array_key_exists('_serialize', $this->viewVars) &&
in_array($this->response->type(), ['application/json', 'application/xml'])
) {
$this->set('_serialize', true);
}
}
そしてbakeで生成したメソッドも_serialize
にセットしています。
また、ここの$this->set('_serialize', ['users']);
を削除しても上記の影響でserializeされます。
public function index()
{
$users = $this->paginate($this->Users);
$this->set(compact('users'));
$this->set('_serialize', ['users']);
}
以上のことからデフォルトのままだとAJAXリクエストをするときにEntityのデータがserializeされjsonで返却されてしまいます。
対策
対策1
RequestHandlerコンポーネントを使わないなら削除します。
とりあえずこれをやっておけば安心です。
これでAJAXリクエストを自動で判定して_serialize
でセットした値をjsonにして返却されることはなくなります。
// $this->loadComponent('RequestHandler');
対策2
RequestHandlerコンポーネントを使いたいときがあると思います。
その場合は以下のようにします。
自動で_serialize
にセットされるコードを削除します。
public function beforeRender(Event $event)
{
// Note: These defaults are just to get started quickly with development
// and should not be used in production. You should instead set "_serialize"
// in each action as required.
// if (!array_key_exists('_serialize', $this->viewVars) &&
// in_array($this->response->type(), ['application/json', 'application/xml'])
// ) {
// $this->set('_serialize', true);
// }
}
bakeで生成される$this->set('_serialize', ['users']);
を削除する。
本当に_serialize
したい部分だけ記載するようにします。
public function index()
{
$users = $this->paginate($this->Users);
$this->set(compact('users'));
// $this->set('_serialize', ['users']);
}
対策3
表示する場合の対策ですが...
表示しない項目はEntityで$_hidden
設定することを心がけましょう。
https://book.cakephp.org/3.0/ja/orm/entities.html#id15