CodeIgniter3のSecurityクラスのCSRF対策を把握しておく

# CodeIgniter3のSecurityクラスのCSRF対策を把握しておく

CodeIgniterのCSRF対策がCookieで管理していたので、Sessionじゃないとダメじゃないか？
と思って勢いで書いてみたけど
理にかなっていました。

この記事には嘘が記載されている可能性があります。
追記: Cookieの改ざんがありえる以上、安全でないかもしれません。(CodeIgniter4.1.5以前は)
https://togetter.com/li/316198
https://twitter.com/ockeghem/status/879876546034520065
https://blog.tokumaru.org/2013/09/cookie-manipulation-is-possible-even-on-ssl.html



## 確認したCodeIgniter

CodeIgniter3 origin/3.1-stable 5d6e77b092ca8f1700a7407bf59bcab6b0e30808


## 準備

実際にCodeIgniter3で以下の様に実装してCSRFを有効にしています。

```php:config/config.php
<?php
// ...
$config['csrf_protection'] = true;
// ...
```

```php:controllers/Csrfcheck.php
<?php
class Csrfcheck extends CI_Controller {

    public function attack()
    {
        if ( $this->input->method(TRUE) !== 'POST' )
        {
            show_404();
        }
        echo 'Success!!';
    }
}
```

## 検証

この状態でCURLからPOSTリクエストを行ってもエラーが発生します。

```shell-session
$ curl -F "csrf_test_name=value1" http://localhost/csrfcheck/attack
```

しかし、CodeIgniterのSecurityクラスではCookieの値とPOSTの値が同一であるかを判定しているのでCookieに同一の値を付与するとリクエストが通ってしまいます。

```shell-session
$ curl -F "csrf_test_name=value1" -b "csrf_cookie_name=value1"  http://localhost/csrfcheck/attack
Success!!
```

## これは危険ではないか！？

Cookie変更できる脆弱性があった場合にこれではcsrf_cookie_nameを書き換えて、csrf_test_nameを同一のPOST値を入れられて危ないのではないかと考えましたが、

Cookieの値は基本的にクロスドメインでの読み書きはできないためこの様な実装になっているものかと思います。

そのため、CodeIgniterの実装のようにCookieでのCSRF対策は理にかなってる実装だなと思い勉強になりました。

## Cookieであることの便利さ

例えばCSRF有効にしているページでAjaxリクエストを発生させなければいけない時に、PHP側で都度Ajaxのレスポンスで`$this->security->get_csrf_hash();`の値を含ませずともCookieからとれるのでコードがすっきりします。

ただし、JSで書き換える事ができる`cookie_httponly`をTRUEに設定する事がセキュアでは

```php:views/sample.php
<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<title></title>
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
</head>
<body>
	<a href="">Ajaxリクエストのトリガー</a>
	<?php echo form_open('welcome/csrfcheck/');?>
        <input type="text" name="request">
	<input type="submit">
	<?php echo form_close();?>
	<script>
		var hidden = $('input[name="<?php echo config_item('csrf_token_name');?>"]');
		$('a:first').click(function(ev) {
			ev.preventDefault();
			$.ajax({
				url: '<?php echo site_url('csrfcheck/ajax_api')?>',
				method: 'post',
                                // cookieからcsrfの値を取得
				data: {'csrf_test_name': getCsrfCookie()}
			}).done(function() {
				hidden.val(getCsrfCookie());
			});
		});

		/**
		 * cookieからcsrfのトークンの値を取得する関数
		 **/
		function getCsrfCookie(){
			var cookies = document.cookie.split("; ");
			for (var i = 0; i < cookies.length; i++) {
				var key, value;
				[key, value] = cookies[i].split("=");
				if (key !== '<?php echo config_item('csrf_cookie_name');?>') {
					continue;
				}
				return value;
			}
		}
	</script>
</body>
</html>
```

## まとめ

CodeIgniterのCSRFはCookieとPOST値を比較していますが
安全です。

追記: ただし、Cookieの改ざんがありえる以上、安全でないかもしれません。
https://togetter.com/li/316198
https://twitter.com/ockeghem/status/879876546034520065
https://blog.tokumaru.org/2013/09/cookie-manipulation-is-possible-even-on-ssl.html


また、現状ではCodeIgniter4のCSRF Filterも同じ様に実装されています。


参考：https://github.com/bcit-ci/CodeIgniter/blob/3.1-stable/system/core/Security.php

コメントありがとうございます。
CodeIgniter 4.1.5でsessionによるCSRFが追加されたようです。

https://codeigniter4.github.io/CodeIgniter4/libraries/security.html#csrf-protection-methods