欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

CSRF在ASP.NET Core中的处理方法详解

程序员文章站 2022-11-22 12:55:31
前言 前几天,有个朋友问我关于antiforgerytoken问题,由于对这一块的理解也并不深入,所以就去研究了一番,梳理了一下。 在梳理之前,还需要简单了解一下背景知...

前言

前几天,有个朋友问我关于antiforgerytoken问题,由于对这一块的理解也并不深入,所以就去研究了一番,梳理了一下。

在梳理之前,还需要简单了解一下背景知识。

antiforgerytoken 可以说是处理/预防csrf的一种处理方案。

那么什么是csrf呢?

csrf(cross-site request forgery)是跨站请求伪造,也被称为one click attack或者session riding,通常缩写为csrf或者xsrf,是一种对网站的恶意利用。

简单理解的话就是:有人盗用了你的身份,并且用你的名义发送恶意请求。

最近几年,csrf处于不温不火的地位,但是还是要对这个小心防范!

更加详细的内容可以参考*:cross-site request forgery

下面从使用的角度来分析一下csrf在 asp.net core中的处理,个人认为主要有下面两大块

  • 视图层面
  • 控制器

层面视图层面

用法

@html.antiforgerytoken()

在视图层面的用法相对比较简单,用的还是htmlhelper的那一套东西。在form表单中加上这一句就可以了。

原理浅析

当在表单中添加了上面的代码后,页面会生成一个隐藏域,隐藏域的值是一个生成的token(防伪标识),类似下面的例子

<input name="__requestverificationtoken" type="hidden" value="cfdj8fbn4lzsygljpe6q0fwvz8wdmtgwk49ldu1xgup5-5j4jlscml_idoo3xdl5eoyi_ms2ux7llsfi7asqniixo2scejvnabf9v51tuzl_im2s63zuipk4lcxrpa_kuudbk-ls4hd16pjusfrppj-degc" />

其中的name="__requestverificationtoken"是定义的一个const变量,value=xxxxx是根据一堆东西进行base64编码,并对base64编码后的内容进行简单处理的结果,具体的实现可以参见base64urltextencoder.cs

生成上面隐藏域的代码在antiforgeryextensions这个文件里面,github上的源码文件:antiforgeryextensions.cs

其中重点的方法如下:

public void writeto(textwriter writer, htmlencoder encoder)
{
 writer.write("<input name=\"");
 encoder.encode(writer, _fieldname);
 writer.write("\" type=\"hidden\" value=\"");
 encoder.encode(writer, _requesttoken);
 writer.write("\" />");
}

相当的清晰明了!

控制器层面

用法

[validateantiforgerytoken]
[autovalidateantiforgerytoken]
[ignoreantiforgerytoken]

这三个都是可以基于类或方法的,所以我们只要在某个控制器或者是在某个action上面加上这些attribute就可以了。

[attributeusage(attributetargets.class | attributetargets.method, allowmultiple = false, inherited = true)]

原理浅析

本质是filter(过滤器),验证上面隐藏域的value

过滤器实现:validateantiforgerytokenauthorizationfilter和autovalidateantiforgerytokenauthorizationfilter

其中 autovalidateantiforgerytokenauthorizationfilter是继承了validateantiforgerytokenauthorizationfilter,只重写了其中的shouldvalidate方法。

下面贴出validateantiforgerytokenauthorizationfilter的核心方法:

public class validateantiforgerytokenauthorizationfilter : iasyncauthorizationfilter, iantiforgerypolicy
{
 public async task onauthorizationasync(authorizationfiltercontext context)
 {
 if (context == null)
 {
 throw new argumentnullexception(nameof(context));
 }

 if (isclosestantiforgerypolicy(context.filters) && shouldvalidate(context))
 {
 try
 {
 await _antiforgery.validaterequestasync(context.httpcontext);
 }
 catch (antiforgeryvalidationexception exception)
 {
 _logger.antiforgerytokeninvalid(exception.message, exception);
 context.result = new badrequestresult();
 }
 }
 }
}

完整实现可参见github源码:validateantiforgerytokenauthorizationfilter.cs

当然这里的过滤器只是一个入口,相关的验证并不是在这里实现的。而是在antiforgery这个项目上,其实说这个模块可能会更贴切一些。

由于是面向接口的编程,所以要知道具体的实现,就要找到对应的实现类才可以。

在antiforgery这个项目中,有这样一个扩展方法antiforgeryservicecollectionextensions,里面告诉了我们相对应的实现是defaultantiforgery这个类。其实nancy的源码看多了,看一下类的命名就应该能知道个八九不离十。

 services.tryaddsingleton<iantiforgery, defaultantiforgery>();

其中还涉及到了iservicecollection,但这不是本文的重点,所以不会展开讲这个,只是提出它在 .net core中是一个重要的点。

好了,回归正题!要验证是否是合法的请求,自然要先拿到要验证的内容。

 var tokens = await _tokenstore.getrequesttokensasync(httpcontext);

它是从cookie中拿到一个指定的前缀为.aspnetcore.antiforgery.的cookie,并根据这个cookie进行后面相应的判断。下面是验证的具体实现:

public bool tryvalidatetokenset(
 httpcontext httpcontext,
 antiforgerytoken cookietoken,
 antiforgerytoken requesttoken,
 out string message)
{
 //去掉了部分非空的判断

 // do the tokens have the correct format?
 if (!cookietoken.iscookietoken || requesttoken.iscookietoken)
 {
 message = resources.antiforgerytoken_tokensswapped;
 return false;
 }

 // are the security tokens embedded in each incoming token identical?
 if (!object.equals(cookietoken.securitytoken, requesttoken.securitytoken))
 {
 message = resources.antiforgerytoken_securitytokenmismatch;
 return false;
 }

 // is the incoming token meant for the current user?
 var currentusername = string.empty;
 binaryblob currentclaimuid = null;

 var authenticatedidentity = getauthenticatedidentity(httpcontext.user);
 if (authenticatedidentity != null)
 {
 currentclaimuid = getclaimuidblob(_claimuidextractor.extractclaimuid(httpcontext.user));
 if (currentclaimuid == null)
 {
 currentusername = authenticatedidentity.name ?? string.empty;
 }
 }

 // openid and other similar authentication schemes use uris for the username.
 // these should be treated as case-sensitive.
 var comparer = stringcomparer.ordinalignorecase;
 if (currentusername.startswith("http://", stringcomparison.ordinalignorecase) ||
 currentusername.startswith("https://", stringcomparison.ordinalignorecase))
 {
 comparer = stringcomparer.ordinal;
 }

 if (!comparer.equals(requesttoken.username, currentusername))
 {
 message = resources.formatantiforgerytoken_usernamemismatch(requesttoken.username, currentusername);
 return false;
 }

 if (!object.equals(requesttoken.claimuid, currentclaimuid))
 {
 message = resources.antiforgerytoken_claimuidmismatch;
 return false;
 }

 // is the additionaldata valid?
 if (_additionaldataprovider != null &&
 !_additionaldataprovider.validateadditionaldata(httpcontext, requesttoken.additionaldata))
 {
 message = resources.antiforgerytoken_additionaldatacheckfailed;
 return false;
 }

 message = null;
 return true;
}

注:验证前还有一个反序列化的过程,这个反序列化就是从cookie中拿到要判断的cookietoken和requesttoken

如何使用

前面粗略介绍了一下其内部的实现,下面再用个简单的例子来看看具体的使用情况:

使用一:常规的form表单

先在视图添加一个form表单

<form id="form1" action="/home/antiform" method="post">
 @html.antiforgerytoken()
 <p><input type="text" name="message" /></p>
 <p><input type="submit" value="send by form" /></p>
</form>

在控制器添加一个action

[validateantiforgerytoken]
[httppost]
public iactionresult antiform(string message)
{
 return content(message);
}

来看看生成的html是不是如我们前面所说,将@html.antiforgerytoken()输出为一个name为__requestverificationtoken的隐藏域:

CSRF在ASP.NET Core中的处理方法详解

再来看看cookie的相关信息:

CSRF在ASP.NET Core中的处理方法详解

可以看到,一切都还是按照前面所说的执行。在输入框输入信息并点击按钮也能正常显示我们输入的文字。

CSRF在ASP.NET Core中的处理方法详解

使用二:ajax提交

表单:

<form id="form2" action="/home/antiajax" method="post">
 @html.antiforgerytoken()
 <p><input type="text" name="message" id="ajaxmsg" /></p>
 <p><input type="button" id="btnajax" value="send by ajax" /></p>
</form>

js:

$(function () {
 $("#btnajax").on("click", function () {
 $("#form2").submit(); 
 });
})

这样子的写法也是和上面的结果是一样的!

怕的是出现下面这样的写法:

$.ajax({
 type: "post",
 datatype: "html",
 url: '@url.action("antiajax", "home")',
 data: { message: $('#ajaxmsg').val() },
 success: function (result) {
 alert(result);
 },
 error: function (err, scnd) {
 alert(err.statustext);
 }
});

这样,正常情况下确实是看不出任何毛病,但是实际确是下面的结果(400错误):

CSRF在ASP.NET Core中的处理方法详解

相信大家也都发现了问题的所在了!!隐藏域的相关内容并没有一起post过去!!

处理方法有两种:

方法一:

在data中加上隐藏域相关的内容,大致如下:

$.ajax({
 // 
 data: { message: $('#ajaxmsg').val(), __requestverificationtoken: $("input[name='__requestverificationtoken']").val()}
});

方法二:

在请求中添加一个header

$("#btnajax").on("click", function () {
 var token = $("input[name='__requestverificationtoken']").val();
 $.ajax({
 type: "post",
 datatype: "html",
 url: '@url.action("antiajax", "home")',
 data: { message: $('#ajaxmsg').val() },
 headers:
 {
  "requestverificationtoken": token
 },
 success: function (result) {
  alert(result);
 },
 error: function (err, scnd) {
  alert(err.statustext);
 }
 });
});

这样就能处理上面出现的问题了!

使用三:自定义相关信息

可能会有不少人觉得,像那个生成的隐藏域那个name能不能换成自己的,那个cookie的名字能不能换成自己的〜〜

答案是肯定可以的,下面简单示范一下:

在startup的configureservices方法中,添加下面的内容即可对默认的名称进行相应的修改。

services.addantiforgery(option =>
{
 option.cookiename = "customer-csrf-cookie";
 option.formfieldname = "customerfieldname";
 option.headername = "customer-csrf-header";
});

相应的,ajax请求也要做修改:

var token = $("input[name='customerfieldname']").val();//隐藏域的名称要改
$.ajax({
 type: "post",
 datatype: "html",
 url: '@url.action("antiajax", "home")',
 data: { message: $('#ajaxmsg').val() },
 headers:
 {
 "customer-csrf-header": token //注意header要修改
 },
 success: function (result) {
 alert(result);
 },
 error: function (err, scnd) {
 alert(err.statustext);
 }
});

下面是效果:

form表单:

CSRF在ASP.NET Core中的处理方法详解

cookie:

CSRF在ASP.NET Core中的处理方法详解

本文涉及到的相关项目:

关于csrf相关的内容

preventing cross-site request forgery (xsrf/csrf) attacks in asp.net core

浅谈csrf攻击方式

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。