CSRF対策用コンポーネントとヘルパー
CSRF対策のトークンチェックは、結局自作で入れる事にして、
Token作成およびチェックのコンポーネントとヘルパーを作成しました。
プラグインにまとめても良さそうなのですが、良い名称が思い浮かばないので保留 *1
(2011/2/10 コメント指摘を受けて修正しました)
作成したコンポーネントとヘルパーのソースは末尾。
Tokenコンポーネント
Authコンポーネントの$ActionMapを見て、未設定/read以外(create, delete, update)のアクションであれば、POSTがある場合Tokenチェックを行なう。
Tokenがない、あるいは正しくない場合、処理停止。
コントローラ・ビュー側の対応
Tokenチェックはapp_controllerのbeforeFilterで実施。
// 対CSRF:Tokenチェック $this->Token->checkToken();
Tokenが必要なviewにはformタグ内に以下の通り追記。
echo $token->create();
create/delete/updateのいずれかだけど、Tokenチェックをさせないアクションがある場合は、個々のコントローラに以下の変数を設定。
var $disableTokenActions = array('add','mobile_add');
* で全アクションで不使用。
長時間同じセッションIDだと無用心なので、有効期限もちょっと変えました*2
- Configure::write('Session.timeout', '3600'); + Configure::write('Session.timeout', '432');
自前の認証コンポーネント仕様になっていますが、AuthPlusをAuthに置換すればCakePHPデフォルトのAuthコンポーネント対応で使用できるはずです。
全差分
Index: controllers/components/token.php =================================================================== --- controllers/components/token.php (revision 0) +++ controllers/components/token.php (revision 0) @@ -0,0 +1,111 @@ +<?php +/** + * CSRF対策用Tokenチェッカー + * Security.level = medium または low のみ + */ + +class TokenComponent extends Object +{ + +/** + * Components used by TokenHelper + * + * @var array + * @access public + */ + var $components = array('Session'); + + var $_modelClass; + var $_data = array(); + var $_action; + var $_actionMap = array(); + var $type; + var $useToken = false; + var $disableActions = array(); + + function initialize(&$controller) + { + $this->_modelClass = $controller->modelClass; + $this->_action = $controller->action; + if (isset($controller->params['data'])) { + $this->_data = $controller->params['data']; + } + + if (isset($controller->AuthPlus)) { + $this->_actionMap = $controller->AuthPlus->actionMap; + } else { + return ; + } + + if (isset($this->_actionMap[$this->_action])) { + $this->type = $this->_actionMap[$this->_action]; + } + + if (!isset($controller->disableTokenActions)) { + $this->useToken = false; + } else { + $this->useToken = $this->isUseToken($controller->disableTokenActions); + } + + $this->Session->startup($controller); + } + + /* true: Token OK */ + function checkToken($tag_name = '__Token', $hash_type = 'md5') + { + if ($this->useToken === false) { + return ; + } + $hashed_session_id = $this->get_hashed_session_id(); + + if ($this->_data) { + if (!isset($this->_data[$this->_modelClass][$tag_name])) { + $this->_blackHole(); + } + if ($this->_data[$this->_modelClass][$tag_name] != $hashed_session_id) { + $this->_blackHole(); + } + } else { + return ; + } + } + + /* true:Token使用 */ + function isUseToken($disableTokenActions) + { + if ($disableTokenActions == '*') { + return false; + } + if (!$this->type || $this->type == 'read') { + return false; + } + if (in_array($this->_action, (array)$disableTokenActions)) { + return false; + } + + return true; + } + + function _blackHole($msg='') + { + if (!$msg) { + $msg = _('ILLEGAL POST!'); + } + + die($msg); + } + + /* 現在のセッションIDを暗号化して取得 */ + function get_hashed_session_id($hash_type = 'md5') + { + $session_id = $this->Session->id(null); + + if (!$session_id) { + $this->_blackHole('No Session.'); + } + + return Security::hash($session_id. Configure::read('Security.salt'), $hash_type); + } + +} + Index: views/helpers/token.php =================================================================== --- views/helpers/token.php (revision 0) +++ views/helpers/token.php (revision 0) @@ -0,0 +1,38 @@ +<?php +/** + * CSRF対策用Token出力ヘルパー + * 要Formヘルパー + */ + +class TokenHelper extends AppHelper { +/** + * Other helpers used by TokenHelper + * + * @var array + * @access public + */ + var $helpers = array('Form', 'Session'); + + /* Tokenをセットしたhiddenタグ出力 */ + function create($tag_name = '__Token', $hash_type = 'md5') + { + $hashed_id = $this-> get_hashed_session_id($hash_type); + + return $this->Form->input($tag_name, array( + 'type' => 'hidden', + 'value' => $hashed_id, + ) + ); + } + + /* 現在のセッションIDを暗号化して取得 */ + function get_hashed_session_id($hash_type = 'md5') + { + $session_id = $this->Session->id(); + + return Security::hash($session_id. Configure::read('Security.salt'), $hash_type); + } + +} + +
Index: controllers/app_controller.php =================================================================== --- controllers/app_controller.php (revision 196) +++ controllers/app_controller.php (working copy) @@ -21,17 +21,23 @@ var $isAdmin = false; var $isMobile = false; var $components = array( 'AuthPlus', 'Acl', + 'Token' ); /* ACL */ // 追加アクション用 crudMap var $actionMapPlus = array(); + // POSTのTokenチェックをしないアクション + var $disableTokenActions = array(); + function beforeFilter() { parent::beforeFilter(); @@ -46,6 +52,9 @@ $this->AuthPlus->actionPath = 'controllers/'; $this->AuthPlus->authorize = 'crud'; + // 対CSRF:Tokenチェック + $this->Token->checkToken(); + // 認証アクション設定 if (Configure::read('mobileUserAgent')) { $this->AuthPlus->loginAction = '/m/users/login'; Index: controllers/users_controller.php =================================================================== --- controllers/users_controller.php (revision 196) +++ controllers/users_controller.php (working copy) @@ -2,7 +2,11 @@ class UsersController extends ModuleController { var $name = 'Users'; var $helpers = array( 'Html', 'Form', + 'Token' ); /* ACL */ // 追加アクション用 crudMap @@ -11,6 +15,8 @@ 'change_password' => 'update', ); + var $disableTokenActions = array('add','mobile_add'); + function beforeFilter() { parent::beforeFilter();
--- views/users/edit.ctp (revision 197) +++ views/users/edit.ctp (working copy) @@ -7,6 +7,7 @@ 'label' => __('YourName', true), ) ); + echo $token->create(); ?> </fieldset> <?php echo $form->end('Submit');?>
--- config/core.php.sample (revision 196) +++ config/core.php.sample (working copy) @@ -122,7 +122,7 @@ * Session time out time (in seconds). * Actual value depends on 'Security.level' setting. */ - Configure::write('Session.timeout', '3600'); + Configure::write('Session.timeout', '432'); // 60 * 60 * 12 / 100 /** * If set to false, sessions are not automatically started. */