CakePHPでCSRF対策

CakePHPCSRF対策するにはSecurityコンポーネントを使うと簡単にできるらしいけど、色々ぐぐってみるとなかなか一筋縄でいかなそうであるのと、どうもhiddenの値を書き換えるとエラーを返してくれる高機能がもれなくついてくるらしかったので、自前でトークンを作るコンポーネントを導入しました。


といっても下記URLにまさに探してたようなものがあったので、それを参考に作成しました。ほぼまんまですけど。
http://d.hatena.ne.jp/cake67/20091217/1261019930


しかも機能としてはややダウングレードしてる部分もあったり。
とりあえずものすごーく単純に、下記のような仕様に。

  • コンポーネントは渡されたトークンをチェックしてtrue/falseを返すだけで、エラー時の処理はaction,controller単位でお任せする。
  • ヘルパーはトークンを吐き出すだけ。

使い方は単純に各アクションに

if(!$this->Token->checkToken()){
  //error
}

っていれるだけ。
viewの方には

$token->create();

と入れるだけの簡単なお仕事です。

ソースは下記のような感じ。

component

<?php
/**
 * CSRF対策用Tokenチェック
 */
define('GET' , 'get');
define('POST' , 'post');
class TokenComponent extends Object
{

	/**
	 * Components used by TokenHelper
	 *
	 * @var array
	 * @access public
	 */
	var $components = array('Session');

	var $_form = array();
	var $_url = array();
	var $_action;
	var $type;
	var $useToken = true;

	function initialize(&$controller)
	{
		$this->_action = $controller->action;
		if(isset($controller->params['form'])){
			$this->_form = $controller->params['form'];
		}
		if(isset($controller->params['url'])){
			$this->_url = $controller->params['url'];
		}

		$this->Session->startup($controller);
	}

	/* true: Token OK */
	function checkToken($method = POST ,$tag_name = '__Token', $hash_type = 'md5')
	{
		if ($this->useToken === false) {
			return true;
		}

		$hashed_session_id = $this->get_hashed_session_id($hash_type);//元のソースには入ってなかったけどmd5以外を指定するなら必要だよね?
		if($this->_form){
			if(POST == strtolower($method)){
				if(isset($this->_form[$tag_name])){
					return $this->_form[$tag_name] == $hashed_session_id;
				}
			}
			else{
				if(isset($this->_url[$tag_name])){
					return $this->_url[$tag_name] == $hashed_session_id;
				}
			}
			
		}
		return false;
	}

	/* 現在のセッションIDを暗号化して取得 */
	function get_hashed_session_id($hash_type = 'md5')
	{
		$session_id = $this->Session->id(null);

		if (!$session_id) {
			//$this->_blackHole('No Session.');
			return false;
		}

		return Security::hash($session_id. Configure::read('Security.salt'), $hash_type);
	}

}

helper

<?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', $id = '__Token' ,$hash_type = 'md5')
	{
		$hashed_id = $this-> get_hashed_session_id($hash_type);

		return '<input type="hidden" name="'.$tag_name.'" value="'.$hashed_id.'" id="'.$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);
	}

}

Ajaxの際にも使用したかったので、inputタグを吐くところはFormHelperは使用せず普通にべた書きしてます。
まぁAjaxのときでも普通にkeyにdata["__Token"]とかしてやれば、dataに入ってくるのかな?