Mojolicious の XSS 対策
Mojolicious の XSS 対策は簡単で、テンプレートで <%= =>
形式で記述すればエスケープで出力されます。
※ <%== =>
で書くとエスケープされない。
<input name="company_name" type="text"value="<%= $company_name %>">
XSS は javascript が画面に表示されて実行されることによる問題なので
出力時にエスケープすれば問題ありません。
HTML メールを送っている場合も同様です。
メールテンプレートでエスケープすればいい。
Mojolicious のセッション漏洩、セッションハイジャック対策
ご存知の方も多いと思いますが Mojolicious はセッション機能に問題があります。普通はセッションIDのみを cookie に保存しデータはサーバ側に保存しますが、Mojolicious の場合はデータをパスフレーズと組み合わせて Base64 にエンコードして cookie に保存しています。Base64 はデコード可能なのでパスフレーズが分かれば値が見えてしまいます。
また、セキュリティとは関係ないですがデータを cookie に入れているのでデータが大きいと正常に動かなくなります。
以下はデフォルトのままセッションを保存したものですが、様々な問題があることがわかります。
設定しだいである程度は対策できますが、cookie にデータそのものを保存するという仕様はどうにもできません。
この対策としては他のライブラリを使うことです。僕の場合はオーソドックスに CGI::Session を使いました。
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 } );
}
- セッションIDのキー(クッキー名)をオリジナルにすることで使われているフレームワークを予測できなくします。
- 保存されるセッションIDは72文字で、ブルートフォース攻撃されてもそれなりに時間がかかるでしょう。
- 有効期限を短くすればブルートフォース攻撃でセッションIDが分かった頃には期限が切れて使えなくなっています。
- cookie を HTTP 通信のみにすることで、仮に XSS 脆弱性があっても javascript で cookie(セッション)を盗めなくなります。
- cookie を SSL 通信のみにすることでセッションIDが常に暗号化されるので、セッションIDが推測されにくくなります。
- GET クエリでセッションIDを送ってきたらリダイレクトしてセッションIDを指定できないようにする。
Mojolicious の CSRF 対策
最新の Mojolicious では CSRF 対策がされているようです。
4.59から導入されたCSRF対策を使ってみる
サーバ環境の問題で古い Mojolicious を使う必要があったので、以下のプラグインをカスタマイズしました。
Mojolicious::Plugin::CSRFDefenderというのを作った
カスタマイズといっても、エラーだった場合の処理を変えているだけですが...
# # 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 でできるセッション対策を施しておきます。
# cookieを暗号化
app->secret('passphrase');
app->sessions->secure(1);
app->sessions->cookie_name('csrftoken');