内容简介:保护 ASP.NET MVC 应用的 10 个关键点,必看必看必看!!!
介绍
很多 ASP.NET MVC 开发者都会写出高性能的代码,很好地交付软件,等等。但是却并没有安全性方面的计划。本文通过 10 个要点来保证 MVC 代码的安全性。
如果还是 MVC 开发的新手,建议你先看看 Youtube 上的教程:-
https://www.youtube.com/watch?v=Lp7nSImO5vk
1) 安全配置错误 (必须设置自定义错误页面来处理错误)
有一种攻击是攻击者截获最终用户提交的表单数据,将其改变再将修改后的数据发送到服务器。
对于这种情况,开发者需要进行适当的验证,不过验证显示的大量错误信息中可能会泄漏服务器信息。
现在来演示一下这个过程。
示例:-
为了演示,我创建了一个员工页面用来获取员工详情。
图 1,使用标记从浏览器视图添加员工
EmployeeDetail 模型视图
图 2,EmployeeDetail 模型
看起来页面上有足够多的注解验证来保证安全。然后并不是,这个页面还不够安全!我会演示如果绕过这些验证。
如果你还刚知道数据注解,那么这个 Youtube https://www.youtube.com/watch?v=Gft64NdIx3k 会是个很好的参考,它详解了如何使用数据注解来验证数据。
下面的截图展示了对地址字段的验证,这个字段要求值在 50 个字符以内。
图 3,为模型添加验证之后,对表单进行验证的时候可以看到,要求最多 50 个字符的地址字段中输入了太多字符,所以显示出了错误消息。
拦截添加员工视图
现在来拦截这个表单,然后向服务器提交拦截后的数据。我使用的 工具 叫 burp suit,它捕捉向服务器发送的请求以及服务器的响应。
下面的截图显示我捕捉到了发往服务器的请求。
图 4,使用 burp suite 拦截添加员工的表单,你可以看到这个工具捕捉到了用户提交的表单数据。
下面的截图中,我捕捉到了发往服务器的请求,你可以看到我改变了地址信息,虽然它限制最多 50 个字符,但是我添加了不止 50 个字符,然后提交到服务器。
拦截添加员工表单的地址字段
图 5,在 burp suite 中拦截处理地址字段,并提交到服务器
下面的截图显示提交到服务器的请求包含了超过 50 个字符的地址数据。
调试模式下查看员工表单
在提交了超过 50 个字符的地址字段到服务器之后,服务器抛出了异常。因为在数据库中,地址字段的数据类型是 varchar(50),如果数据超出了 50 个字符,显然会产生异常。
图 6,在 burp suite 中拦截地址字段并提交到服务器
图 7,拦截修改的地址字段导致错误
向用户显示错误时的问题
现在发生的异常会直接显示给攻击者,这会泄漏大量有价值的服务器信息和程序行为相关的信息。通过这些信息,攻击者可以通过多种方式及其组合来试探我们的系统。
图 8:- 直接向用户呈现错误
解决办法:-
要解决这个问题,需要我们设置一些不会显示内部技术错误的页面,这些页面只会显示一些自定义的错误消息。
这里有两种方法:-
- 创建自定义的错误处理属性。
- 在 Web.config 文件中设置自定义错误页面
方法 1:-
使用 HandleErrorAttribute 或 IExceptionFilterFilter 创建自定义的错误处理属性。
下面是使用 HandleErrorAttribute 的示例
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Web.Mvc; namespace MvcSecurity.Filters { public class CustomErrorHandler : HandleErrorAttribute { public override void OnException(ExceptionContext filterContext) { Exception e = filterContext.Exception; filterContext.ExceptionHandled = true; var result = new ViewResult() { ViewName = "Error" }; ; result.ViewBag.Error = "Error Occur While Processing Your Request Please Check After Some Time"; filterContext.Result = result; } } }
创建了自定义错误属性之后,我们要在整个应用中把它作为全局属性使用。因此我们需要在 App_Start 目录下的 FilterConfig 类中调用这个属性,如下:
using MvcSecurity.Filters; using System.Web; using System.Web.Mvc; namespace MvcSecurity { public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new CustomErrorHandler()); } } }
一旦发生错误,CustomErrorHandler 属性就会被调用,它会把请求重定向到 Error.cshtml 页面。如果你想传递一些消息,可以在 CustomErrorHandler 属性中通过 @ViewBag.Error 来传递。
Html 错误页面的代码
@{ Layout = null; } <!DOCTYPEhtml> <html> <head> <metaname="viewport"content="width=device-width"/> <title>Error</title> </head> <body> <hgroup> <h1 class="text-danger">Error.</h1> <h2>@ViewBag.Error</h2> <h2></h2> </hgroup> </body></html>
错误页面视图
图 9,如果应用中发生错误,显示自定义的错误页面
方法 2:-
在 Web.config 文件中设置自定义页面
如果你不想写属性,你可以在 Web.config 文件中设置自定义页面。在此之前先使用 HTML 创建一个简单的错误页面用来显示发生的错误。
Web.config 文件中有一个 system.web 标签,在这里添加 CustomErrors 标签。如下所示:
图 10,设置自定义错误页面时的 Web.config 文件截图
Html 错误页面代码
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Error</title> </head> <body> <hgroup> <h1 class="text-danger">Error.</h1> <h2>An error occurred while processing your request...........</h2> <h2></h2> </hgroup> </body> </html>
Html 错误页面呈现
图 11,如果应用中发生错误,显示自定义的 HTML 错误页面
哇,这才是保护 Web 应用程序的第一步。接下来看看第二点。
2) 跨站请求伪造 (CSRF)
CSRF 漏洞让攻击者可以通过已验证通过并登录的用户账号进行操作,而且不会引起用户的注意。
举一个简单的例子。
- 用户登入银行服务器。
- 银行验证后授权并在用户和银行服务器之间建立一个安全会话。
- 攻击者向用户发送一个恶意链接,其内容是“立刻赚取100000美元”。
- 用户点击这个恶意链接,这个网站会试图从你的账号转账给攻击者的账号。因为安全会话已经建立,所以恶意代码可以执行成功。
微软已经认识到这种威胁,并使用 AntiForgeryToken 来防止这种攻击。
解决办法:-
我们需要在表单内部添加
@Html.AntiForgeryToken()
然后在处理提交([HttpPost])请求的 Action 方法上添加 [ValidateAntiForgeryToken] 属性,用于检查令牌是否有效。
在视图中添加 [AntiForgeryToken] 辅助方法
图 13,在视图中添加 AntiForgeryToken
为 Post [HttpPost] 方法添加 [ValidateAntiForgeryToken] 属性
图 14,为 [HttpPost] 方法(Index) 添加 ValidateAntiForgeryToken
使用 AntiForgeryToken
我们在视图上添加 AntiForgeryToken 辅助方法后,它会创建一个隐藏域,其值是一个唯一的令牌,同时为浏览器添加一项 Cookie。
在我们从 HTML 上提交数据的时候,它会检查 __RequestVerificationToken 隐藏字段和 __RequestVerificationToken Cookie 是否存在。如果 Cookie 或 __RequestVerificationToken 隐藏字段任意缺失一个,或者它们的值并不一致,ASP.NET MVC 就不会进行后续处理。因此,我们可以在 ASP.NET MVC 中防止跨站请求伪造攻击。
RequestVerificationToken 截图
图 15,RequestVerificationToken 产生的隐藏域
RequestVerificationToken Cookie 截图
图 16,RequestVerificationToken 产生的 Cookie
3) 跨站脚本 (XSS) 攻击
跨站脚本 (XSS) 攻击很觉,它通过向输入字段注入恶意脚本,使攻击者能够窃取凭据和其它有价值的数据,这会导致重大安全漏洞。
图 17,跨站脚本 (XSS).
在这个攻击中,攻击者访问网站并试图通过表单的注释输入框执行恶意脚本。这时候如果网站没有检查恶意代码,那么代码就可能在服务器上执行,造成损害。
让我们用一个简单的例子来帮助理解。下面是我们要保存的员工表单。在文本框中,我通过 SCRIPT 标记尝试执行某些恶意脚本。不过在提交的时候,MVC 会抛出错误提示有坏事发生。
总的来说,默认情况下 ASP.NET 会防止跨站脚本攻击。
理解显示出来的错误
从客户端检查到一个存在潜在威胁的表单数据 (worktype= "<script>alert('hi');")
.
因为 MVC 会验证用户输入的数据,如果用户试图执行不允许的脚本,就会发生这样的错误,这实在是个好消息。
图 18,通过 input 字段提交恶意脚本会导致错误
但现在我们希望能填入 SCRIPT 标签。比如,像 CodeProject 这样的编程网站确实需要用户提交代码和脚本片段。在这些场景中,我们希望用户能从 UI 提交代码。
那么我们得搞清楚如何在不失安全性的情况下完成这项任务。
为了提交脚本,我们有4件事要做。
解决办法:-
- [ValidateInput(false)]
- [AllowHtml]
- [RegularExpressionAttribute]
- AntiXSS 库
方法 1:-
ValidateInput
[ValidateInput] 属性用在我们希望允许提交代码的控制器和动作方法上。
如果我们想允许提交标签,就需要设置 enableValidation 属性为 false([ValidateInput(false)]),这样它就不会进行验证。如果我们设置为 true ([ValidateInput(true)]),那输入的内容就会被验证。如果你把这个属性用于控制器,它就会作用于该控制器内的所有动作方法上,如果你把它用于某个动作方法,则只有这个方法会受到影响。
不过 ValidateInput 属性会应用于模型的所有属性(EmployeeDetails).
在 HttpPost 方法上应用 ValidateInputAttribute 的截图
图 19,在 HttpPost 方法上应用 ValidateInput 属性
应用 ValidateInputAttribute 之后的截图
图 20,在 HttpPost 方法加添加 ValidateInput 属性后,它允许提交脚本
方法 2:-
AllowHtml
[AllowHtml] 用在模型属性上,使用了 AllowHtml 属性的模型属性将不会被验证,这样就可以在防御了跨站脚本攻击的情况下提交 HTML。
在下面的截图中,我在 EmpolyeeDetail 模型的 Address 属性中应用了 AllowHtml 属性。
图 21,在指定模型属性上应用 [AllowHtml] 属性
在 Address 属性上应用了 AllowHtml 属性之后,将不再验证 Address 属性,从而允许向这个字段提交 HTML。
图 22,在地址属性中应用 [AllowHtml] 之后它就允许提交脚本了
方法 3:-
正则表达式
第三种解决 XSS 攻击的办法使用正则表达式验证所有字段,这样可以限制只有有效的数据可以填入。
下面是用正则表达式验证输入以保护不受 XSS 攻击的截图
图 23,在模型属性中应用正则表达式验证
常用正则表达式列表
字母和空格
[a-zA-Z ]+$
字母
^[A-z]+$
数字
^[0-9]+$
字母和数字
^[a-zA-Z0-9]*$
电子邮件
[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?
手机号
^([7-9]{1})([0-9]{9})$
日期格式( mm/dd/yyyy | mm-dd-yyyy | mm.dd.yyyy)
/^(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.](19|20)\\d\\d+$/
网站 URL
^http(s)?://([\\w-]+.)+[\\w-]+(/[\\w- ./?%&=])?$
信用卡号
Visa
^4[0-9]{12}(?:[0-9]{3})?$
MasterCard
^5[1-5][0-9]{14}$
American Express
^3[47][0-9]{13}$
含小数的数字
((\\d+)((\\.\\d{1,2})?))$
办法 4:-
AntiXSS 库
防御 XSS 攻击的第四个办法是使用 Microsoft AntiXSS 库 ,它能保证你的应用程序。
我们从使用 NuGet 安装 Microsoft AntiXSS 库 开始, 右键点击项目,然后选择 管理NuGet包
图 24,选择管理 NuGet 包来添加包
选择之后会弹出一个 管理 NuGet 包 对话框,在这里搜索 AntiXSS 库。选中第一项, AntiXSS ,并点击安装按钮。
图 25,往项目中添加 Microsoft AntiXSS 库
安装后的引用
图 26,在项目中添加 Microsoft AntiXSS 库之后
完成安装之后我们来看看如何使用 AntiXSS 库
Sanitizer 类
图 27,Sanitizer 类用于净化输入
下面的截图展示如何使用 Sanitizer 类方法
Sanitizer 是一个静态类,我们可以在任何地方访问它。我们只需要向 Sanitizer 类方法 (GetSafeHtmlFragment) 提供要验证的字段的输入,它就会返回经过检查和净化后的字符串。
图 28,这里以净化地址字段为例展示如何使用 Sanitizer 类净化输入
我们可以用这个方法在保存到数据库和显示到浏览器的时候过滤恶意脚本。
技巧:- 在使用 AntiXSS 库之前使用 [ValidateInput(false)] 或者 [AllowHtml],否则会报错“Form 请求中存在潜在的威胁”
4) 恶意文件上传
到目前为止,我们已经学会了如何保护所有输入字段不受攻击,但仍然缺少一个主要的输入域,即文件上传。很多攻击者会通过上传恶意文件来进行攻击,造成安全问题,我们需要对此进行防护。攻击者可能会修改文件扩展名 [tuto.exe 改为 tuto.jpeg],恶意脚本可能会被当作图片上传上来。多数开发者只看文件扩展名,就将这些文件保存在目录或数据库。但文件扩展名并不代码文件内容,这个文件有可能是恶意脚本。
图 29,此图展示人们如何上传允许或不允许的文件
解决办法:-
- 我们需要做的第一件事是验证文件上传
- 只允许上传需要的文件扩展名
- 检查文件头
首先,在视图中添加一个文件上传控件
添加文件上传控件
图 30,在添加员工视图中添加文件上传控件
添加文件上传控件之后需要在提交时验证文件
在 Index HttpPost 方法中验证文件上传
在这个方法中,我们先验证文件的内容长度是否为 0 [upload.ContentLength == 0],为 0 则不允许上传这个文件。
如果内容长度大于 0 则说明包含文件 [upload.ContentLength > 0],然后我们会访问 File 的 Filename,以及 ContentType 和 Bytes。
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Index(EmployeeDetail EmployeeDetail) { if (ModelState.IsValid) { HttpPostedFileBase upload = Request.Files["upload"]; if (upload.ContentLength == 0) { ModelState.AddModelError("File", "Please Upload Your file"); } else if (upload.ContentLength > 0) { string fileName = upload.FileName; // getting File Name string fileContentType = upload.ContentType; // getting ContentType byte[] tempFileBytes = new byte[upload.ContentLength]; // getting filebytes var data = upload.InputStream.Read(tempFileBytes, 0, Convert.ToInt32(upload.ContentLength)); var types = MvcSecurity.Filters.FileUploadCheck.FileType.Image; // Setting Image type var result = FileUploadCheck.isValidFile(tempFileBytes, types, fileContentType); // Validate Header if (result == true) { int FileLength = 1024 * 1024 * 2; //FileLength 2 MB if (upload.ContentLength > FileLength) { ModelState.AddModelError("File", "Maximum allowed size is: " + FileLength + " MB"); } else { string demoAddress = Sanitizer.GetSafeHtmlFragment(EmployeeDetail.Address); dbcon.EmployeeDetails.Add(EmployeeDetail); dbcon.SaveChanges(); return View(); } } } } return View(EmployeeDetail); }
目前为止我们对文件所做的都是基本验证。我写了一个静态类 FileUploadCheck,在这个类中有各种方法用来验证不同的文件类型。现在我告诉你们如何验证图片文件,并只允许上传图片文件。
FileUploadCheck 类
图 31,展示 FileUploadCheck 类,它被定制用于验证文件上传。
在上面的截图中有个 ImageFileExtension 枚举,它包含了图像格式和文件类型。
private enum ImageFileExtension { none = 0, jpg = 1, jpeg = 2, bmp = 3, gif = 4, png = 5 }
public enum FileType { Image = 1, Video = 2, PDF = 3, Text = 4, DOC = 5, DOCX = 6, PPT = 7, }
如果它通过了基本验证,我们会调用 isValidFile 方法,它需要字节(内容)、文件类型和文件的 ContentType 作为输入。
public static bool isValidFile(byte[] bytFile, FileType flType, String FileContentType) { bool isvalid = false; if (flType == FileType.Image) { isvalid = isValidImageFile(bytFile, FileContentType); } else if (flType == FileType.Video) { isvalid = isValidVideoFile(bytFile, FileContentType); } else if (flType == FileType.PDF) { isvalid = isValidPDFFile(bytFile, FileContentType); } return isvalid; }
调用 isValidFile 方法后它会调用另一个关于文件类型的方法。
如果文件类型是图像,会调用第1个方法 [isValidImageFile],如果不是图像而是视频则会调用第2个方法 [isValidVideoFile]。类似的如果文件是 PDF,则会调用最后一个方法 [isValidPDFFile]。
搞明白 isValidFile 方法之后我们来看看要调用的 [isValidImageFile] 方法。
下面是 [isValidImageFile] 方法的完整代码段
在这个方法中,我们允许限制文件扩展名 [jpg, jpeg, png, bmp, gif]
使用 isValidImageFile 方法
我们把字节和文件的 ContentType 传入这个方法之后,它会先检查 ContentType 并据此设置
ImageFileExtension
之后就会检查文件头是否与之匹配,如果匹配则文件是有效的 [true],不匹配则无效 [false]。
public static bool isValidImageFile(byte[] bytFile, String FileContentType) { bool isvalid = false; byte[] chkBytejpg = { 255, 216, 255, 224 }; byte[] chkBytebmp = { 66, 77 }; byte[] chkBytegif = { 71, 73, 70, 56 }; byte[] chkBytepng = { 137, 80, 78, 71 }; ImageFileExtension imgfileExtn = ImageFileExtension.none; if (FileContentType.Contains("jpg") | FileContentType.Contains("jpeg")) { imgfileExtn = ImageFileExtension.jpg; } else if (FileContentType.Contains("png")) { imgfileExtn = ImageFileExtension.png; } else if (FileContentType.Contains("bmp")) { imgfileExtn = ImageFileExtension.bmp; } else if (FileContentType.Contains("gif")) { imgfileExtn = ImageFileExtension.gif; } if (imgfileExtn == ImageFileExtension.jpg || imgfileExtn == ImageFileExtension.jpeg) { if (bytFile.Length >= 4) { int j = 0; for (Int32 i = 0; i <= 3; i++) { if (bytFile[i] == chkBytejpg[i]) { j = j + 1; if (j == 3) { isvalid = true; } } } } } if (imgfileExtn == ImageFileExtension.png) { if (bytFile.Length >= 4) { int j = 0; for (Int32 i = 0; i <= 3; i++) { if (bytFile[i] == chkBytepng[i]) { j = j + 1; if (j == 3) { isvalid = true; } } } } } if (imgfileExtn == ImageFileExtension.bmp) { if (bytFile.Length >= 4) { int j = 0; for (Int32 i = 0; i <= 1; i++) { if (bytFile[i] == chkBytebmp[i]) { j = j + 1; if (j == 2) { isvalid = true; } } } } } if (imgfileExtn == ImageFileExtension.gif) { if (bytFile.Length >= 4) { int j = 0; for (Int32 i = 0; i <= 1; i++) { if (bytFile[i] == chkBytegif[i]) { j = j + 1; if (j == 3) { isvalid = true; } } } } } return isvalid; }
从动作方法中调用 isValidFile 方法
我们会调用 (FileUploadCheck.isValidFile) 并传入文件的字节(内容)、类型、ContentType 作为参数。
这个方法会返回布尔值,用 true 表示文件有效,false 表示文件无效。
string fileName = upload.FileName; // getting File Name string fileContentType = upload.ContentType; // getting ContentType byte[] tempFileBytes = new byte[upload.ContentLength]; // getting filebytes var data = upload.InputStream.Read(tempFileBytes, 0, Convert.ToInt32(upload.ContentLength)); var types = MvcSecurity.Filters.FileUploadCheck.FileType.Image; // Setting Image type var result = FileUploadCheck.isValidFile(tempFileBytes, types, fileContentType); // Validate Header
搞懂了上面那段代码之后我们来看个演示。
下面的截图是一个带文件上传 控件的员工表单
我们会填写这个表单并上传一个有效的文件。
图 32,含文件上传的添加员工表单
选择一个有效的 .jpg 文件并检查它具体如何验证
从磁盘上选择一个 .jpg 图像。
图 33,选择文件上传
选择文件之后的员工表单
在这里我们已经选择了一个文件。
图 34,已选择文件准备上传
调试 Index Post 动作
这一部分我们提交了一个含有文件的员工表单,我们可以看看对它进行的基本验证。
图 35,提交用于保存的表单之后,实时显示出来我们上传的文件
调试 Index Post 动作
这一部分,你会看到已经通过了基本验证
图 36,提交表单数据后,实时显示上传的文件
调用 isValidFile 方法时的 FileUploadCheck 类
这部分是调用 isValidFile 方法之后,正准备根据文件的 ContentType 调用另一个方法。
图 37,根据文件类型调用 FileUploadCheck 类内部的方法
isVaildImageFile 方法检查文件头数据
这个方法会检查上传的图像文件的文件头是否与我们指定的相匹配,并以此判断文件是否有效。
图 38,检查图像的文件头是否与指定的相匹配
5) 版本披露
攻击者可以利用暴露出来的版本信息针对特定的版本进行攻击。
浏览器通过 HTTP 向服务器发送请求后都收到响应头会包含这些信息:[Server, X-AspNet-Version,X-AspNetMvc-Version, X-Powered-By]。
显示的这些信息正是 Web 服务器正在使用的。
X-AspNet-Version 是使用的 Asp.Net 版本。
X-AspNetMvc-Version 是 ASP.NET MVC 版本。
X-Powered-By 是框架版本信息。
图 39,响应头暴露了版本信息
解决办法:-
- 删除 X-AspNetMvc-Version 头
我们可以通过内置的 MVC 属性显示 ASP.NET MVC 版本的 X-AspNetMvc-Version。
只需要在 Global.asax 的应用启动事件 [Application_Start()] 中设置 [MvcHandler.DisableMvcResponseHeader = true;],这样响应头中就不会再出现 MVC 的版本信息 了。
图 40,在 Global.asax 中通过设置属性移除 X-AspNetMvc-Version 响应头。
图 41,删除 X-AspNetMvc-Version 响应头之后的响应信息
2) 删除 X-AspNet-Version 和服务器响应头
要删除服务器响应头显示的服务相关的信息以及 X-AspNet-Version,只需要在 Global.asax 的 [Application_PreSendRequestHeaders()] 事件中像下面这样设置个属性:
protected void Application_PreSendRequestHeaders() { Response.Headers.Remove("Server"); //Remove Server Header Response.Headers.Remove("X-AspNet-Version"); //Remove X-AspNet-Version Header }
图 42,在 Global.asax 中添加 Application_PreSendRequestHeaders 事件,从中删除响应头
图 43,删除 X-AspNet-Version 和 Server 响应头之后的响应信息
3) 删除 X-Powered-By 头信息
X-Powered-By 响应头中包含运行网站的框架信息。
要删除 [X-Powered-By] 响应头,只需要在 Web.Config 文件的 System.webServer 配置添加下面的标签。
<httpprotocol> <customheaders> </customheaders> </httpprotocol>
图 44,在 Web.config 中添加自定义头标签来删除响应头
图 45,删除 X-Powered-By 响应头之后的响应信息
6) SQL 注入攻击
SQL 注入攻击是最危险的攻击之一,它在 OWASP2013 [开放Web应用安全项目] 提到的 10 大漏洞中排名第1。SQL 注入攻击可以向攻击者提供有价值的数据,攻击者可以利用这个安全漏洞访问数据库服务。
SQL注入攻击者总是试图输入恶意的 SQL 语句,使之在在数据库中执行并返回出本来不应该返回的数据给攻击者。
图 46,SQL 注入攻击示例,展示最常见的使用内联查询进行攻击。
显示用户数据的简单视图
下面的截图是基于 EmployeeID 显示单个员工的数据。
图 47,显示用户数据的员工视图
在受到 SQL 注入攻击之后显示所有用户数据的简单视图
攻击者可以在这个浏览器视图中看到应用 URL 中包含重要数据 ID [http://localhost:3837/EmployeeList/index?Id=2] ,攻击者如下图所示进行SQL注入攻击。
图 48,受到 SQL 注入攻击之后显示所有用户数据的员工视图
通过对 排序 和组合 SQL 注入攻击,攻击者可以访问所有用户数据。
在调试模式下显示 SQL 注入
这里我们可以看到攻击者使用的恶意 SQL 语句。
图 49,调试模式下看 Index Action 方法
SQL Profiler 视图中的 SQL 语句
图 50,SQL Profiler 视图
解决办法:-
- 验证输入
- 使用权限较低的数据库用户
- 使用参数化查询
- 使用 ORM (比如 Dapper、Entity framework )
- 使用存储过程
- 验证输入
在客户端和服务器端都对输入进行验证,避免攻击者通过特殊字符进入系统。
我们在 MVC 中使用数据注解来进行验证
数据注解属性很容易应用于模型来验证数据
客户端验证输入
图 51,客户端验证输入
服务端验证输入
模型状态为 false,这表明模型有错
图 52,服务端验证输入
2) 给予最小权限的数据库用户
Db_owner是数据库的默认角色,它可以授权和取消授权,创建表、存储过程、视图,执行备份、计划任务,甚至删除数据库。如果使用这种角色来访问数据库,用户会拥有完整的访问权,并可以进行各种活动。我们必须创建一个具有访问数据所需要的最低权限的新用户来进行操作。
比如,如果用户只需要进行查询、插入和更新员工详情的操作,那么只需要为其分配 select、insert 和 update 权限。
添加用户并分配权限的步骤
这里我会展示一个创建用户并分配指定权限的示例。
- 创建新用户
- 创建用户之后进行查询
- 选择用户可以访问的对象(表)
- 选择指定的表来分配权限
图 53,创建新用户并分配权限
选择表之后,我们按下图所示分配权限。你可以看到我们已经给用户分配了 'Inset'、'Select‘、’Update' 权限。
图 54,分配 Insert、Select 和 Update 权限
使用最小权限可以帮助我们防止攻击者对数据库的攻击。
3) 使用存储过程
存储过程是参数化查询的一种形式。使用存储过程也是保护不受 SQL 注入攻击的方法之一。
在下面的截图中我们去掉了之前使用的内联查询,改写为使用存储过程来从数据库获取数据,这能有效防止 SQL 注入攻击。使用存储过程的时候我们需要传入参数 [@EmpID],它会根据这个参数从数据库获取数据记录。我们会在代码中使用 CommandType [CommandType.StoredProcedure] 来表明是在使用存储过程。
注意:- 总是使用带参数的存储过程,如果你不使用参数,仍然很容易受到 SQL 注入攻击。
图 55,使用存储过程显示员工详情
使用存储过程之后的 GetEmployee 视图
显示了数据的员工视图。
图 56,使用存储过程之后的员工详情
使用存储过程后的 Profiler 视图
跟踪显示我们使用的存储过程。
图 57,跟踪存储过程执行
使用存储过程后再次尝试进行 SQL 注入攻击
图 58,尝试在使用存储过程后进行 SQL 注入攻击
使用存储过程后在 Debug 模式下查看 SQL 注入攻击的执行过程
你可以看到,包含恶意 SQL 脚本的参数 id [?Id=2 or 1=1] 在传递给存储过程之后会显示一个错误,指出存储过程未能执行,因为它需要一个数值类型的参数,但我们传递进去的却是恶意 SQL 脚本 [?Id=2 or 1=1]。
图 59,使用存储过程
图 60,使用存储过程后尝试 SQL 注入攻击
使用存储过程后的 Profiler 视图
图 61,跟踪存储过程执行
输出:-
图 62,在攻击者进行攻击之后显示错误
这个时间你可能会认为如果参数定义为 varchar 类型,攻击就会成功。让我们再来看看。
使用带有 @name 参数的存储过程后在 Debug 模式下查看 SQL 注入攻击的执行过程
这已经是第二次我们通过传入不同的参数来观察存储过程真正保护 SQL 注入攻击了。
图 63,使用存储过程
图 64,在使用存储过程后尝试SQL注入攻击
图 65,跟踪存储过程执行
输出:-
图 66,使用存储过程后如果我们尝试 SQL 注入攻击,它不会执行也不会有结果输出
使用参数化查询
使用参数化查询是另外一种保护不受 SQL 注入攻击的解决办法。它不通过连接字符串的方式,而是需要为 SQL 查询添加参数并传递给 SqlCommand 使用。
图 68,参数化查询也可以保护不受 SQL 注入攻击
参数化查询的 Profiler 视图
图 69,跟踪参数化查询的执行
输出:-
图 70,使用参数化查询之后如果我们尝试 SQL 注入攻击它不会执行也没有结果显示出来
使用 ORM (Entity framework )
ORM 是对象关系映射(Object Relational Mapper),用于将 SQL 对象映射到领域(模型)对象 [C#]。
如果你使用实体框架,则不容易受到 SQL 注入攻击,因为实体框架内部使用参数化查询。
实体框架的常规执行
这里我们把名称作为参数 [?name=Saineshwar].
图 71,使用 Entity Framework 后传入参数
控制器执行时的截图
在调试模式下我们可以看到查询字符串传入的 name 参数,我们在 Linq 中使用它来获取记录。
图 72,控制器执行
Linq 查询的 Profiler 视图
Entity Framework 内部确实使用参数化查询的截图
图 73,跟踪 Entity Framework 生成的查询
尝试通过 SQL 注入攻击 Entity Framework
在这部分我们尝试传入参数 [?name=Saineshwar or 1=1] 来攻击实体框架。
图 74,尝试对 Entity Framework 进行 SQL 注入攻击
控制器执行时的截图
在调试模式中可以看到我们把查询字符串中的 name 参数传递给 Linq 来获取记录,但这时候正常的参数中存在恶意脚本。
图 75,使用 Enity Framework 后尝试 SQL 注入攻击没有输出
Linq 查询的 Profiler 视图
如果你仔细观察跟踪信息就会发现它把名称和恶意脚本作为一个单独的参数来处理,这就防止了 SQL 注入攻击。
图 76,尝试 SQL 注入攻击时跟踪由 Entity Framework 产生的查询
7) 暴露敏感数据
所有网站或应用程序都有一个存储着所有数据的数据库。也就是说,我们把用户的私人信息(可能包含密码、PAN码,护照详情,信用卡号等)保存在其中,然而我们只对密码进行了加密,其它数据都是明文存储,这在攻击者通过攻击得到数据库访问权的时候会造成敏感数据泄漏。他们会找到存储着这些私人数据和财务数据的表并从中偷取信息。
图 77,敏感数据泄漏
简单的演示嗅探敏感数据。
登录页面截图
创建项目时如果选择 Internet 模板,默认的登录页会有这样简单的代码。
图 78,登录页面标记及效果截图
看到了登录页面的代码和显示效果,现在输入凭证进行登录。
输入凭证进行登录
在登录页面我们会输入用户名和密码来登录到应用程序。
图 79,输入凭证
攻击者拦截登录页面来盗取凭证
用户在登录页面输入凭证并向服务器提交数据[username 和 password]的时候,数据是明文传输的,这些数据[username 和 password] 可以被攻击者拦截以获取凭证。
下面的截图展示了你的数据被攻击者拦截
图 80,拦截登录页面并显示 form 数据
解决办法 :-
- 总是以加密的方式向服务器传输敏感数据,使用健壮的随机 Hash
- 总是对 Web 应用使用 SSL
- 不要保存敏感数据,如果你需要存储,使用强大的 Hash 技术
使用强大的随机 Hash 加密发往服务器的敏感数据
在这个示例中,我们会使用 MD5 算法在客户端加密数据并发送到服务器端,这样攻击者就不能盗取相关信息了。
下面的截图是用户在输入凭证。
图 81,使用加密数据之后输入凭证
用户输入凭证之后,在点击登录按钮时,会对用户输入的密码使用某个种子进行 MD5 加密,之后再发往服务器。这个过程中如果有攻击者在嗅探网络,他只会看到加密后没法解密的 Hash。
你可以在下面的截图中看到攻击者嗅探网络的详情。
截图中蓝色线条表示 用户名 。
截图中的红色线条表示 随机加密后的密码 。
截图中的绿色线条表示 由服务器生成的随机种子 。
图 82,拦截加密数据后的登录页数据
到目前为止,我们已经从截图看到了效果,再来看看代码。
登录模型
图 83,LoginModel 截图
下面是 [HttpGet] Login 行为方法的代码
在这个方法中,我们产生随机 Hash [种子] 然后把它赋值给 LoginModel 再传递给登录视图。
图 84,生成随机 Hash 并将其赋予 [hdrandomSeed],然后将这个模型传递给视图。
给模型的 [hdrandomSeed] 赋值后在 Login 行为方法中我们将它处理成一个隐藏域。
图 85,使用 [hdrandomSeed] 属性在视图中生成隐藏域
现在页面上添加了一个隐藏域,我们来看看 JavaScript 中如何使用这个种子和 MD5 来加密数据。
我们需要使用 jQuery 1.8 和 md5.js 库,在用户点输入凭证并点击登录按钮之后,我们先获取用户输入的密码 [ var password1 = $('#Password');] 并生成 Hash [ calcMD5(password).toUpperCase()],然后加入种子再生成一次 Hash [ var hash = calcMD5(seed + calcMD5(password).toUpperCase());],这个结果是唯一的,它会被发送给服务器。
在下面的截图中查看详情
图 86,客户端使用种子和 MD5 算法产生 Hash 的代码截图
登录页面的调试视图
在变量窗口中,你可以实时看到用户点击登录按钮时生成的值。
图 87,调试模式下的客户端加密
客户端加密后攻击者可以拦截并查看密码文本。
拦截登录页
下面的截图中,用户输入了密码,然后密码被加密了,攻击者不会知道那是什么,因为它是由种子和密码共同计算的 Hash。
图 88,拦截登录页面
拦截之后我们可以看到提交到 Login 行为方法的数据。
从登录视图提交数据到 Login 行为方法之后
这里我们可以清楚地看到密码是加密后的形式。
图 89,从登录视图提交数据到 Login 行为方法后
下一步,我们要比较数据库中存储的密码和用户输入并加密的密码。标记为绿色的是种子值,标记为红色的是通过用户名从数据库取得的存储的密码,标记为黄色的是从登录页面提交过来的密码,最后蓝色标记的数据是通过数据库密码和种子计算而来,用于和提交的密码进行比较。
图 90,Login Actoin 方法的代码及实时显示的值
最终,用户输入的敏感数据受到了保护。
2) 使用 SSL 来保护 Web 应用程序
SSL (Secure Sockets Layer,安全套接字层) 是客户端和服务端进行通讯的安全(加密)层,它保证在客户端和服务端传输的所有数据 [银行信息、密码和其它金融交易] 都是安全(加密)的。
Fig 91.SSL (安全套接字层)
SSL 主要用在登录页面和支付网关。如果你愿意,也可以用于整个应用。
If you want to know in details how to Enable SSL on IIS Server there is a good article from Scott Guthrie Sir blog check URL below.
http://weblogs.asp.net/scottgu/tip-trick-enabling-ssl-on-iis7-using-self-signed-certificates
3) Do not store Sensitive data in Database in a clear form
Always Try not to store Credit Card, Debit Card and financial details and other Sensitive details in thedatabasein Clear form . Always Use Strong Hashing techniques to encrypted data and then store in thedatabase. if anattacker gets direct access to thedatabase then all data in clear form can be Breached.
下面几种算法可按需使用。
哈希(散列)算法
如果只是想计算摘要可以使用哈希算法,我们常用哈希算法来加密密码。
对称算法
如果想使用一个密钥来进行加密和解密,那么可以选用对称算法。
非对称算法
如果想使用一个密钥来(公钥)加密,然后使用另一个密钥(私钥)来解密,那可以选用非对称算法。比如我们可以在与客户分享 Web 服务和 Web API 的时候使用非对称算法。
哈希算法
- MD5
- SHA256
- SHA384
- SHA512
示例:-
生成 MD5 哈希的方法
private string Generate_MD5_Hash(string data_To_Encrypted) { using (MD5 encryptor = MD5.Create()) { MD5 md5 = System.Security.Cryptography.MD5.Create(); byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(data_To_Encrypted); byte[] hash = md5.ComputeHash(inputBytes); StringBuilder sb = new StringBuilder(); for (int i = 0; i < hash.Length; i++) { sb.Append(hash[i].ToString()); } return sb.ToString(); } }
输入文本来生成哈希值
string Hash = Generate_MD5_Hash("Hello");
输出
对称算法
- Aes
- DES
- RC2
- Rijndael
- TripleDES
示例:-
使用 AES 加密的方法
private string Encrypt_AES(string clearText) { string EncryptionKey = "##SAI##1990##"; byte[] clearBytes = Encoding.Unicode.GetBytes(clearText); byte[] array = Encoding.ASCII.GetBytes("##100SAINESHWAR99##"); using (Aes encryptor = Aes.Create()) { Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, array); encryptor.Key = pdb.GetBytes(32); encryptor.IV = pdb.GetBytes(16); using (MemoryStream ms = new MemoryStream()) { using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write)) { cs.Write(clearBytes, 0, clearBytes.Length); cs.Close(); } clearText = Convert.ToBase64String(ms.ToArray()); } } return clearText; }
使用 AES 解密的方法
private string Decrypt_AES(string cipherText) { string EncryptionKey = "##SAI##1990##"; byte[] cipherBytes = Convert.FromBase64String(cipherText); byte[] array = Encoding.ASCII.GetBytes("##100SAINESHWAR99##"); using (Aes encryptor = Aes.Create()) { Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, array); encryptor.Key = pdb.GetBytes(32); encryptor.IV = pdb.GetBytes(16); using (MemoryStream ms = new MemoryStream()) { using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write)) { cs.Write(cipherBytes, 0, cipherBytes.Length); cs.Close(); } cipherText = Encoding.Unicode.GetString(ms.ToArray()); } } return cipherText; }
加密文本
string DataEncrypt = Encrypt_AES("Hello"); // Encrypting Data (Pass text to Encrypt)
解密文本
string DataDecrypt = Decrypt_AES(DataEncrypt); // Decrypt data (Pass Encrypt text to Decrypt)
输出
非对称算法
- DSA
- ECDiffieHellman
- ECDsa
- RSA
示例:-
使用 RSA 加密的方法
public byte[] Encrypt(string publicKeyXML, string dataToDycript) { RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(publicKeyXML); return rsa.Encrypt(ASCIIEncoding.ASCII.GetBytes(dataToDycript), true); }
使用 RSA 解密的方法
public string Decrypt(string publicPrivateKeyXML, byte[] encryptedData) { RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(publicPrivateKeyXML); return ASCIIEncoding.ASCII.GetString(rsa.Decrypt(encryptedData, true)); }
输出
8) 跟踪审查
在 IT 世界,跟踪审查用于在用户使用的 Web 应用中追踪其活动,这对于检查安全问题、性能问题和应用系统错误来说非常重要。这能帮助我们了解问题所在并解决问题。
图 96,审查
解决办法:-
- 在 Web 应用中对所有用户活动进行跟踪审查,并随时监控审查信息
在 Web 应用中对所有用户活动进行跟踪审查,并随时监控审查信息
为了维护跟踪审查,首先我们要在数据库中创建一张表来保存审查数据,表名叫作 [AuditTB]。然后我们得创建一个名为 [UserAuditFilter] 的 ActionFilterAttribute,并写代码在其执行 Action 时往数据库插入数据,这些数据能表明当前是谁在访问应用程序。
AuditTB 表视图
在这个表中,我们加入可以识别用户及其行为的一些常见的列。
Fig 97.AuditTB 表视图
看过表视图之后,我们来关注根据表创建的模型。
AuditTB 模型
public partial class AuditTB { public int UsersAuditID { get; set; } public int UserID { get; set; } public string SessionID { get; set; } public string IPAddress { get; set; } public string PageAccessed { get; set; } public Nullable<System.DateTime> LoggedInAt { get; set; } public Nullable<System.DateTime> LoggedOutAt { get; set; } public string LoginStatus { get; set; } public string ControllerName { get; set; } public string ActionName { get; set; } }
现在来创建名为 UserAuditFilter 的 ActionFilter。
名称 UserAuditFilter 的 Actionfilter 代码
UserAuditFilter 是一个自定义的 ActionFilter,我们创建它来将用户活动相关的数据插入 AuditTB 表中,顺便我们会检查用户是否登入应用程序,我们同样也可以将用户访问应用程序的 IP 地址和时间戳插入数据表,当作记录。我们使用 Entity Framework 来插入数据。
public class UserAuditFilter : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { AllSampleCodeEntitiesappcontext = newAllSampleCodeEntities(); AuditTBobjaudit = newAuditTB(); //Getting Action Name string actionName = filterContext.ActionDescriptor.ActionName; //Getting Controller Name string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; var request = filterContext.HttpContext.Request; if (HttpContext.Current.Session["UserID"] == null) // For Checking User is Logged in or Not { objaudit.UserID = 0; } else { objaudit.UserID = Convert.ToInt32(HttpContext.Current.Session["UserID"]); } objaudit.UsersAuditID = 0; objaudit.SessionID = HttpContext.Current.Session.SessionID; // Application SessionID // User IPAddress objaudit.IPAddress = request.ServerVariables["HTTP_X_FORWARDED_FOR"] ?? request.UserHostAddress; objaudit.PageAccessed = request.RawUrl; // URL User Requested objaudit.LoggedInAt = DateTime.Now; // Time User Logged In || And time User Request Method if (actionName == "LogOff") { objaudit.LoggedOutAt = DateTime.Now; // Time User Logged OUT } objaudit.LoginStatus = "A"; objaudit.ControllerName = controllerName; // ControllerName objaudit.ActionName = actionName; // ActionName appcontext.AuditTBs.Add(objaudit); appcontext.SaveChanges(); // Saving in database using Entity Framework base.OnActionExecuting(filterContext); } }
将 UserAuditFilter 注册为全局 Action 过滤器
全局 Action 过滤器主要用于处理和记录错误。
如果你希望在项目中所有 Action 方法上应用某个 Action 过滤,你可以将其注册为全局 Action 过滤器。在这里,因为我们想记录所有用户请求,进行跟踪审查,所以我们需要全局 Action 过滤器。
图 98,将 UserAuditFilter 添加到全局过滤器集合中
输出
用户请求页面和进行某些活动的时候相关数据会被被插入到审查表中
图 99,插入数据之后的审查表视图
9) 打破谁和会话管理
如果认证和会话管理并没有恰当的在同一个 Web 应用中实现,那攻击者就有可能偷取密码、会话令牌、Cookie 和类似能让攻击者不需要用户凭据访问整个应用的东西。
攻击者偷取数据的途径有
- 非安全连接(没使用SSL)
- 可预见的登录凭证
- 不以加密形式存储凭证的表单
- 未正确注销
可能的攻击
- 固化会话
在找到应对这种攻击的解决办法之前,让我们先来看一个小小的固化会话的攻击演示。
用户第一次发送请求到服务器时,加载了登录页面,之后用户输入有效的登录凭证来登录 Web 应用。如果登录成功,我们会在会话里保存一些值来对用户进行唯一识别,也就是说,[“ASP.NET_SessionId”] 这个 Cookie 被加入浏览器用于识别当前用户,之后所有向服务器发送的请求都会包含 [“ASP.NET_SessionId”] Cookie 值直到注销。而在注销的时候,我们基本上会写一段代码来删除创建于会话中的数据,但我们不会删除登录时创建的 [“ASP.NET_SessionId”] Cookie 值。这个数据会帮助攻击者进行固化会话攻击。
图 100,固化会话
固化会话的演示
我们访问登录页面时,浏览器中没有 [“ASP.NET_SessionId”] cookie,我们可以在 Cookie 管理器中查看。
用户输入有效的凭证之后
输入有效凭证之后 [“ASP.NET_SessionId”] Cookie 被添加到浏览器。
注意:- 任何数据保存到 Session 对象都会导致在浏览器端创建 [“ASP.NET_SessionId”] cookie 。
从应用程序注销之后 Cookie 仍然存在于浏览器中
从应用程序注销之后仍然存在 [“ASP.NET_SessionId”] Cookie。
注意:之前的 cookie 和之后的 cookie 是相同的,所以会造成固化会话
Let’s do Some Session Fixation
The [“ASP.NET_SessionId”] Cookie which is not removed after logout which helps attacker for doing session fixationI will open abrowser (chrome) in that I will enter URL [http://localhost:3837/] of application in which we are going to do session fixation.
After Entering URL in thebrowsernow lets check we have any [“ASP.NET_SessionId”] Cookie Created here oh we don’t have any Cookie.
Cookie which is already Created in Firefox browser
In this view, I have shown [“ASP.NET_SessionId”] cookie which is created in firefox browser when theuser logged in.
Note :- For managing Cookie, I have installed Cookie Manager+ addon in Chrome browser.
After having aview on [“ASP.NET_SessionId”] cookie in firefox now lets Create [“ASP.NET_SessionId”] cookie in Chrome browser similar to Cookie which is in Firefox browser with same [“ASP.NET_SessionId”] Cookie Name and Values.
Created New [“ASP.NET_SessionId”] Cookie in Chrome browser similar to Cookie created in Firefox browser.
This is astep where we have Fixed Session this session is live on another browser[firefox] we have copied values similar to that and created a [“ASP.NET_SessionId”] Cookie and assign similar values of SessionID to this Cookie.
Note :- For Adding Cookie I have installed Edit this Cookie addon in Chrome browser
After fixing cookie now we no more require any login to theapplication if we just enter Inner URL of application we get direct access because this session is created after authentication.
Solution :-
- Remove [“ASP.NET_SessionId”]after logout .
- Securing Cookies
- Use SSL for Securing Cookies and Session
Remove [“ASP.NET_SessionId”] after logout
On logout we are removing Session values long with that we are removing [“ASP.NET_SessionId”] Cookie from browser.
// // POST: /Account/LogOff publicActionResultLogOff() { //Removing Session Session.Abandon(); Session.Clear(); Session.RemoveAll(); //Removing ASP.NET_SessionId Cookie if (Request.Cookies["ASP.NET_SessionId"] != null) { Response.Cookies["ASP.NET_SessionId"].Value = string.Empty; Response.Cookies["ASP.NET_SessionId"].Expires = DateTime.Now.AddMonths(-10); } if (Request.Cookies["AuthenticationToken"] != null) { Response.Cookies["AuthenticationToken"].Value = string.Empty; Response.Cookies["AuthenticationToken"].Expires = DateTime.Now.AddMonths(-10); } returnRedirectToAction("Login", "Account"); }
Securing cookie
For securing cookies On Login [HttpPost] Action Method we are goingto Create a New Session in that Session [Session["AuthenticationToken"]] we are going save NewGuid along with that we are going to add a cookie with Name ["AuthenticationToken"] and it will also have same Value [Guid] which we have stored in Session.
Code Snippet
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] publicActionResult Login(LoginModel model, stringreturnUrl) { if (ModelState.IsValid) { //Getting Pasword from Database varstoredpassword = ReturnPassword(model.UserName); // Comparing Password With Seed if (ReturnHash(storedpassword, model.hdrandomSeed) == model.Password) { Session["Username"] = model.UserName; Session["UserID"] = 1; // Getting New Guid stringguid = Convert.ToString(Guid.NewGuid()); //Storing new Guid in Session Session["AuthenticationToken"] = guid; //Adding Cookie in Browser Response.Cookies.Add(newHttpCookie("AuthenticationToken", guid)); returnRedirectToAction("Index", "Dashboard"); } else { ModelState.AddModelError("", "The user name or password provided is incorrect."); } } return View(model); }
Code snippet description
Creating a new Guid.
// Getting New Guid stringguid = Convert.ToString(Guid.NewGuid());
Saving a new Guid in Session.
//Storing new Guid in Session Session["AuthenticationToken"] = guid;
Saving a new Guid in Cookie and adding.
//Adding Cookie in Browser Response.Cookies.Add(newHttpCookie("AuthenticationToken", guid));
After storing data in session and adding a cookie in thebrowser now let’s match this values on every request and check these values are similarif not then we are going to redirect to Login page.
For doing this part I am going to add an AuthorizationFilter in theproject and inside that we are going to write logic for checking Session and cookie values are similar or not.
AuthenticateUser ActionFilter
If you check below code snippet I have created an AuthorizationFilterwith name AuthenticateUser this filter we are inheriting IAuthorizationFilter Interface and FilterAttribute class with this we are implementing method inside interface [OnAuthorization] and write whole logic in this method.
using System; usingSystem.Web.Mvc; namespaceMvcSecurity.Filters { publicclassAuthenticateUser : FilterAttribute, IAuthorizationFilter { publicvoidOnAuthorization(AuthorizationContextfilterContext) { stringTempSession = Convert.ToString(filterContext.HttpContext.Session["AuthenticationToken"]); stringTempAuthCookie = Convert.ToString(filterContext.HttpContext.Request.Cookies["AuthenticationToken"].Value); if (TempSession != null&&TempAuthCookie != null) { if (!TempSession.Equals(TempAuthCookie)) { ViewResult result = newViewResult(); result.ViewName = "Login"; filterContext.Result = result; } } else { ViewResult result = newViewResult(); result.ViewName = "Login"; filterContext.Result = result; } } } }
Code snippet description
In this method first we are going get session and cookie values.
stringTempSession = Convert.ToString(filterContext.HttpContext.Session["AuthenticationToken"]); stringTempAuthCookie = Convert.ToString(filterContext.HttpContext.Request.Cookies["AuthenticationToken"].Value);
After getting values from session and cookie now we are going check that both session and cookie values are not null after that we are going see values are equal of both session and acookie if they are not then we are going to redirect to login page.
if (TempSession != null&&TempAuthCookie != null) { if (!TempSession.Equals(TempAuthCookie)) { ViewResult result = newViewResult(); result.ViewName = "Login"; filterContext.Result = result; } } else { ViewResult result = newViewResult(); result.ViewName = "Login"; filterContext.Result = result; }
After understanding codesnippet now we are going apply this filter on every controller which user access when he is logged in to application.
Applying AuthenticateUser Filter
Apply this filter on every controller which user access when he is logged into theapplication.
After applying Action filter on all the controller which user access after Logged into theapplication.
Now if theattackerknows[“ASP.NET_SessionId”] cookie valueand a new cookie[Cookies["AuthenticationToken"]] value he will still not able to Session Fixation attack here because the new [Cookies["AuthenticationToken"]] contains GUID which is unique and same values is stored in Session [Session["AuthenticationToken"]] on Web Serverbut attacker can’t know the Session value that is stored on the web server and this values keep changing every time when user is logging to application and the old session values which attacker used to do attack will not work in this scenarios.
Finally, if we will allow those User access to theapplication who has valid Session["AuthenticationToken"]value and Cookies["AuthenticationToken"]value.
Realtime Values of both Cookies.
Use SSL for Securing Cookies and Session Values
SSL (Secure Sockets Layer) is Layer which Secure (Encrypted) communication between client and server such that any data [Banking details, Password, Session ,Cookie and anotherfinancial transaction] passed from client and server is Secure (Encrypted).
Fig 111.SSL (Secure Sockets Layer).
10) Unvalidated Redirects and Forwards
In all web application, we do redirect from one page to another page and sometimes we redirect to another application too but while redirect we won't validate URL which we are going redirect which causesUnvalidated Redirects and Forwards Attack.
This attack mostly uses to phish User to get Valuable details (User Credentials) or to install maliciousmalware to the User computer.
Examples
In below snapshot, you will see Simple MVC application URL along with that a created malicious URL by anattackerthat redirects users to a malicious site that performs phishing and installs malware into user computers.
Fig 112.Crafted URL by anattacker.
Original URL :- http://localhost:7426/Account/Login
Crafter URL by Attacker :- ?returnUrl=https://www.google.co.in
Attack Scenario
In this attack User gets Email from attacker which contains offer related to Ecommerce shopping when user click on link given below he is redirected to shopping site [http://demotop.com] but if you see URL closely you will see that this URL contains redirect [http://demotop.com/Login/Login?url=http://mailicious.com] now after entering valid Username and Password the User will be redirected to Malicious site [http://mailicious.com] which is similar to [http://demotop.com] shopping site . On theMalicious site it will show Message “Invalid Username and Password” then again User will enter Username and Password and he will be redirected back to Original shopping site but meanwhile User Credentials are stolen in this attack.
Fig 113.Unvalidated Redirects and ForwardsAttack Scenario.
Solution
- Simply avoid using redirects and forwards.
- If you still want to use redirects and forwards then validate URL first.
- Use Url.IsLocalUrlto Preventing redirects and forwards in MVC.
Using Url.IsLocalUrlin MVC
Below is asnapshot of thelogin page in MVC which contains redirect URL to www.google.com when user enter Username and password then he will be redirected to www.google.comwhich is invalid to prevent this in MVC4 we have built in method called as Url.IsLocalUrlwhich checks that redirect URL which is Crafted is Local or not if not then it will not redirect it.
Fig 114.Login page in MVC with Redirect URL.
After understanding how redirect is passed not let’s check how this URL gets checked and executed.
The below [HttpPost] Login method get called when user enter Credentials and submit theform along with that redirect URL is also get posted which may containmalicious URL. For showing demo I have just checked that Username and password is not null after that we are calling RedirectToLocalAction Method and to this method we are going to pass redirect URL (returnUrl).
Fig 115.Login Post Action Method with returnURL.
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 深度有趣 | 27 服饰关键点定位
- 公链破局的5个关键点
- 使用Kubernetes的5个关键点!
- 人脸专集(三):人脸关键点检测(下)
- 在 GPUImage 中检测人脸关键点
- DevOps 实施的五个关键点
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
写给大家看的Web设计书
Robin Williams、John Tollett / 苏金国、刘亮 / 人民邮电出版社 / 201005 / 69.00元
在这个网络时代,Web设计几乎已经成为每个人生活的必备技能。如果你想自力更生创建一个网站,或者认为自己的网站在设计上还不尽如人意,希望它看上去更具创意和专业性,那么本书正是为你准备的! 作者Robin和John先采用通俗易懂的方式将有关基础知识娓娓道来,比如Internet、搜索信息、构建简单网页等,然后为我们奉上了精妙的技巧、技术和设计示例来启发大家的设计灵感,比如Web基本设计原则、实现......一起来看看 《写给大家看的Web设计书》 这本书的介绍吧!