根据我在互联网上的一些博客文章中阅读的信息,我在我的应用程序中实施了对 CSRF attacks 的缓解措施。特别是这些帖子是我实施的驱动力
来自 ASP.NET 和 Web 工具开发人员内容团队的 ASP.NET MVC 最佳实践
Phil Haack 博客的跨站点请求伪造攻击剖析
ASP.NET MVC 框架中的 AntiForgeryToken - David Hayden 博客中的 Html.AntiForgeryToken 和 ValidateAntiForgeryToken 属性
基本上这些文章和建议说,为了防止 CSRF 攻击,任何人都应该实现以下代码:
在每个接受 POST Http 动词 [HttpPost] [ValidateAntiForgeryToken] public ActionResult SomeAction( SomeModel model ) { } 的操作上添加 [ValidateAntiForgeryToken] 在向服务器提交数据的表单中添加 <%= Html.AntiForgeryToken() %> 帮助器
无论如何,在我的应用程序的某些部分,我正在使用 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的答案,我以下列方式使用
$("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) {
// ....
}
});
});
我使用这样的简单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 调用中执行(编辑以匹配您的第二个示例)
$.ajax({
type: "post",
dataType: "html",
url: $(this).attr("rel"),
data: AddAntiForgeryToken({ id: parseInt($(this).attr("title")) }),
success: function (response) {
// ....
}
});
我喜欢360Airwalk提供的解决方案,但可能会有所改进。
第一个问题是,如果您使用空数据创建 $.post()
,jQuery 不会添加 Content-Type
标头,在这种情况下,ASP.NET MVC 无法接收和检查令牌。因此,您必须确保标题始终存在。
另一个改进是支持所有带有内容的 HTTP 动词: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);
}
}
});
});
.ajaxSend()
的 jQuery 文档声明“从 jQuery 1.8 开始,.ajaxSend() 方法只能附加到文档。” api.jquery.com/ajaxsend
options
来自哪里,在最后的 if
语句中列出?谢谢。
我知道还有很多其他的答案,但是这篇文章非常简洁明了,并迫使您检查所有的 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 () {
...
}
});
我在这里感觉自己像个高级死灵法师,但这在 4 年后的 MVC5 中仍然是一个问题。
要正确处理 ajax 请求,需要在 ajax 调用上将防伪令牌传递给服务器。将其集成到您的帖子数据和模型中既麻烦又不必要。将令牌添加为自定义标头是干净且可重用的 - 您可以对其进行配置,这样您就不必每次都记得这样做。
有一个例外 - Unobtrusive ajax 不需要对 ajax 调用进行特殊处理。令牌像往常一样在常规隐藏输入字段中传递。与常规 POST 完全相同。
_Layout.cshtml
在 _layout.cshtml 我有这个 JavaScript 块。它不会将令牌写入 DOM,而是使用 jQuery 从 MVC Helper 生成的隐藏输入文字中提取它。作为标题名称的 Magic 字符串在属性类中定义为常量。
<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);
}
$.ajaxSetup
来定义一个通用的 beforesend
事件处理程序,所以您可能会覆盖它。我找到了 another solution,您可以在其中添加第二个处理程序,该处理程序也将被调用。效果很好,不会破坏您的实施。
不要使用 Html.AntiForgeryToken。而是使用来自 Web API 的 AntiForgery.GetTokens 和 AntiForgery.Validate,如 Preventing Cross-Site Request Forgery (CSRF) Attacks in ASP.NET MVC Application 中所述。
我认为您所要做的就是确保“__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) {
// ....
}
});
});
AntiForgeryToken
,那么无论如何它都没有实际意义。如果确实如此(不确定在这种情况下您是如何获得的,但假设您是),那么上面的方法就可以了。如果您尝试创建一个简单的网页,该网页会将请求发布到期望所述令牌的服务器,并且服务器没有生成所述页面,那么您就不走运了。这基本上就是 AntiForgeryToken 的重点......
我只是在我当前的项目中实现这个实际问题。我为所有需要经过身份验证的用户的 ajax-POST 做了它。
首先,我决定挂钩我的 jquery ajax 调用,这样我就不会经常重复自己。这个 javascript 片段确保所有 ajax(post)调用都会将我的请求验证令牌添加到请求中。注意:.Net 框架使用名称 __RequestVerificationToken,因此我可以使用标准的 Anti-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-Helper。您基本上可以随心所欲地添加此代码。我将它放在 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 机制。我是这样做的(尽管我实际上使用了 Salt)。
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public JsonResult SomeMethod(string param)
{
// do something
return Json(true);
}
使用 Firebug 或类似工具,您可以轻松查看您的 POST 请求现在如何附加 __RequestVerificationToken 参数。
你也可以这样做:
$("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);
}
我不得不承认我不喜欢将整个表单的数据作为模型发布,但如果你需要这样做,那么这是一种可行的方法。 MVC 只是让数据绑定变得太容易了,所以我猜,与其替换 16 个单独的值(或弱类型的 FormCollection),这还可以。如果您知道得更好,请告诉我,因为我想生成健壮的 MVC C# 代码。
从 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();
}
}
});
if (options.contentType != false && options.contentType.indexOf('application/json') === 0) {
来捕获未指定内容类型的 Ajax 调用
1.定义从服务器获取Token的函数
@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)
});
3. 在处理 Post/get 的方法上对 HttpRequestBase 进行 Onserver 验证
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中使用@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);
}
我知道这个问题发布已经有一段时间了,但我发现了非常有用的资源,它讨论了 AntiForgeryToken 的用法并使其使用起来不那么麻烦。它还提供 jquery 插件,以便在 AJAX 调用中轻松包含防伪令牌:
Anti-Forgery Request Recipes For ASP.NET MVC And AJAX
我贡献不多,但也许有人会觉得它有用。
这是我见过的最简单的方法。注意:确保您的视图中有“@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
});
});
对 360Airwalk 解决方案略有改进。这将 Anti Forgery Token 嵌入到 javascript 函数中,因此 @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);
}
}
});
});
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 "";
}
}
我正在使用 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()
}
}
);
它将从页面上刮下的令牌值添加到发布的字段中
在此之前,我尝试将值放在标题中,但我得到了同样的错误
随时发布改进。这当然似乎是一种我能理解的简单方法
好吧,这里有很多帖子,没有一个对我有帮助,谷歌的日子和日子,但我仍然没有进一步从头开始编写整个应用程序,然后我注意到我的 Web.confg 中的这个小块
<httpCookies requireSSL="false" domain="*.localLookup.net"/>
现在我不知道为什么要添加它,但后来我注意到,它在调试模式下被忽略,而不是在生产模式下(IE 安装到 IIS 某处)
对我来说,解决方案是两个选项之一,因为我不记得为什么要添加它,我不能确定其他事情不依赖它,其次,域名必须全部小写,TLD 不像我做的那样在 *.localLookup.net
也许它有帮助也许它没有。我希望它对某人有帮助
我发现的解决方案不是针对 ASPX,而是针对 Razor,但相当可观的问题。
我通过将 AntiForgery 添加到请求中来解决它。 HTML Helper 不会通过调用创建 HTML id
@Html.AntiForgeryToken()
为了将令牌添加到 postrequest,我刚刚使用 jquery 将 AntiForgery id 添加到隐藏字段:
$("input[name*='__RequestVerificationToken']").attr('id', '__AjaxAntiForgeryForm');
这导致控制器接受带有 [ValidateAntiForgeryToken] 属性的请求
AntiforgeryToken 仍然很痛苦,上面的例子对我来说都没有一个字一个字。太多了。所以我把它们都结合起来了。需要一个挂在iirc周围的@Html.AntiforgeryToken
解决如下:
function Forgizzle(eggs) {
eggs.__RequestVerificationToken = $($("input[name=__RequestVerificationToken]")[0]).val();
return eggs;
}
$.ajax({
url: url,
type: 'post',
data: Forgizzle({ id: id, sweets: milkway }),
});
如有疑问,请添加更多 $ 符号
AddAntiForgeryToken
的调用中,如下所示:data: AddAntiForgeryToken({ id: parseInt($(this).attr("title")) }),
ajaxSend
或覆盖ajax
以始终使用防伪令牌增加data
是多么糟糕的想法?也许添加一些检查以确保url
是发往您的服务器的。