web-dev-qa-db-ja.com

jQuery Ajax呼び出しとHtml.AntiForgeryToken()

インターネットに関するブログ投稿で読んだ情報に従って、アプリに CSRF攻撃 の緩和策を実装しました。特に、これらの投稿は私の実装の推進力でした

基本的に、これらの記事と推奨事項では、CSRF攻撃を防ぐために誰でも次のコードを実装する必要があると述べています。

1)POST Http動詞を受け入れるすべてのアクションに[ValidateAntiForgeryToken]を追加します

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SomeAction( SomeModel model ) {
}

2)サーバーにデータを送信するフォーム内に<%= Html.AntiForgeryToken() %>ヘルパーを追加します

<div style="text-align:right; padding: 8px;">
    <%= Html.AntiForgeryToken() %>
    <input type="submit" id="btnSave" value="Save" />
</div>

とにかく、アプリの一部で、フォームをまったく持たずにサーバーにjQueryを使用してAjax POSTを実行しています。これは、たとえば、ユーザーが画像をクリックして特定のアクションを実行できるようにする場合に発生します。

アクティビティのリストを含むテーブルがあるとします。テーブルの列に「アクティビティを完了済みとしてマークする」という画像があり、ユーザーがそのアクティビティをクリックすると、次のサンプルのようにAjax POSTを実行しています。

$("a.markAsDone").click(function (event) {
    event.preventDefault();
    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: {},
        success: function (response) {
            // ....
        }
    });
});

これらの場合に<%= Html.AntiForgeryToken() %>を使用するにはどうすればよいですか? Ajax呼び出しのdataパラメーター内にヘルパー呼び出しを含める必要がありますか?

長い投稿でごめんなさい、手伝ってくれてありがとう

編集

jayrdub answerに従って、私は次のように使用しました

$("a.markAsDone").click(function (event) {
    event.preventDefault();
    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: {
            AddAntiForgeryToken({}),
            id: parseInt($(this).attr("title"))
        },
        success: function (response) {
            // ....
        }
    });
});
192
Lorenzo

私はこのようなシンプルなjs関数を使用します

AddAntiForgeryToken = function(data) {
    data.__RequestVerificationToken = $('#__AjaxAntiForgeryForm input[name=__RequestVerificationToken]').val();
    return data;
};

ページ上のすべてのフォームはトークンに対して同じ値を持つため、最上位のマスターページに次のようなものを配置するだけです。

<%-- used for ajax in AddAntiForgeryToken() --%>
<form id="__AjaxAntiForgeryForm" action="#" method="post"><%= Html.AntiForgeryToken()%></form>  

次に、ajaxコールでdo(2番目の例に合わせて編集)

$.ajax({
    type: "post",
    dataType: "html",
    url: $(this).attr("rel"),
    data: AddAntiForgeryToken({ id: parseInt($(this).attr("title")) }),
    success: function (response) {
        // ....
    }
});
238
JeremyWeir

360Airwalkが提供するソリューションは気に入っていますが、少し改善される可能性があります。

最初の問題は、空のデータで$.post()を作成した場合、jQueryはContent-Typeヘッダーを追加しないため、この場合、ASP.NET MVCはトークンの受信とチェックに失敗することです。したがって、ヘッダーが常にそこにあることを確認する必要があります。

別の改善点は、すべてのHTTP動詞のサポートですwith content:POST、PUT、DELETEなど。アプリケーションでPOSTのみを使用できますが、一般的なソリューションを使用して、受信するすべてのデータを確認することをお勧めします動詞には偽造防止トークンがあります。

$(document).ready(function () {
    var securityToken = $('[name=__RequestVerificationToken]').val();
    $(document).ajaxSend(function (event, request, opt) {
        if (opt.hasContent && securityToken) {   // handle all verbs with content
            var tokenParam = "__RequestVerificationToken=" + encodeURIComponent(securityToken);
            opt.data = opt.data ? [opt.data, tokenParam].join("&") : tokenParam;
            // ensure Content-Type header is present!
            if (opt.contentType !== false || event.contentType) {
                request.setRequestHeader( "Content-Type", opt.contentType);
            }
        }
    });
});
29
Bronx

他にもたくさんの答えがあることは知っていますが、この記事は簡潔で簡潔であり、HttpPostの一部だけではなく、すべてのHttpPostを確認する必要があります。

http://richiban.wordpress.com/2013/02/06/validating-net-mvc-4-anti-forgery-tokens-in-ajax-requests/

フォームコレクションを変更しようとする代わりに、HTTPヘッダーを使用します。

サーバ

//make sure to add this to your global action filters
[AttributeUsage(AttributeTargets.Class)]
public class ValidateAntiForgeryTokenOnAllPosts : AuthorizeAttribute
{
    public override void OnAuthorization( AuthorizationContext filterContext )
    {
        var request = filterContext.HttpContext.Request;

        //  Only validate POSTs
        if (request.HttpMethod == WebRequestMethods.Http.Post)
        {
            //  Ajax POSTs and normal form posts have to be treated differently when it comes
            //  to validating the AntiForgeryToken
            if (request.IsAjaxRequest())
            {
                var antiForgeryCookie = request.Cookies[AntiForgeryConfig.CookieName];

                var cookieValue = antiForgeryCookie != null
                    ? antiForgeryCookie.Value 
                    : null;

                AntiForgery.Validate(cookieValue, request.Headers["__RequestVerificationToken"]);
            }
            else
            {
                new ValidateAntiForgeryTokenAttribute()
                    .OnAuthorization(filterContext);
            }
        }
    }
}

クライアント

var token = $('[name=__RequestVerificationToken]').val();
var headers = {};
headers["__RequestVerificationToken"] = token;

$.ajax({
    type: 'POST',
    url: '/Home/Ajax',
    cache: false,
    headers: headers,
    contentType: 'application/json; charset=utf-8',
    data: { title: "This is my title", contents: "These are my contents" },
    success: function () {
        ...
    },
    error: function () {
        ...
    }
});
21
viggity

Html.AntiForgeryTokenは使用しないでください。代わりに、Web APIからAntiForgery.GetTokensおよびAntiForgery.Validateを使用します ASP.NET MVCアプリケーションでのクロスサイトリクエストフォージェリ(CSRF)攻撃の防止 で説明されています。

19
Edward Brey

ここでは高度なネクロマンサーのように感じますが、これは4年後のMVC5でもまだ問題です。

Ajaxリクエストを適切に処理するには、Ajax呼び出しで偽造防止トークンをサーバーに渡す必要があります。投稿データとモデルに統合するのは面倒で不必要です。トークンをカスタムヘッダーとして追加することは、クリーンで再利用可能です。また、毎回行うことを覚えておく必要がないように構成できます。

例外があります-目立たないajaxはajax呼び出しの特別な処理を必要としません。トークンは、通常の非表示の入力フィールドで通常どおり渡されます。通常のPOSTとまったく同じです。

_Layout.cshtml

_layout.cshtmlには、このJavaScriptブロックがあります。 DOMにトークンを書き込むのではなく、jQueryを使用して、MVC Helperが生成する非表示の入力リテラルからトークンを抽出します。ヘッダー名であるマジック文字列は、属性クラスの定数として定義されています。

<script type="text/javascript">
    $(document).ready(function () {
        var isAbsoluteURI = new RegExp('^(?:[a-z]+:)?//', 'i');
        //http://stackoverflow.com/questions/10687099/how-to-test-if-a-url-string-is-absolute-or-relative

        $.ajaxSetup({
            beforeSend: function (xhr) {
                if (!isAbsoluteURI.test(this.url)) {
                    //only add header to relative URLs
                    xhr.setRequestHeader(
                       '@.ValidateAntiForgeryTokenOnAllPosts.HTTP_HEADER_NAME', 
                       $('@Html.AntiForgeryToken()').val()
                    );
                }
            }
        });
    });
</script>

beforeSend関数での単一引用符の使用に注意してください-レンダリングされる入力要素は、JavaScriptリテラルを壊す二重引用符を使用します。

クライアントJavaScript

これを実行すると、上記のbeforeSend関数が呼び出され、AntiForgeryTokenが要求ヘッダーに自動的に追加されます。

$.ajax({
  type: "POST",
  url: "CSRFProtectedMethod",
  dataType: "json",
  contentType: "application/json; charset=utf-8",
  success: function (data) {
    //victory
  }
});

サーバーライブラリ

非標準トークンを処理するには、カスタム属性が必要です。これは@viggityのソリューションに基づいていますが、控えめなAjaxを正しく処理します。このコードは、共通のライブラリに隠れることができます

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ValidateAntiForgeryTokenOnAllPosts : AuthorizeAttribute
{
    public const string HTTP_HEADER_NAME = "x-RequestVerificationToken";

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        var request = filterContext.HttpContext.Request;

        //  Only validate POSTs
        if (request.HttpMethod == WebRequestMethods.Http.Post)
        {

            var headerTokenValue = request.Headers[HTTP_HEADER_NAME];

            // Ajax POSTs using jquery have a header set that defines the token.
            // However using unobtrusive ajax the token is still submitted normally in the form.
            // if the header is present then use it, else fall back to processing the form like normal
            if (headerTokenValue != null)
            {
                var antiForgeryCookie = request.Cookies[AntiForgeryConfig.CookieName];

                var cookieValue = antiForgeryCookie != null
                    ? antiForgeryCookie.Value
                    : null;

                AntiForgery.Validate(cookieValue, headerTokenValue);
            }
            else
            {
                new ValidateAntiForgeryTokenAttribute()
                    .OnAuthorization(filterContext);
            }
        }
    }
}

サーバー/コントローラー

これで、アクションに属性を適用するだけです。さらに良いことに、コントローラーに属性を適用すると、すべてのリクエストが検証されます。

[HttpPost]
[ValidateAntiForgeryTokenOnAllPosts]
public virtual ActionResult CSRFProtectedMethod()
{
  return Json(true, JsonRequestBehavior.DenyGet);
}
17
Will D

私は現在のプロジェクトでこの実際の問題を実装していました。認証されたユーザーを必要とするすべてのajax-POSTに対してそれを行いました。

最初に、jquery ajax呼び出しをフックすることにしました。このJavaScriptスニペットにより、すべてのajax(post)呼び出しがリクエスト検証トークンをリクエストに追加します。注:名前__RequestVerificationTokenは.Netフレームワークで使用されるため、以下に示すように標準のCSRF防止機能を利用できます。

$(document).ready(function () {
    var securityToken = $('[name=__RequestVerificationToken]').val();
    $('body').bind('ajaxSend', function (Elm, xhr, s) {
        if (s.type == 'POST' && typeof securityToken != 'undefined') {
            if (s.data.length > 0) {
                s.data += "&__RequestVerificationToken=" + encodeURIComponent(securityToken);
            }
            else {
                s.data = "__RequestVerificationToken=" + encodeURIComponent(securityToken);
            }
        }
    });
});

上記のJavaScriptでトークンを使用する必要があるビューでは、一般的なHTMLヘルパーを使用します。基本的に、このコードはどこにでも追加できます。 if(Request.IsAuthenticated)ステートメント内に配置しました。

@Html.AntiForgeryToken() // you can provide a string as salt when needed which needs to match the one on the controller

コントローラーでは、標準のASP.Net MVC Anti-CSRFメカニズムを使用するだけです。私はこのようにしました(実際に塩を使用しました)。

[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public JsonResult SomeMethod(string param)
{
    // do something
    return Json(true);
}

Firebugまたは同様のツールを使用すると、POSTリクエストに__RequestVerificationTokenパラメーターがどのように追加されたかを簡単に確認できます。

16
360Airwalk

あなたがしなければならないのは、「__ RequestVerificationToken」入力がPOSTリクエストに含まれていることを確認することだけだと思います。情報の残りの半分(つまり、ユーザーのCookie内のトークン)は、AJAX POSTリクエストで既に自動的に送信されます。

例えば。、

$("a.markAsDone").click(function (event) {
    event.preventDefault();
    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: { 
            "__RequestVerificationToken":
            $("input[name=__RequestVerificationToken]").val() 
        },
        success: function (response) {
            // ....
        }
    });
});
11
jball

これも行うことができます:

$("a.markAsDone").click(function (event) {
    event.preventDefault();

    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: $('<form>@Html.AntiForgeryToken()</form>').serialize(),
        success: function (response) {
        // ....
        }
    });
});

これはRazorを使用していますが、WebForms構文を使用している場合は、<%= %>タグも使用できます

@JBallの答えに対する私のコメントに加えて、これは私を助けてくれました。これが私にとって有効な最終的な答えです。 MVCとRazorを使用しており、jQuery AJAXを使用してフォームを送信しているため、一部のビューを新しい結果で更新でき、完全なポストバック(およびページのちらつき)はしたくありませんでした。

通常どおり、フォーム内に@Html.AntiForgeryToken()を追加します。

私のAJAX送信ボタンコード(つまり、onclickイベント)は次のとおりです。

//User clicks the SUBMIT button
$("#btnSubmit").click(function (event) {

//prevent this button submitting the form as we will do that via AJAX
event.preventDefault();

//Validate the form first
if (!$('#searchForm').validate().form()) {
    alert("Please correct the errors");
    return false;
}

//Get the entire form's data - including the antiforgerytoken
var allFormData = $("#searchForm").serialize();

// The actual POST can now take place with a validated form
$.ajax({
    type: "POST",
    async: false,
    url: "/Home/SearchAjax",
    data: allFormData,
    dataType: "html",
    success: function (data) {
        $('#gridView').html(data);
        $('#TestGrid').jqGrid('setGridParam', { url: '@Url.Action("GetDetails", "Home", Model)', datatype: "json", page: 1 }).trigger('reloadGrid');
    }
});

MvcJqGridを含む部分ビューがどのように更新され、どのように更新されるかを示す「成功」アクションを残しました(非常に強力なjqGridグリッドであり、これはすばらしいMVCラッパーです)。

私のコントローラーメソッドは次のようになります。

    //Ajax SUBMIT method
    [ValidateAntiForgeryToken]
    public ActionResult SearchAjax(EstateOutlet_D model) 
    {
        return View("_Grid", model);
    }

フォーム全体のデータをモデルとしてPOSTすることを好まないことは認めざるを得ませんが、必要な場合はこれが有効な方法の1つです。 MVCはデータバインディングを単純化しすぎているため、16個の個別の値(または弱い型指定のFormCollection)をサブミットするのではなく、これで問題ありません。堅牢なMVC C#コードを生成したいので、よく知っている場合はお知らせください。

4
Ralph Bacon

https://Gist.github.com/scottrippey/3428114 からこの非常に巧妙なアイデアを見つけました。$。ajaxの呼び出しごとに、リクエストを変更し、トークンを追加します。

// Setup CSRF safety for AJAX:
$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
    if (options.type.toUpperCase() === "POST") {
        // We need to add the verificationToken to all POSTs
        var token = $("input[name^=__RequestVerificationToken]").first();
        if (!token.length) return;

        var tokenName = token.attr("name");

        // If the data is JSON, then we need to put the token in the QueryString:
        if (options.contentType.indexOf('application/json') === 0) {
            // Add the token to the URL, because we can't add it to the JSON data:
            options.url += ((options.url.indexOf("?") === -1) ? "?" : "&") + token.serialize();
        } else if (typeof options.data === 'string' && options.data.indexOf(tokenName) === -1) {
            // Append to the data string:
            options.data += (options.data ? "&" : "") + token.serialize();
        }
    }
});
4
masterlopau

1。サーバーからトークンを取得する関数を定義

@function
{

        public string TokenHeaderValue()
        {
            string cookieToken, formToken;
            AntiForgery.GetTokens(null, out cookieToken, out formToken);
            return cookieToken + ":" + formToken;                
        }
}

2。トークンを取得し、サーバーに送信する前にヘッダーを設定します

var token = '@TokenHeaderValue()';    

       $http({
           method: "POST",
           url: './MainBackend/MessageDelete',
           data: dataSend,
           headers: {
               'RequestVerificationToken': token
           }
       }).success(function (data) {
           alert(data)
       });

。Post/getを処理するメソッドのHttpRequestBaseでのサーバー検証

        string cookieToken = "";
        string formToken = "";
        string[] tokens = Request.Headers["RequestVerificationToken"].Split(':');
            if (tokens.Length == 2)
            {
                cookieToken = tokens[0].Trim();
                formToken = tokens[1].Trim();
            }
        AntiForgery.Validate(cookieToken, formToken);

これが私が見た最も簡単な方法です。注:ビューに「@ Html.AntiForgeryToken()」があることを確認してください

  $("a.markAsDone").click(function (event) {
        event.preventDefault();
        var sToken = document.getElementsByName("__RequestVerificationToken")[0].value;
        $.ajax({
            url: $(this).attr("rel"),
            type: "POST",
            contentType: "application/x-www-form-urlencoded",
            data: { '__RequestVerificationToken': sToken, 'id': parseInt($(this).attr("title")) }
        })
        .done(function (data) {
            //Process MVC Data here
        })
        .fail(function (jqXHR, textStatus, errorThrown) {
            //Process Failure here
        });
    });
1
Dominic Sputo

この質問が投稿されてからしばらく経ちましたが、AntiForgeryTokenの使用方法について説明し、使用するのが面倒にならない、本当に役立つリソースを見つけました。また、AJAX呼び出しに偽造防止トークンを簡単に含めるためのjqueryプラグインも提供します。

ASP.NET MVCおよびAJAXの偽造防止リクエストレシピ

私はあまり貢献していませんが、誰かが役に立つかもしれません。

1
slawek

私はajax投稿を使用して削除メソッドを実行しています(visjsタイムラインからのものである可能性がありますが、それは無関係です)。これが私がしていることです:

これは私のIndex.cshtmlです

@Scripts.Render("~/bundles/schedule")
@Styles.Render("~/bundles/visjs")
@Html.AntiForgeryToken()

<!-- div to attach schedule to -->
<div id='schedule'></div>

<!-- div to attach popups to -->
<div id='dialog-popup'></div>

ここに追加したのは、トークンをページに表示するための@Html.AntiForgeryToken()だけです

次に、私のajaxの投稿で私が使用しました:

$.ajax(
    {
        type: 'POST',
        url: '/ScheduleWorks/Delete/' + item.id,
        data: {
            '__RequestVerificationToken': 
            $("input[name='__RequestVerificationToken']").val()
              }
     }
);

投稿されたフィールドにページから削り取られたトークン値を追加します

この前に、ヘッダーに値を入力しようとしましたが、同じエラーが発生しました

改善点を投稿してください。これは確かに私が理解できるシンプルなアプローチのようです

0
Nick.McDermaid

360Airwalkソリューションのわずかな改善。これにより、javascript関数内にAnti Forgery Tokenが埋め込まれるため、すべてのビューに@ Html.AntiForgeryToken()を含める必要がなくなりました。

$(document).ready(function () {
    var securityToken = $('@Html.AntiForgeryToken()').attr('value');
    $('body').bind('ajaxSend', function (Elm, xhr, s) {
        if (s.type == 'POST' && typeof securityToken != 'undefined') {
            if (s.data.length > 0) {
                s.data += "&__RequestVerificationToken=" + encodeURIComponent(securityToken);
            }
            else {
                s.data = "__RequestVerificationToken=" + encodeURIComponent(securityToken);
            }
        }
    });
});
0
Barry MSIH

最初にHTMLで@ Html.AntiForgeryToken()を使用します

 $.ajax({
        url: "@Url.Action("SomeMethod", "SomeController")",
        type: 'POST',
        data: JSON.stringify(jsonObject),
        contentType: 'application/json; charset=utf-8',
        dataType: 'json',
        async: false,
        beforeSend: function (request) {
            request.setRequestHeader("RequestVerificationToken", $("[name='__RequestVerificationToken']").val());
        },
        success: function (msg) {
            alert(msg);
        }
0
Amir Reza
function DeletePersonel(id) {

    var data = new FormData();
    data.append("__RequestVerificationToken", "@HtmlHelper.GetAntiForgeryToken()");

    $.ajax({
        type: 'POST',
        url: '/Personel/Delete/' + id,
        data: data,
        cache: false,
        processData: false,
        contentType: false,
        success: function (result) {
        }
    });
}

public static class HtmlHelper {
    public static string GetAntiForgeryToken() {
        System.Text.RegularExpressions.Match value = 
                System.Text.RegularExpressions.Regex.Match(System.Web.Helpers.AntiForgery.GetHtml().ToString(), 
                        "(?:value=\")(.*)(?:\")");
        if (value.Success) {
            return value.Groups[1].Value;
        }
        return "";
    }
}
0
ismail eski

ここにはたくさんの投稿がありますが、どれも私を助けてくれませんでした、グーグルの日々、そしてそれ以上アプリ全体をゼロから書くという点に到達することはできませんでしたし、Web.confgのこの小さなナゲットに気付きました

 <httpCookies requireSSL="false" domain="*.localLookup.net"/>

今、私はそれを追加した理由がわかりませんが、その後気づきましたが、本番モードではなくデバッグモードで無視されます(IEはIISにインストールされています)

私にとって解決策は2つのオプションのうちの1つでした。なぜ追加したのか覚えていないので、他のものがそれに依存していないことを確信できません.2番目はドメイン名がすべて小文字であり、TLDがivedのようではないこと* .localLookup.net

たぶんそれは助けないかもしれません。私はそれが誰かを助けることを願っています

0
Agitated