Perl楽しいから好き

Perlをはじめとしたプログラミング周りのあれこれについて。モダーンなPerlを楽しんでいます。

Mojolicious::Lite のcsrf_fieldタグヘルパーでCSRF対策を体感してみた

クロスサイト・リクエスト・フォージェリ(CSRF)対策としてのトークン発行

Webアプリケーションを狙った攻撃としてポピュラーなCSRF。MojoliciousにそんなCSRF対策のためのタグヘルパーがあったので、試してみました。

CSRFだけでなくWebアプリケーションのセキュリティに関しては徳丸本を読んでおくとよいと思います。


>> Webアプリのセキュリティに関するデファクトスタンダード的な徳丸本




Mojolicious::Lite に標準装備されてるCSRF対策を触る

理屈だけじゃなくって、実際に動かしてみるのが一番学習効率が高いと思う。ということで、業務での実運用に使おうと目論んでいるMojolicious でCSRF対策をやってみた。

実行環境は、

OS: Windows10
Perl バージョン: 5.20.2
Mojolicious バージョン: 7.01

です。

ソースは以下の通り。




CSRF対策をした入力フォーム(csrf_protect.pl)

#!"C:\xampp\perl\bin\perl.exe"

use FindBin;
use lib "$FindBin::Bin/../../perl/site/lib/";
use Mojolicious::Lite;

get '/' => {template => 'target'};

post '/' => sub {
    my $c = shift;

    my $validation = $c->validation;
    return $c->render(text => 'BAD CSRF token! No more cracking!', status => 403)
        if $validation->csrf_protect->has_error('csrf_token');

    my $city = $validation->required('city')->param('city');
    $c->render(text => "あなたが住みたい町は $city ですね!")
        unless $validation->has_error;
} => 'target';

app->start;
__DATA__
@@ target.html.ep
<!DOCTYPE html>
<html>
<body>
%= form_for target => begin
    %= csrf_field
    %= label_for city => 'あなたが住みたいと思う町はどこですか?'
    <br /><br />
    %= text_field 'city'
    %= submit_button
% end
</body>
</html>



入力フォームへCSRFトークン無しでPOST送信を試みるスクリプト(csrf_protect_post.pl)

#!"C:\xampp\perl\bin\perl.exe"

use strict;
use warnings;
use LWP::UserAgent;

my $url = 'http://localhost:3000';
my %postdata = ('city' => '一宮市');

my $ua = LWP::UserAgent->new;
my $res = $ua->post($url, \%postdata)->as_string;

print $res;




実行



実際に実行してみましょう。サーバー側とクライアント側両方でスクリプトを実行するのでコマンドプロンプトを2つ立上げます。

f:id:perl48:20160805222446p:plain

まずは、「perl csrf_protect.pl daemon」でMojoliciousを立ち上げます。ポート番号3000でリッスン状態になります。


で、ブラウザからPOSTします。





f:id:perl48:20160805222457p:plain

ブラウザで「localhost:3000」にアクセスすると、簡素なフォームが現れます。





f:id:perl48:20160805222507p:plain

「愛知県稲沢市」と入力して「OK」をクリックすると、





f:id:perl48:20160805222514p:plain

入力した情報が表示されます。いたって普通です。





f:id:perl48:20160805222528p:plain

サーバー側の通信履歴は上の画像の通りです。ちゃんと「200 OK」が2回出てますね。







では、CSRFトークンなしでPOSTしてみます。



f:id:perl48:20160805222536p:plain

2つめのコマンドプロンプト画面で「perl csrf_protect_post.pl」と打ちます。





f:id:perl48:20160805222547p:plain

見事に「BAD CSRF token! ,,,」のメッセージが返されました。送信した「一宮市」は住みたい町になれない結果に。





f:id:perl48:20160805222555p:plain

サーバー側の通信履歴を見ると、最後の行が「403 Forbidden」になってます。CSRFトークンが無いから非表示されたことがわかります。





実験成功です!



f:id:perl48:20160805222604p:plain

入力フォームのソースコードを見てみると、


<input name="csrf_token" type="hidden" value="58054b73ccfe687d02d51bc6a3e2200d7be31fcb">

という部分があります。



ここに入ってるトークン(アクセスごとにランダム生成)を一緒にPOST送信して、サーバー側のバリデーションOKなら正常なレスポンス。NGなら、403エラーになるよ、という流れですね。



ロジックとしてはシンプルですが、実装して動かして体感すると理解が腹に落ちる感じがします。少なくとも僕にとって「Shut the fuck up and write some code」は有効みたいです。





■参考にさせてもらったURL

日本語版 Mojoliciousドキュメント(木本さん)
https://github.com/yuki-kimoto/mojolicious-guides-japanese/wiki/Mojolicious%3A%3AGuides%3A%3ARendering#user-content-クロスサイトリクエストフォージェリcsrfgithub.com



英語版 Mojolicious ドキュメント
http://mojolicious.org/perldoc/Mojolicious/Guides/Rendering#Cross-site%20request%20forgeryMojolicious::Guides::Rendering - Rendering content