原文地址: PREVENTING INSECURE OBJECT REFERENCES IN ASP.NET CORE 2.0

作者: Tahir Naushad

背景介绍

在 OWASP(开放式 Web 应用程序安全项目) 2013 年发布的报告中,将不安全的直接对象引用(Insecure Direct Object Reference)标记为 十大 Web 应用程序风险之一, 其表现形式是对象的引用(例如数据库主键)被各种恶意攻击利用, 所以对于Api返回的各种主键外键ID, 我们需要进行加密。

.NET Core 的数据保护组件

.NET Core 中内置了一个IDataProtectionProvider接口和一个IDataProtector接口。其中IDataProtectionProvider是创建保护组件的接口,IDataProtector是数据保护的接口。开发人员可以实现这 2 个接口,创建数据保护组件。

内置的数据保护组件

.NET Core 中默认提供了一个数据保护组件, 下面我们来尝试使用这个默认组件来保护我们的数据。

例: 当前我们有一个Movie类,代码如下, 我们期望当获取Movie对象的时候,Id字段是加密的。

    public class Movie
{
public Movie(int id, string title)
{
Id = id;
Title = title;
} public int Id { get; set; }
public string Title { get; set; }
}

首先我们需要在Startup.csConfigureService方法中配置使用默认的数据保护组件。

    public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDataProtection();
}

这段代码会启用.NET Core默认的数据保护器。

然后我们创建一个MoviesController, 并在构造函数中注入IDataProtectionProvider对象, 然后使用这个Provider对象创建一个实现IDataProtector接口的数据保护器对象

    [Route("movies")]
public class MoviesController : Controller
{
private readonly IDataProtector protector; public MoviesController(IDataProtectionProvider provider)
{
this.protector = provider.CreateProtector("protect_my_query_string");
}
}

TIPS: 使用Provider创建Protector的时候,我们传入了一个参数"protect_my_query_string", 这个参数标明了这个保护器的用途,你也可以把它就当成这个保护器的名字。

注意: 不同用途的保护器不能解密对方的加密字符串。, 如果使用了保护器A去解密保护器B生成的字符串,会产生以下异常CryptographicException: The payload was invalid.

然后我们在MovieController中添加2个Api, 一个是获取所有Movies对象的,一个是获取指定Movie对象的

   [HttpGet]
public IActionResult Get()
{
var model = GetMovies(); var outputModel = model.Select(item => new
{
Id = this.protector.Protect(item.Id.ToString()),
item.Title,
item.ReleaseYear,
item.Summary
}); return Ok(outputModel);
} [HttpGet("{id}")]
public IActionResult Get(string id)
{
var orignalId = int.Parse(this.protector.Unprotect(id)); var model = GetMovies(); var outputModel = model.Where(item => item.Id == orignalId); return Ok(outputModel);
}

代码解释

  • 在获取Movie列表的api中,我们使用了IDataProtector接口的Protect方法对Id字段进行了加密
  • 相应的在获取单个Movie对象的api中, 我们需要使用IDataProtector接口的Unprotect方法对Id字段进行解密。

最终效果

首先我们调用/api/movies, 返回结果如下, id字段已经被正确加密了

[{
"id": "CfDJ8D9KlbQBeipPoQwll5uLR6ygyO6avkgI2teCQGZQShNwsxC9ApDdsnyYd1K5IyNHjhZcRoGd6W31se3W6TWM8H9UdLEPn4fJpS5uKkqUa0PMV6a0ZZHBQSnlGoisSnj29g",
"title": "泰坦尼克号"
}, {
"id": "CfDJ8D9KlbQBeipPoQwll5uLR6wkMUYyzflIzy3CwoMhcaO-np2WOy4czIL3WZd2FWi7Tsy119tDeFq7yAeye4o2W-KmbffpGXnTDZzNv2QbCrAm7-AyEN35g3pkfAYHa3X7aQ",
"title": "我是谁"
}, {
"id": "CfDJ8D9KlbQBeipPoQwll5uLR6x2AXM6ulCwts2-uQSfzIU8UquTz-OAZIl-49D5-CYYl5H4mfZH8VihhCBJ60MMrZOlZla9qvb8EIP6GYRkEap4nhktbzGxW0Qu5r3edm6_Kg",
"title": "蜘蛛侠"
}, {
"id": "CfDJ8D9KlbQBeipPoQwll5uLR6zDZeLtPIVlkRLCd_V6Mr2kTzWsCkfYgmS0-cqhFAOu4dUWGtx6d402_eKnObAOFUClEDdF4mrUeDQawE71DDa805umhbAvX2712i7UgYO5MA",
"title": "钢铁侠"
}]

然后我们继续调用api, 查询钢铁侠的电影信息

/api/movies/CfDJ8D9KlbQBeipPoQwll5uLR6zDZeLtPIVlkRLCd_V6Mr2kTzWsCkfYgmS0-cqhFAOu4dUWGtx6d402_eKnObAOFUClEDdF4mrUeDQawE71DDa805umhbAvX2712i7UgYO5MA

结果也正确的返回了。

[{"id":4,"title":"钢铁侠"}]

带过期时间的数据保护器(Limited Lifetime)

.NET Core默认还提供了一种带过期时间的数据保护器, 这种数据保护器许多使用场景,最常用的场景就是当为一个重置密码操作的Token设置失效时间, 这样一旦超时的, Token就不能解密成功, 从而我们就可以认定重置密码操作超时了。

.NET Core中, 我们可以使用IDataProtector接口的ToTimeLimitedDataProtector方法创建一个带过期时间的数据保护器。

这里我们还是使用默认还是继续以上面的例子为例, 代码修改如下

    private readonly ITimeLimitedDataProtector protector;

    public MoviesController(IDataProtectionProvider provider)
{
this.protector = provider.CreateProtector("protect_my_query_string")
.ToTimeLimitedDataProtector();
} [HttpGet]
public IActionResult Get()
{
var model = GetMovies(); // simulate call to repository var outputModel = model.Select(item => new
{
Id = this.protector.Protect(item.Id.ToString(),
TimeSpan.FromSeconds(10)),
item.Title,
item.ReleaseYear,
item.Summary
}); return Ok(outputModel);
}

代码解释

  • 这里我们定义了一个ITimeLimitedDataProtector接口对象protector, 并在构造函数中使用ToTimeLimitedDataProtector方法,将一个普通的数据保护器转换成了一个带过期时间的数据保护器
  • 在获取Movie列表的api中, 我们依然使用Protect方法来加密Id字段, 与之前不同的是,这里我们加入了第二个TimeSpan参数,这个参数表示了当前加密的有效时间只有10秒。

最终效果

现在我们重新运行项目,还是和之前一样先调用/api/movies方法来获取Movies列表, 结果如下

[{
"id": "CfDJ8D9KlbQBeipPoQwll5uLR6yzbDbZ931toH32VC6Jqg8DWsrmiLrOxOFFViH4QWZne43jwSVzBjzJIfctYKZniZKNVbr50RRIZpW2fe9UtPajEzBhI-H32Effm-F0ColUaA",
"title": "泰坦尼克号"
}, {
"id": "CfDJ8D9KlbQBeipPoQwll5uLR6zDDVymvftZK9lKBIjEyuoNTzOEu0SC2-qfTy6quXir2S8f3A1r44f9Yz3Sd_cyLZUp-_4gfJAasMfE8_ngYLrJmdsjN9LZ0g4vox0WJLjiGA",
"title": "我是谁"
}, {
"id": "CfDJ8D9KlbQBeipPoQwll5uLR6zL-M2jzv2HCeTiHjevkXvI2216NERplp43TOjCXtj4S52ll68sLyQNtG2FhhWlsOmFGvYY5G4gm5SKfASMMgE1jBr20xc2b_djWdLhWLIxnA",
"title": "蜘蛛侠"
}, {
"id": "CfDJ8D9KlbQBeipPoQwll5uLR6wAoZKCHTG0lvgYS3If_0_eAD30a2YV8RjNagwLXUdCSKsO3kyS58hqDqAPHw_KHwNpd-hjDFl3hFPa8LOWHyk901oc6ZuSxwzxFlljaVreFA",
"title": "钢铁侠"
}]

等待10秒钟后,我们继续调用api, 查询钢铁侠的电影信息

/api/movies/CfDJ8D9KlbQBeipPoQwll5uLR6wAoZKCHTG0lvgYS3If_0_eAD30a2YV8RjNagwLXUdCSKsO3kyS58hqDqAPHw_KHwNpd-hjDFl3hFPa8LOWHyk901oc6ZuSxwzxFlljaVreFA

返回了错误信息CryptographicException: The payload expired at 9/29/2018 11:25:05 AM +00:00. 这说明当前加密的有效期已过, 不能正确解密了。

Tips: 使用Action Filter解密参数

在之前的代码中,我们在获取单个Movie的方法中,我们手动调用了Unprotected方法来解密id属性。

    [HttpGet("{id}")]
public IActionResult Get(string id)
{
var orignalId = int.Parse(this.protector.Unprotect(id)); var model = GetMovies(); // simulate call to repository var outputModel = model.Where(item => item.Id == orignalId); return Ok(outputModel);
}

下面我们改用Action Filter来改进这部分代码。

首先我们创建一个DecryptReferenceFilter, 代码如下:

    public class DecryptReferenceFilter : IActionFilter
{
private readonly IDataProtector protector; public DecryptReferenceFilter(IDataProtectionProvider provider)
{
this.protector = provider.CreateProtector("protect_my_query_string");
} public void OnActionExecuting(ActionExecutingContext context)
{
object param = context.RouteData.Values["id"].ToString();
var id = int.Parse(this.protector.Unprotect(param.ToString()));
context.ActionArguments["id"] = id;
} public void OnActionExecuted(ActionExecutedContext context)
{ }
} public class DecryptReferenceAttribute : TypeFilterAttribute
{
public DecryptReferenceAttribute() :
base(typeof(DecryptReferenceFilter))
{ }
}

代码解释

  • 这里DecryptReferenceFilter实现了IActionFilter接口, 并实现了OnActionExecutingOnActionExecuted方法
  • DecryptReferenceFilter类中,我们注入了默认的数据保护器提供器,并在构造函数中初始化了一个数据保护器
  • OnActionExecuting中我们从RouteData中获取到未解密的id字段, 然后将其解密之后,替换了之前未解密的id字段,这样ModelBinder就会使用解密后的字符串来绑定模型。

最终修改

最后我们修改一下获取单个Movie的api, 代码如下:

    [HttpGet("{id}")]
[DecryptReference]
public IActionResult Get(int id)
{
var model = GetMovies(); var outputModel = model.Where(item => item.Id == id); return Ok(outputModel);
}

我们在获取单个Movie的方法上添加了DecryptReference特性。

运行代码之后,代码和之前的效果一样。

本篇源代码

.NET Core中的数据保护组件的更多相关文章

  1. .NET Core中的验证组件FluentValidation的实战分享

    今天有人问我能不能出一篇FluentValidation的教程,刚好今天在实现我们的.NET Core实战项目之CMS的修改密码部分的功能中有用到FluentValidation,所以就以修改用户密码 ...

  2. ASP.NET Core中的数据保护

    在这篇文章中,我将介绍ASP.NET Core 数据保护系统:它是什么,为什么我们需要它,以及它如何工作. 为什么我们需要数据保护系统? 数据保护系统是ASP.NET Core使用的一组加密api.加 ...

  3. 玩转ASP.NET Core中的日志组件

    简介 日志组件,作为程序员使用频率最高的组件,给程序员开发调试程序提供了必要的信息.ASP.NET Core中内置了一个通用日志接口ILogger,并实现了多种内置的日志提供器,例如 Console ...

  4. Asp.Net Core中利用Seq组件展示结构化日志功能

    在一次.Net Core小项目的开发中,掌握的不够深入,对日志记录并没有好好利用,以至于一出现异常问题,都得跑动服务器上查看,那时一度怀疑自己肯定没学好,不然这一块日志不可能需要自己扒服务器日志来查看 ...

  5. 【Blazor】在ASP.NET Core中使用Blazor组件 - 创建一个音乐播放器

    前言 Blazor正式版的发布已经有一段时间了,.NET社区的各路高手也创建了一个又一个的Blazor组件库,其中就包括了我和其他小伙伴一起参与的AntDesign组件库,于上周终于发布了第一个版本0 ...

  6. .Net Core中的日志组件(Logging)

    1.介绍 Logging组件是微软实现的日志记录组件包括控制台(Console).调试(Debug).事件日志(EventLog)和TraceSource,但是没有实现最常用用的文件记录日志功能(可以 ...

  7. 关于.net core 中的signalR组件的使用

    SignalR是为了提供更方便的web交互响应式到推送式的解决方案.有了它之后可以实现客户端直接调用服务端的方法并且获得返回值 (客户端可以是各种平台,目前SignalR支持的语言版本有C#.java ...

  8. ASP.NET Core中的ActionFilter与DI

    一.简介 前几篇文章都是讲ASP.NET Core MVC中的依赖注入(DI)与扩展点的,也许大家都发现在ASP.NET CORE中所有的组件都是通过依赖注入来扩展的,而且面向一组功能就会有一组接口或 ...

  9. ASP.NET Core中使用Razor视图引擎渲染视图为字符串

    一.前言 在有些项目需求上或许需要根据模板生产静态页面,那么你一样可以用Razor语法去直接解析你的页面从而把解析的页面生成静态页,这样的使用场景很多,不限于生成静态页面,视图引擎为我们提供了模型到视 ...

随机推荐

  1. MongoDB与Spring整合(支持事务)——SpringDataMongoDB

    1.将MongoDB设置为复制集模式 a.修改 mongod.cfg 文件,添加replSetName复制集名称 #replication: replication: replSetName: &qu ...

  2. 练习2-1 Programming in C is fun!

    练习2-1 Programming in C is fun! 一 问题描述 本题要求编写程序,输出一个短句“Programming in C is fun!”. 输入格式: 本题目没有输入. 输出格式 ...

  3. [CF893F] Subtree Minimum Query

    Description: 给定一棵树,每次询问某点子树中到其不超过k的所有点的最小点权 强制在线 Hint: \(n,m\le 10^5\) Solution: 看到题目第一反应是以深度为下标,dfs ...

  4. MYSQL必知必会学习笔记

    8.1.1 百分号( %)通配符最常使用的通配符是百分号( %).在搜索串中, %表示任何字符出现任意次数.例如,为了找出所有以词jet起头的产品,可使用以下SELECT语句:SELECT prod_ ...

  5. C\C++ 内存对齐现象

    前几天一个在自学C语言的小伙伴问了我个问题,C语言结构体储存所占空间为啥和自己预测的不一样.看一下下面这一段代码: struct node{ int num; char ch; }a; printf( ...

  6. Java 什么是线程安全

    当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要额外的同步或协同,这个类都能表现出正确的行为,那么这个类就是线程安全的.其中,正确性指某个类的行 ...

  7. 《SpringMVC从入门到放肆》十三、SpringMVC数据校验

    上一章,我们学习了SpringMVC的自定义类型转换器,但是如果转换后的数据传递到Controller的方法中,忽然发现有某些属性为Null了,这怎么办?我们需要一种有效的数据校验机制,来对数据进行有 ...

  8. 整理SpringMVC

    Spring Web MVC核心架构图: 核心架构图流程如下: 1.首先用户发送请求------->DispatcherServlet(前端控制器),前端控制器收到请求后自己不进行处理,而是委托 ...

  9. cadence布线完成后的补充操作

    完成布线之后,需要生成光绘文件和钻孔文件,在生成钻孔文件之前,还有几点补充!

  10. Reactjs组件中的方法为什么绑定this?

    前言:Reactjs中的定义的方法其实是基于实例的原型方法:es6默认严格模式   问题:在Reactjs组件中写入以下代码,那么执行结果会是什么呢? ··· 结果是这样的: 对的,你没看错,不是do ...