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"が出力されています。