第二十三节:Asp.Net Core中的几种安全防护
一 . 客户端IP白名单限制
1.通过中间件检测
新建中间件类AdminSafeListMiddleware,获取白名单ip,通过比较byte值来比较访问的ip是否在白名单中,如果不在,则返回401无权限。 然后在ConfigureService中进行全局拦截app.UseMiddleware<AdminSafeListMiddleware>(Configuration["IpSafeList"]);
注:该案例完全可以改造成黑名单拦截。
PS:
A. ::1 代表localhost或者127.0.0.1
B. context.Connection.RemoteIpAddress; HttpContext对象获取远程访问地址,类似的还有RemotePort、LocalIpAddress、LocalPort 。
代码分享:
1 public class AdminSafeListMiddleware 2 { 3 private readonly RequestDelegate _next; 4 private readonly string _adminSafeList; 5 public AdminSafeListMiddleware(RequestDelegate next,string adminSafeList) 6 { 7 _adminSafeList = adminSafeList; 8 _next = next; 9 } 10 public async Task Invoke(HttpContext context) 11 { 12 //if (context.Request.Method != "GET") 13 { 14 var remoteIp = context.Connection.RemoteIpAddress; //获取远程访问IP 15 string[] ip = _adminSafeList.Split(';'); 16 var bytes = remoteIp.GetAddressBytes(); 17 var badIp = true; 18 foreach (var address in ip) 19 { 20 var testIp = IPAddress.Parse(address); 21 if (testIp.GetAddressBytes().SequenceEqual(bytes)) 22 { 23 badIp = false; 24 break; //直接跳出ForEach循环 25 } 26 } 27 if (badIp) 28 { 29 context.Response.StatusCode = 401; 30 return; 31 } 32 } 33 await _next.Invoke(context); 34 } 35 }
2.通过操作过滤器拦截
利用操作过滤器,Override方法OnActionExecuting,在方法执行前进行拦截。
1 /// <summary> 2 /// IP校验过滤器 3 /// </summary> 4 public class ClientIpCheckFilter: ActionFilterAttribute 5 { 6 7 private readonly string _safelist; 8 public ClientIpCheckFilter(IConfiguration configuration) 9 { 10 _safelist = configuration["IpSafeList"]; 11 } 12 /// <summary> 13 /// 方法执行前执行 14 /// </summary> 15 /// <param name="context"></param> 16 public override void OnActionExecuting(ActionExecutingContext context) 17 { 18 var remoteIp = context.HttpContext.Connection.RemoteIpAddress; 19 string[] ip = _safelist.Split(';'); 20 var badIp = true; 21 foreach (var address in ip) 22 { 23 if (remoteIp.IsIPv4MappedToIPv6) 24 { 25 remoteIp = remoteIp.MapToIPv4(); 26 } 27 var testIp = IPAddress.Parse(address); 28 if (testIp.Equals(remoteIp)) 29 { 30 badIp = false; 31 break; 32 } 33 } 34 if (badIp) 35 { 36 context.Result = new StatusCodeResult(401); 37 return; 38 } 39 base.OnActionExecuting(context); 40 } 41 }
1 public void ConfigureServices(IServiceCollection services) 2 { 3 services.AddControllersWithViews(options => 4 { 5 //全局注册的时候,过滤器中的构造函数中的注入对象会自动注入(不需要多此一举进行传入: options.Filters.Add(new ClientIpCheckFilter(Configuration));) 6 //1. 操作过滤器 7 options.Filters.Add(typeof(ClientIpCheckFilter)); 8 //2. 页面过滤器 9 //options.Filters.Add(new ClientIpCheckPageFilter(Configuration)); 10 }); 11 }
3. Razor Pages 过滤器
同上操作过滤器类似。
二. 重定向攻击
1. 定义
Web 应用程序频繁地将用户重定向到登录页时他们访问要求进行身份验证的资源。 重定向通常包括returnUrl查询字符串参数,以便用户可以返回到最初请求的 URL 后它们已成功登录。 对用户进行身份验证后,它们重定向到他们具有最初请求的 URL。因为请求的查询字符串中指定的目标 URL, 则恶意用户可能篡改在查询字符串。 被篡改的查询字符串可能允许将用户重定向到外部、恶意站点的站点。 这一技术称为开放重定向(或重定向)攻击。
2. 案例分析
(1).需求
一个网站,有些页面是页面是可以直接查看,有些页面或操作需要登陆后才能看,在直接可以看的页面点击登录,登录成功后还是回到该页面。分两类:
A. 某个页面的按钮触发ajax请求需要登录:如果没有登录,点击后,提示没有登录,然后进入登录页,登录成功后,进入发送ajax请求的那个页面。
B. 某个页面的href链接需要登录,如果没有登录,点击后会直接进入登录页,登录成功后,进入到哪个页面,这里有两种选择
①. href要链接到的页面
②. 触发href的页面,然后需要进入的话,再重新点击一下这个链接
注:大部分情况这里倾向情况①,即href要链接到的目标页面
(2).技术分析
核心实现原理:进入登录页面会拼接 xxx?returnUrl=xxxx,在登录页面通过js截取returnUrl的值,传入到校验登录的接口,校验通过后,还需要通过IsLocalUrl校验returnUrl的合法性,合法直接将该地址返回给前端,进行跳转;不合法,将首页地址返回给前端用于跳转;如果returnUrl为空,表示是直接进入到登录页,同样也是返回首页地址。
校验是否登录:这里通过Session中是否有值来校验,还需要区分ajax请求和非ajax请求,如果是ajax请求,返回401给前端,然后前端跳转到登录页,并且将?returnUrl=windows.location.pathName,传递给登录页;如果是非ajax请求,则在过滤器中通过request.Path.ToString()获取请求地址(即href要链接到的页面),进行传递。
ps:如果是要进入到触发href的页面,则前端要进行传递returnUrl,然后在过滤器中通过Query获取,进行传递即可。
登录页面和校验登录的代码
1 <html> 2 <head> 3 <meta name="viewport" content="width=device-width" /> 4 <title>LoginIndex</title> 5 <script src="~/lib/jquery/dist/jquery.js"></script> 6 <script> 7 $(function () { 8 //获取某个Url的参数值 9 function GetUrlParam(name) { 10 var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); 11 var r = window.location.search.substr(1).match(reg); 12 if (r != null) { 13 return unescape(r[2]); 14 } else { 15 return ""; 16 } 17 } 18 19 $("#j_login").click(function () { 20 //获取地址栏returnUrl参数 21 var returnUrl = GetUrlParam("returnUrl"); 22 $.post("/Home/CheckLogin", { userAccount: "admin", pwd: "12345", returnUrl: returnUrl }, function (data) { 23 if (data.status == "ok") { 24 alert("登录成功"); 25 window.location.href = data.data; 26 } else { 27 window.location.href = data.data; 28 } 29 30 }); 31 }); 32 }); 33 </script> 34 </head> 35 <body> 36 我是登录页面 37 <button id="j_login">点我登录</button> 38 </body> 39 </html>
/// <summary> /// 登录接口 /// </summary> /// <param name="userAccount"></param> /// <param name="pwd"></param> /// <returns></returns> public IActionResult CheckLogin(string userAccount, string pwd, string returnUrl) { //1. 执行登录校验逻辑 //这里假设校验通过 HttpContext.Session.SetString("userName", userAccount); //2. 判断returnUrl中是否有值,如果没有值表示是直接进入到登录页面的,如果有值表示从别的页面跳转过来的 if (string.IsNullOrEmpty(returnUrl)) { //表示是直接进入到的登录页,则跳转到主页 return Json(new { status = "ok", data = "/Home/Index" }); } else { if (Url.IsLocalUrl(returnUrl)) { //是本地地址,则跳转过去 return Json(new { status = "ok", data = returnUrl }); } else { //非本地地址(如:非法地址),则跳转到主页 return Json(new { status = "ok", data = "/Home/Index" }); } } }
校验是否登录过滤器代码:
1 /// <summary> 2 /// 校验是否登录的过滤器 3 /// </summary> 4 public class CheckLogin : ActionFilterAttribute 5 { 6 /// <summary> 7 /// 方法执行前执行 8 /// </summary> 9 /// <param name="context"></param> 10 public override void OnActionExecuting(ActionExecutingContext context) 11 { 12 13 var _session = context.HttpContext.Session; 14 //判断是否登录 15 var isLogin = _session.GetString("userName"); 16 if (string.IsNullOrEmpty(isLogin)) 17 { 18 var request = context.HttpContext.Request; 19 var returnUrl = request.Path.ToString(); //获取当前请求地址 20 //判断请求类型 21 if (IsAjaxRequest(request)) 22 { 23 //表示是ajax请求 24 context.Result = new ContentResult { StatusCode = 401, Content = "您没有登录" }; 25 return; 26 } 27 else 28 { 29 //截断请求 30 context.Result = new RedirectResult($"/Home/LoginIndex?returnUrl={returnUrl}"); 31 return; 32 } 33 } 34 } 35 36 /// <summary> 37 /// 判断该请求是否是ajax请求 38 /// </summary> 39 /// <param name="request"></param> 40 /// <returns></returns> 41 private bool IsAjaxRequest(HttpRequest request) 42 { 43 string header = request.Headers["X-Requested-With"]; 44 return "XMLHttpRequest".Equals(header); 45 } 46 }
其它子页面代码
1 <html> 2 <head> 3 <meta name="viewport" content="width=device-width" /> 4 <title>Index1</title> 5 <style> 6 .now { 7 background-color: red; 8 } 9 </style> 10 <script src="~/lib/jquery/dist/jquery.js"></script> 11 12 <script> 13 $(function () { 14 console.log(window.location.pathname); 15 16 $("#j_btn1").click(function () { 17 window.location.href = encodeURI("/Home/Index1"); 18 }) 19 $("#j_btn2").click(function () { 20 window.location.href = encodeURI("/Home/Index2"); 21 }) 22 23 $("#j_login").click(function () { 24 window.location.href = encodeURI("/Home/LoginIndex?returnUrl=" + window.location.pathname); 25 }); 26 27 $("#j_btn3").click(function () { 28 $.ajax({ 29 url: "/Home/GetMsg", 30 type: "post", 31 data: {}, 32 datatype: "json", 33 success: function (data) { 34 console.log(data); 35 if (data.status == "ok") { 36 alert(data.msg); 37 } else { 38 alert(data.msg); 39 } 40 }, 41 //当安全校验未通过的时候进入这里 42 error: function (xhr) { 43 alert("您没有登录"); 44 window.location.href = encodeURI("/Home/LoginIndex?returnUrl=" + window.location.pathname); 45 } 46 }); 47 }) 48 $("#j_btn4").click(function () { 49 window.location.href = "/Home/DetailViews"; 50 }) 51 52 53 }); 54 55 </script> 56 57 58 </head> 59 <body> 60 <button id="j_btn1" class="now">页面1</button> 61 <button id="j_btn2">页面2</button> 62 <button id="j_login">登录</button> 63 64 <br /> 65 <button id="j_btn3">获取信息</button> 66 <button id="j_btn4">进入详情页</button> 67 68 <br /> 69 我是页面一 70 </body> 71 </html>
1 @{ 2 Layout = null; 3 } 4 5 <!DOCTYPE html> 6 7 <html> 8 <head> 9 <meta name="viewport" content="width=device-width" /> 10 <title>Index2</title> 11 <style> 12 .now { 13 background-color: red; 14 } 15 </style> 16 <script src="~/lib/jquery/dist/jquery.js"></script> 17 18 <script> 19 $(function () { 20 console.log(window.location.pathname); 21 22 $("#j_btn1").click(function () { 23 window.location.href = encodeURI("/Home/Index1"); 24 }) 25 $("#j_btn2").click(function () { 26 window.location.href = encodeURI("/Home/Index2"); 27 }) 28 29 $("#j_login").click(function () { 30 window.location.href = encodeURI("/Home/LoginIndex?returnUrl=" + window.location.pathname); 31 }); 32 33 $("#j_btn3").click(function () { 34 $.ajax({ 35 url: "/Home/GetMsg", 36 type: "post", 37 data: {}, 38 datatype: "json", 39 success: function (data) { 40 console.log(data); 41 if (data.status == "ok") { 42 alert(data.msg); 43 } else { 44 alert(data.msg); 45 } 46 }, 47 //当安全校验未通过的时候进入这里 48 error: function (xhr) { 49 alert("您没有登录"); 50 window.location.href = encodeURI("/Home/LoginIndex?returnUrl=" + window.location.pathname); 51 } 52 }); 53 }) 54 $("#j_btn4").click(function () { 55 window.location.href = "/Home/DetailViews"; 56 }) 57 58 59 }); 60 61 </script> 62 63 64 </head> 65 <body> 66 <button id="j_btn1" >页面1</button> 67 <button id="j_btn2" class="now">页面2</button> 68 <button id="j_login">登录</button> 69 70 <br /> 71 <button id="j_btn3">获取信息</button> 72 <button id="j_btn4">进入详情页</button> 73 74 <br /> 75 我是页面2 76 </body> 77 </html>
三. 跨站脚本攻击(XSS)
1. 定义
跨站点脚本 (XSS) 是一个安全漏洞,这会使攻击者将客户端脚本 (通常是 JavaScript) 放入网页。 当其他用户加载攻击者的脚本将运行受影响的页面时,攻击者可以窃取 cookie 和会话令牌,更改通过 DOM 操作网页的内容或将浏览器重定向到另一页。 当应用程序接受用户输入, 并将其输出到页面而无需验证、 编码或进行转义,通常会发生 XSS 漏洞。
2. 三种编码
Url、Html、JS三种编码,使用HttpUtility类,需要依赖程序集【System.Web】
1 public void TestCode() 2 { 3 string msg = "http://www.baidu.com"; 4 //Url编码和解码 5 var urlCode= HttpUtility.UrlEncode(msg); 6 var urlMsg = HttpUtility.UrlDecode(urlCode); 7 //Html编码和解码 8 var htmlCode = HttpUtility.HtmlEncode(msg); 9 var htmlMsg = HttpUtility.HtmlDecode(htmlCode); 10 //JS编码和解码 11 var jsCode = HttpUtility.JavaScriptStringEncode(msg); 12 }
四. 跨站点请求伪造攻击(XSRF/CSRF)
1. 定义
跨站点请求伪造(也称为 XSRF 或 CSRF)是一种针对 web 托管应用的攻击,恶意 web 应用可能会影响客户端浏览器与信任该浏览器的web 应用之间的交互。 这些攻击是可能的,因为 web 浏览器会自动向网站发送每个请求的身份验证令牌。 这种形式的攻击也称为一键式攻击或会话riding ,因为攻击利用用户以前的经过身份验证的会话。
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。