初探CSRF在ASP.NET Core中的处理方式

前言

前几天,有个朋友问我关于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

过滤器实现:ValidateAntiforgeryTokenAuthorizationFilterAutoValidateAntiforgeryTokenAuthorizationFilter

其中 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的隐藏域:

再来看看cookie的相关信息:

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

使用二: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错误):

相信大家也都发现了问题的所在了!!隐藏域的相关内容并没有一起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表单:

Cookie:

本文涉及到的相关项目:

  • Mvc

  • Antiforgery

  • HttpAbstractions

关于CSRF相关的内容

Preventing Cross-Site Request Forgery (XSRF/CSRF) Attacks in ASP.NET Core

浅谈CSRF攻击方式

原文地址:http://www.cnblogs.com/catcher1994/p/6720212.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/325946.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HBase体系架构说明

HBase体系架构说明&#xff01; client&#xff1a;两种—-Hbase shell 命令行接口 JavaAPI client访问Region不能直接访问&#xff0c;首先client需要先连接到zookeeper&#xff0c;zookeeper管理HMaster&#xff0c;说明&#xff0c;hdfs集群&#xff0c;在没有zookeepe…

asp.net core 编译mvc,routing,security源代码进行本地调试

因为各种原因&#xff0c;需要查看asp.net core mvc的源代码来理解运行机制等等&#xff0c;虽说源代码查看已经能很好的理解了。但是能够直接调试还是最直观的。所有就有了本次尝试。因调试设置源代码调试太辍笔&#xff0c;所以不用这个方法&#xff0c;转而使用编译源代码的…

并查集判断是否有环存在

题目描述 思路分析 代码实现 package com.atguigu.disjointSet;public class djset {public static int VERTICES6;public static void initialise(int parent[]){int i;for (i 0; i < VERTICES; i) {parent[i]-1;}}public static int find_root(int x,int parent[]){int …

Visual Studio 2017更新,侧重于提高稳定性

Microsoft已对其旗舰开发软件产品Visual Studio 2017&#xff08;VS2017&#xff09;的安装程序和设置引擎做了全新设计&#xff0c;意在能快速地部署更新到VS2017。VS2017的第一个命名版本更新在VS2017首发的一个月后就可用&#xff0c;这充分体现了这一全新设计的优势所在。 …

阅读器关闭时尝试调用Read无效时的解决方法

今天在写asp .netmvc的项目时&#xff0c;发现了个困扰我很久的问题&#xff0c;经过仔细研究终于解决了。 问题如下&#xff1a; 首先来看一下原来有问题的代码&#xff1a; public static SqlDataReader Excutereader(string sql,params SqlParameter [] param) {using (SqlC…

.NET或将引入类型类和扩展

类型类是另外一项正被考虑引入.NET未来版本的特性。在提案“外观和扩展&#xff08;Shapes and Extensions&#xff09;”中&#xff0c;该特性被称为外观&#xff0c;它们将大幅提升.NET泛型的能力。Mads Torgersen这样描述类型类&#xff1a; 接口抽象的是作为类型实例的对象…

成为更优秀的程序员:退后一步看问题

转载自 成为更优秀的程序员&#xff1a;退后一步看问题 一天&#xff0c;在工作中… Bug #3890 来自客户&#xff1a; 有个程序出现了错误&#xff0c;程序提示说“SpeedCalculator::compute()里出现了除零情况”。 请尽快修复&#xff01; 你打开SpeedCalculator.php&#…

深夜福利, 小试linuxkit

前言 今天小编的朋友圈被DockerCon和linuxkit刷屏了&#xff0c;再不出来写点linuxkit的东东恐怕就要跟不上时代了。小编翻阅了N多的有关linuxkit的文章&#xff0c;发现绝大多数都是英文的讲解&#xff0c;小编本着对读者负责的态度决定先将linuxkit的文章翻译成中文&#xff…

.NET Core 2.0及.NET Standard 2.0

.NET Core 2.0的发布时间&#xff0c;.NET Core 2.0预览版及.NET Standard 2.0 Preview大概在5月中旬或下旬发布。 .NET Core 2.0正式版本发布时间大约在Q3 2017发布&#xff0c;具体我估计大概在8月份左右。同时一起发布的也就是.NET Standard 2.0。 MilestoneRelease Date.…

Linux清理磁盘挂载点方法(亲测有效)

问题现象 Linux操作系统云服务器根目录空间占用率过高。例如&#xff0c;以图1为例&#xff0c;根目录空间占用率为96%。 根目录空间占用率过高 查询当前系统存在一个约42G 大小的文件access_log&#xff0c;这个文件应该是apache产生的访问日志文件&#xff0c;从日志大小来…

.NET程序在Linux容器中的演变

本文将首先讨论镜像的构建时间和启动时间&#xff0c;接着会将一个简单的.NET程序运行在基于容器的应用上&#xff0c;然后观察镜像大小的变化&#xff0c;最终缩短镜像的构建和加载时间。此外&#xff0c;代码优化是本文的另一个主题。 现在&#xff0c;.NET开发人员可以无障…

微服务的概念——《微服务设计》读书笔记

《领域驱动设计》&#xff08;Eric Evans&#xff09;&#xff1a;告诉我们用代码呈现真实世界的重要性&#xff0c;并且告诉我们如何更好地建模。 持续交付理论&#xff1a;如何更有效及更高效地发布软件品&#xff0c;并指出保持每次提交均可发布的重要性。 六边形架构理论&a…

想要玩转实现负载均衡,你知道这些吗?

转载自 想要玩转实现负载均衡&#xff0c;你知道这些吗&#xff1f; 一、前言 互联网早期&#xff0c;业务流量比较小并且业务逻辑比较简单&#xff0c;单台服务器便可以满足基本的需求&#xff1b;但随着互联网的发展&#xff0c;业务流量越来越大并且业务逻辑也越来越复杂&…

netcore实践:跨平台动态加载native组件

缘起netcore框架下实现基于zmq的应用。在.net framework时代&#xff0c;我们进行zmq开发由很多的选择&#xff0c;比较常用的有clrzmq4和NetMQ。 其中clrzmq是基于libzmq的Interop包装&#xff0c; NetMQ是100%C#的zmq实现&#xff08;基于AsyncIO组件&#xff09;。以上两种…

如何实现酷狗音乐pc页面点击播放时,打开多个歌曲播放时,始终在一个播放页面,(标签页的通讯)

大致有两种思路&#xff0c; 一种是通过wind.open()方法传第二个参数&#xff0c; A页面&#xff1a; //点击跳转播放页函数function toPlayPage(){window.open(path/xxxx/xxxx?name音乐名,music)//第二个参数写一个定值&#xff0c;代表跳转页面都为music标签页&#xff0…

Redis进阶之内存模型

转载自 Redis进阶之内存模型 前言 Redis是目前最火爆的内存数据库之一&#xff0c;通过在内存中读写数据&#xff0c;大大提高了读写速度&#xff0c;可以说Redis是实现网站高并发不可或缺的一部分。 我们使用Redis时&#xff0c;会接触Redis的5种对象类型&#xff08;字符…

C#将引入可空的引用类型

是的&#xff0c;标题没错。C#其中一份新提案假定&#xff0c;所有的引用类型在默认情况下都是不可空的。在新语法下&#xff0c;你需要显式地标明一个引用变量是可空的&#xff0c;就像对值类型所做的那样。 和值类型一样&#xff0c;T是指不可空类型&#xff0c;而T?是指可…

Android中SlidingDrawer开发报错You need to use a Theme.AppCompat theme (or descendant) with this activity.

Android抽屉开发报错You need to use a Theme.AppCompat theme (or descendant) with this activity. 方法1&#xff1a; 创建的activity时&#xff0c;如果不是那么强烈需要继承自AppCompatActivity&#xff0c;就直接继承Activity。 如将activity继承自AppCompatActivity&…

Redis进阶之持久化

转载自 Redis进阶之持久化 一、Redis高可用概述 在介绍Redis高可用之前&#xff0c;先说明一下在Redis的语境中高可用的含义。 我们知道&#xff0c;在web服务器中&#xff0c;高可用是指服务器可以正常访问的时间&#xff0c;衡量的标准是在多长时间内可以提供正常服务&am…