Mojolicious のセキュリティ対策

### Mojolicious の XSS 対策
Mojolicious の XSS 対策は簡単で、テンプレートで `<%= =>` 形式で記述すればエスケープで出力されます。
※ `<%== =>` で書くとエスケープされない。

```html
<input name="company_name" type="text"value="<%= $company_name %>">
```

XSS は javascript が画面に表示されて実行されることによる問題なので
出力時にエスケープすれば問題ありません。
HTML メールを送っている場合も同様です。
メールテンプレートでエスケープすればいい。

### Mojolicious のセッション漏洩、セッションハイジャック対策
ご存知の方も多いと思いますが Mojolicious はセッション機能に問題があります。普通はセッションIDのみを cookie に保存しデータはサーバ側に保存しますが、Mojolicious の場合はデータをパスフレーズと組み合わせて Base64 にエンコードして cookie に保存しています。Base64 はデコード可能なのでパスフレーズが分かれば値が見えてしまいます。
また、セキュリティとは関係ないですがデータを cookie に入れているのでデータが大きいと正常に動かなくなります。

以下はデフォルトのままセッションを保存したものですが、様々な問題があることがわかります。
![対策前.jpg](https://qiita-image-store.s3.amazonaws.com/0/9064/f912cdc4-2fa4-64f8-57ca-d89542fddbf6.jpeg)

設定しだいである程度は対策できますが、cookie にデータそのものを保存するという仕様はどうにもできません。

この対策としては他のライブラリを使うことです。僕の場合はオーソドックスに CGI::Session を使いました。

```perl
use CGI::Session;
use CGI::Session::ExpireSessions;
use Mojolicious::Lite;

my $app = app;
my $session;

post '/send' => sub {
    my $c = shift;
    
    # セッションを読み込む
    &load_session($c);
    
    # 何らかの処理
};

# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ■ セッション
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
sub load_session
{
	my $c = shift;
	
	# セッションを保存するディレクトリ
	my $session_dir = defined $c->app->config->{'session'}->{'session_dir'} ? $c->app->config->{'session'}->{'session_dir'} : 'session';
	# 有効期限
	my $expire = defined $c->app->config->{'session'}->{'expire'} ? $c->app->config->{'session'}->{'expire'} : 1200;
	# クッキー名
	my $cookie_name = defined $c->app->config->{'session'}->{'cookie_name'} ? $c->app->config->{'session'}->{'cookie_name'} : 'htwsid';
	# パス
	my $path = defined $c->app->config->{'session'}->{'path'} ? $c->app->config->{'session'}->{'path'} : '/';
	# cookieはSSLのみ
	my $secure = defined $c->app->config->{'session'}->{'secure'} ? $c->app->config->{'session'}->{'secure'} : 1;
	# cookieはhttpのみ
	my $httponly = defined $c->app->config->{'session'}->{'httponly'} ? $c->app->config->{'session'}->{'httponly'} : 1;
	
	# cookie名をセット
	CGI::Session->name($cookie_name);
	
	# 古くなったセッションファイルを削除 1h
	CGI::Session::ExpireSessions -> new(temp_dir => $session_dir, delta => 3600 ) -> expire_file_sessions();
	
	# GETリクエストによるセッションIDの指定の無効化
	$c->redirect_to('/') and exit if $c->param($cookie_name);
	
	# cookieからセッションIDを探す。なければ undef。
	my $sid = $c->signed_cookie($cookie_name) || undef;
	$session = CGI::Session->load(undef, $sid, { Directory => $session_dir }) or die CGI::Session->errstr();
	
	# 取得したセッションidが有効ならそのまま。無効（空or有効期限切れ）なら別のidを発番。
	if ( $session->is_empty || $session->is_expired )
	{
		$session = $session->new(undef, $sid, { Directory => $session_dir }) or die $session->errstr;
	}
	
	# cookieをセット
	$sid = $session->id();
	$c->signed_cookie( $cookie_name, $sid, { expires => time + $expire, path => $path, secure => $secure, httponly => $httponly } );
}
```

対策後の cookie の状態。
![対策後.jpg](https://qiita-image-store.s3.amazonaws.com/0/9064/28eb58cf-7245-62d5-5ce8-263a7c6f8e10.jpeg)

* セッションIDのキー（クッキー名）をオリジナルにすることで使われているフレームワークを予測できなくします。
* 保存されるセッションIDは72文字で、ブルートフォース攻撃されてもそれなりに時間がかかるでしょう。
* 有効期限を短くすればブルートフォース攻撃でセッションIDが分かった頃には期限が切れて使えなくなっています。
* cookie を HTTP 通信のみにすることで、仮に XSS 脆弱性があっても javascript で cookie（セッション）を盗めなくなります。
* cookie を SSL 通信のみにすることでセッションIDが常に暗号化されるので、セッションIDが推測されにくくなります。
* GET クエリでセッションIDを送ってきたらリダイレクトしてセッションIDを指定できないようにする。


### Mojolicious の CSRF 対策
最新の Mojolicious では CSRF 対策がされているようです。
[4.59から導入されたCSRF対策を使ってみる](http://qiita.com/yusukebe/items/430804b6a9511da6fb3a)

サーバ環境の問題で古い Mojolicious を使う必要があったので、以下のプラグインをカスタマイズしました。
[Mojolicious::Plugin::CSRFDefenderというのを作った](http://blog.shibayu36.org/entry/20110210/1297322357)

カスタマイズといっても、エラーだった場合の処理を変えているだけですが...

```perl:CSRFDefender.pm
#    # input check
#    $app->hook(after_static_dispatch => sub {
#        my ($c) = @_;
#        unless ($self->_validate_csrf($c)) {
#            my $content;
#            if ($self->error_template) {
#                my $file = file($self->error_template);
#                $content = $file->slurp;
#            }
#            else {
#                $content = $self->{error_content},
#            }
#            $c->render(
#                status => $self->{error_status},
#                text   => $content,
#            );
#        };
#    });

    # input check
    # CSRFエラーの場合はフォームに戻る
    $app->hook(after_static_dispatch => sub {
        my ($c) = @_;
        unless ($self->_validate_csrf($c)) {
            $c->redirect_to('/');
        };
    });
```

Mojolicious のセッション機能を使うので、Mojolicious でできるセッション対策を施しておきます。

```perl
# cookieを暗号化
app->secret('passphrase');
app->sessions->secure(1);
app->sessions->cookie_name('csrftoken');
```