Nancyでのセキュリティー (CSRF)

ナンシーでのセキュリティ対策として、CSRFについて調査しました。
パッケージは Nancy.Hosting.Aspnet と Nancy.Viewengines.Razor を使いました。


ビュー - 登録フォーム (Views/form.cshtml)

ASP.NET MVCと同様に、チェック対象フォームにトークンを組み込みます。
このHTMLヘルパーはNancy.ViewEngines.Razor.HtmlHelpersクラスで定義されています。

<html>
<head>
    <title>登録フォーム</title>
</head>
<body>
    <h1>個人情報を入力してください</h1>
    <form action="~/" method="post">
        <p>名前:<input type="text" name="Namae" /></p>
        <p>年齢:<input type="text" name="Age" /></p>
        <p>カード番号:<input type="text" name="CardNo" /></p>
        <p><input type="submit" value="確認" /></p>
        @Html.AntiForgeryToken()
    </form>
</body>
</html>

ビュー - 登録完了ページ (Views/thanks.cshtml)

<html>
<head>
    <title>登録しました</title>
</head>
<body>
    <h1>ありがとうございました</h1>
</body>
</html>

CSRFチェックを有効にする1

初期化時にNancy.Security.Csrf.Enable()します。
これでリクエスト毎のAfterPipeline処理でトークン作成が行われます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Nancy;
using Nancy.Security;

namespace NancyCsrfForm
{
    public class MyBootstrapper : DefaultNancyBootstrapper
    {
        protected override void ApplicationStartup(Nancy.TinyIoc.TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines)
        {
            base.ApplicationStartup(container, pipelines);
            Csrf.Enable(pipelines);            
            pipelines.OnError.AddItemToEndOfPipeline((ctx, exc) =>
            {
                return exc.Message;
            });
        }
    }
}

CSRFチェックを有効にする2

チェックを行いたいルート処理内で、ValidateCsrfToken()します。
チェック不合格で、CsrfValidationExceptionがthrowされます。

using System.Web;
using Nancy;
using Nancy.ModelBinding;
using Nancy.Extensions;
using Nancy.Security;
using System.IO;

namespace NancyCsrfForm
{
    public class MyModule : NancyModule
    {
        public MyModule()
            : base()
        {

            Get["/"] = _ =>
            {
                return View["form.cshtml"].WithContentType("text/html; charset=UTF-8");
            };

            Post["/"] = _ =>
            {
                this.ValidateCsrfToken();
                var model = this.Bind<FormModel>();
                ViewBag.model = model;
                //todo: 入力チェック
                return Response.AsRedirect("~/regist");
            };

            Get["/regist"] = _ =>
            {
                //todo: 登録処理
                return View["thanks.cshtml"].WithContentType("text/html; charset=UTF-8");
            };
        }
    }

    public class FormModel
    {
        public string Namae { get; set; }
        public int Age { get; set; }
        public string CardNo { get; set; }
        public FormModel() { }
    }
}

動作確認用ページ1

実際の悪さをするページ

<html>
<head><title>CSRFページ</title></head>
<body onload="document.attackform.submit()">
<form name="attackform" action="<登録フォームURL>" method="post">
	<input type="text" name="Namae" value="攻撃太郎" />
	<input type="text" name="Age" value="99" />
	<input type="text" name="CardNo" value="9999-9999-9999-9999" />
	<input type="submit" value="確認" />
</form>
</body>
</html>

動作確認用ページ2

本当は1*1なiframeを使ったりするのでしょうが、今回はわかるように大きくしています。

<html>
<head><title>ダミーページ</title></head>
<h1>ようこそいらっしゃいました!!</h1>
<body>
<iframe src="./attackform.html" width="500px" height="500px" />
</body>
</html>

動作結果

エラー時は、チェック不合格種類を表示しています。
フォーム側のトークンが取得できなかったので、"TokenMissing"が出力されています。
f:id:ham007:20130731185828p:plain