FuelPHPをやってみる (16) - CSRFに関して -
CSRF関係のAPIも存在するのでそれを利用すれば良い模様。あくまでFuelPHP APIの話なだけですので
fuel/app/views/home.php
<html>
<body>
<form action="/" method="POST">
<input type="hidden" name="<?php echo Config::get("security.csrf_token_key") ?>" value="<?php echo Security::fetch_token() ?>" />
<input type="submit" />
</form>
</body>
</html>
Security::fetch_tokenを使ってCSRFに使うワンタイムトークンのような物を生成出来る模様。んでCSRFチェックを行う際にSecurityクラスで使うメソッドがConfig::get("security.csrf_token_key")を参照してポストされたトークンを取得すると思われるのでそれを確認しておく(デフォルトはfuel_csrf_token)。まぁ要はキー名ってだけなんだけど
fuel/app/classes/controller/home.php
<?php
class Controller_Home extends Controller {
public function get_index() {
return View::forge("home");
}
public function post_index() {
if (Security::check_token()) {
return Response::forge("OK");
}
return Response::forge("NG");
}
}
CSRFチェックを行うにはSecurity::check_tokenを使用する。本来であれば、CSRFがfailした際には正常リクエストな場合で出すステータスとは別にした方が良いかと思われる(そうじゃないとコントローラーテストで識別しにくいのではと)
一回普通にポストしてリダイレクト等をせずリロードした場合には、既にセッション内に確保されている(と思われる)CSRFトークンは削除されている(ワンタイムである為)。なのでリロードしてもその時点でセッション内トークンは削除されているのでCSRFチェックはfailすると思われる
でhiddenを設定しない、もしくは別の領域からポストされている場合には送信元認証がされてない事によりCSRFチェックはfailする
っていう感じでしょうかね。んじゃこのコントローラーのテストケースを書くには
fuel/app/tests/controller/home_test.php
<?php
class Test_Controller_Home extends TestCase {
public function test_post_index() {
$key = Config::get("security.csrf_token_key");
$val = Security::fetch_token();
$res = Request::forge(
"http://localhost/",
array(
"driver" => "Curl",
"options" => array(CURLOPT_COOKIE => "$key=$val;")
)
)->set_method(
"POST"
)->execute(
array($key => $val)
)->response();
$this->assertEquals("OK", $res->body);
}
}
っていう感じじゃないですかね。CurlドライバーなRequestクラスを使い、クッキーにfetch_tokenした結果をぶち込んでおく。んでPOSTする際にも同じトークンをぶち込む(ここの値を変えるとCSRFチェックがfailする)
な感じですかねと。ぶっちゃけこれ気休め程度にしかならないんじゃないのかっていう疑問もちょっとあるんですが
備考1: JavaScript用のCSRFトークン機能に関して
Security::js_fetch_tokenを使うとJavaScriptで利用する用のCSRFトークンを取得する関数が定義されるのですが、これを使うと
function fuel_csrf_token() {
if (document.cookie.length > 0) {
var c_name = "fuel_csrf_token";
c_start = document.cookie.indexOf(c_name + "=");
if (c_start != -1) {
c_start = c_start + c_name.length + 1;
c_end = document.cookie.indexOf(";" , c_start);
if (c_end == -1) {
c_end=document.cookie.length;
}
return unescape(document.cookie.substring(c_start, c_end));
}
}
return "";
}
という風になっている。見てわかる通りにJavaScriptからクッキーにアクセスしているんですが、cookieがhttponlyな場合はJavaScriptからアクセスする事が出来ないはずなのでfuel/app/config/config.phpに
<?php
return array(
"cookie" => array("http_only" => true)
);
とするとFuelPHP上のクッキーはhttponlyが付与されるようになりますが、この状態で上記のJavaScript用のfuel_csrf_tokenを実行しても取得出来なくなるっていうのがある