原文链接:http://www.codeproject.com/Articles/1116318/Points-to-Secure-Your-ASP-NET-MVC-Applications
很多.net的程序员在交付、编写高性能的Asp.net Mvc代码等方面都做的很棒。但当谈及安全方面,却没有一个比较好的方案。
这里,我们将讲述10个使我们的Mvc代码更安全的建议。假如你是MVC新手,建议先了解下MVC。
错误的安全配置(必须设置自定义错误页)
在这种攻击中,攻击者截取并修改用户提交的数据,然后把修改后的数据提交到服务器。
下面演示一下该情景:
为了展示,创建了一个Employee 表单,表单里包含基本的员工信息。
using System.ComponentModel.DataAnnotations; namespace MvcSecurity.Models { public partial class EmployeeDetails { public int EmpID { get; set; } [Required(ErrorMessage = "Enter Name")] public string Name { get; set; } [StringLength(50)] [Required(ErrorMessage = "Enter Address")] public string Address { get; set; } [Required(ErrorMessage = "Enter Age")] public int? Age { get; set; } [Required(ErrorMessage = "Enter Salary")] public decimal? Salary { get; set; } [Required(ErrorMessage = "Enter worktype")] public string worktype { get; set; } } }
所以DataAnnotations 验证足以让页面安全吗?那就是想多了。那还远远不够。下面让你看看这些验证是怎样绕过的。
下图展示 在页面上 Address 字段的验证,字符长度不能超过50,超过50时,会有相关的错误提示信息展示出来。
现在让我们拦截这个表单,然后提交给服务器。使用一个工具叫 burpsuite,它能捕捉你发送给服务器的请求以及服务器发给你给回应。
Fig 7.Interception of Address field caused an error.
Fig 6.Intercepted Address field in burp suite and submitted to theserver.
Fig 7.Interception of Address field caused an error.
当前发生的异常直接展示给了攻击者,这就泄露了很多关于服务器和程序行为的信息。使用这些错误信息提醒,他可以尝试各种排列组合来测试我们的系统从而查看相关业务逻辑及服务器信息。
Fig 8 :- Displaying Error directly to Users.
解决方案:
这里的解决方案就是设置错误页,错误页上显示自定义错误信息。这样就不会显示内部技术错误了
我们有两种方法:
1.创建自定义错误处理属性。
2.在Web.config文件中设置自定义错误页
方法1:
使用HandleErrorAttribute或IExceptionFilterFilter创建自定义错误处理属性。
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; } } }
创建了自定义错误页之后,我们需要应用全局。这就需要在FilterConfig类中调用这个属性。FilterConfig类在App_Start 文件夹中能找到。
using MvcSecurity.Filters; using System.Web.Mvc; namespace MvcSecurity { public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new CustomErrorHandler()); } } }
当有错误出现的时候,CustomErrorHandler属性就会被调用,然后跳转到Error.cshtml页面。你想需要输出的信息,你可以通过@ViewBag.Error来传递。
@model HandleErrorInfo @{ ViewBag.Title = "Error"; } <h1 class="text-danger">Error.</h1> <h2 class="text-danger">@ViewBag.Error</h2>
方法2:
在web.config中设置你的自定义错误页。
如果你不想写属性,你可以在web.config中设置自定义错误页。同时你要先准备好一个简单的html错误展示页。
<!DOCTYPEhtml> <html> <head> <metaname="viewport"content="width=device-width"/> <title>Error</title> </head> <body> Error. An error occurred while processing your request. Please Check After Sometime </body> </html>
Fig 11.Displaying Custom HTML Error page if any error occurs in Application.
第二点:跨站请求伪造(CSRF)
跨站请求伪造漏洞,攻击者盗用了你的身份,以你的名义发送恶意请求。
下面举一个简单的例子。
- 用户登录到银行系统。
- 银行验证通过,用户处此时银行的cookie
- 攻击者此时发送一个邮件给用户,邮件内容为恶意链接,文本内容为:“轻松赚取10000块”
- 用户点击恶意链接,恶意站点会尝试从你的银行账户转钱到他们的账户上。因为此时,你跟银行之间建立的连接是可信的,所以恶意代码能成功执行。
Microsoft 已经认识到这一威胁,为了防止此类事情发生,我们可以使用 AntiForgeryToken。
解决方法:
我们需要在 form 表单里加上 @Html.AntiForgeryToken() helper 。同时在表单提交到的对应的 Http Action中需要加上[ValidateAntiForgeryToken]属性,它将会检查token是否有效。
Adding [AntiForgeryToken] helper to View
Fig 13.Adding AntiForgeryToken on View.
为[HttpPost] 方法添加[ValidateAntiForgeryToken] 属性。
当我们添加了AntiForgeryToken helper在视图上时,它会创建一个隐藏的input,值为唯一的token。此外,浏览器会话Cookie中也会被添加。
当我们提交我们的表单信息到url的Action中时,无论Cookie 是否存在,都会先检查__RequestVerificationToken这一字段。假如cookie或者 表单 __RequestVerificationToken 这一隐藏字段不存在,又或者token值不符合,Asp.net MVC 都不会继续这一次的提交。这就是Asp.net MVC预防 csrf攻击的方法。
RequestVerificationTokenon View snapshot.
Fig 15.RequestVerificationToken generated the hidden field.
RequestVerificationToken Cookie snapshot.
Fig 16.RequestVerificationToken generated a cookie.
3) 跨站脚本(XSS)攻击
XSS攻击是指恶意脚本通过输入框被注入。这种攻击很普遍,会导致有价值的数据被窃取进而导致大的安全漏洞。
Fig 17. Cross Site Scripting (XSS).
在这次攻击中,攻击者访问一个网站,在评论框里提交恶意脚本。假如网站没有检查恶意脚本代码,这就会导致恶意脚本在服务器上执行,从而造成危害。
让我们用实际例子来理解一下。在下面的Employee 的提交表单中,在输入框中,我尝试输入恶意脚本代码。假如我们提交到服务器上,会有不好的事情发生。
ASP.NET MVC 默认会阻止XSS攻击。
Understanding the Error Displayed
A potentially dangerous Request.Form value was detected from the client (worktype="<script>alert(‘hi‘);")
.
这个错误是因为MVC会验证用户输入的数据,用户想要输入这样的代码是不允许的。
Fig 18.SubmittingMaliciousscripts in Input fields which lead to Error.
但假如说我们想要输入 SCRIPT 标签呢。比如说编程性的网站如 codeproject 有这样的需求,终端用户可以提交 script片段。这些场景中,我们希望用户在页面上可以提交代码。
让我们知道怎样做到,但同时也能确保安全。
我们有四件法宝可以让用户提交脚本。
解决方法:
- [ValidateInout(false)]
- [AllowHtml]
- [RegularExpressionAttribute]
- AntiXSS Library
方法1:
ValidateInput
[ValidateInput] is an attribute which can be applied on Controller or Action Method on which we want the script to go through.
[ValidateInput]应用于 Controller或Action,它可以让script脚本通过。
如果我们想使用这个属性,我们需要设置为属性值为False[ValidateInput(false)],这样Asp.net MVC 就不会验证用户输入的数据了。如果应用于Controller上的话,Controller中的Action都会有效。如果只应用于Action的话,那只是这个Action有效。
但是[ValidatInput]属性是对整个Model(EmployeeDetails)有效的,就是说所有的字段都不会校验了。
Snapshot of Applying ValidateInputAttributeon HttpPostMethod.
Fig 19.Applying ValidateInput Attribute on HttpPost Method.
Snapshot after Applying ValidateInputAttribute
Fig 20.After adding ValidateInput Attributeon HttpPost Method it allows submitting script.
解决方法2:
AllowHtml
[AllowHtml]属性是作用于Model的属性上的,所以它不会验证加上了这个特性的属性的用户输入的。它允许提交HTML,避免XSS攻击。
下图,我把[AllowHtml]作用在EmployeeDetails Model的Address属性上。
Fig 21.Applying [AllowHtml] Attribute on Required Model Property.
当在Address属性上作用了[AllowHtml]属性时,Address 属性就不会校验,可以允许HTML提交。
Fig 22.After adding [AllowHtml] Attributeon Address Model Property it allows submitting script.
解决方法三:
Regular Expression
The third solution to XSS attack is validating all your Fields with Regular Expression such that only valid data can move in.
Use Regular Expression to validate input to protect from XSS attack below is Snapshot.
第三种应对XSS攻击的方法是给你的字段加上正则表达式验证,这样只有有效的数据才能通过。
下图使用正则表达式来验证输入,从而预防XSS攻击。
Fig 23.Applying Regular Expression to Model Property.
下面列出一些常用的正则表达式
字母及空格
[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})?$
十进制数
((\\d+)((\\.\\d{1,2})?))$
解决方法四:
AntiXSS Library
The fourth solution to XSS attack is by using MicrosoftAntiXSSLibrary which will help to protect your application.
Now let’s start with install MicrosoftAntiXSSLibrary from NuGetjust right click on Project then selectManageNuGetPackages.
第四种预防XSS攻击的方法是使用 MicrosoftAntiXSSLibrary 类库,她会保护你的应用。
首先我们需要安装 MicrosoftAntiXSSLibrary,在Nuget控制台中输入:Install-Package AntiXSS,即可成功安装。
安装过后会有下面的引用。
Reference added after installing
Fig 26.After adding MicrosoftAntiXSS Library to Project.
After installing we are going to have a look on how to use AntiXSSLibrary.
安装之后,我们来看一下怎么使用 AntiXSS类库。
Sanitizer类
Fig 27. Sanitizer Class which we are going to use for sanitizes inputs.
下图展示了如何使用Santizer类中的方法。
Santizer是一个静态方法,我们可以随时访问。我们需要提供输入的字段给 Santizer的方法 GetSafeHtmlFragment,她会检查然后返回 已经处理过的字符串。
Fig 28.Here we are showing how to sanitize inputs usingSanitizer Class for that I have taken address field as input to sanitize.
当数据需要保存在数据库中并且展示到浏览器时,我们可以使用这种方法来过滤恶意脚本。
Tip:使用AntiXSS的同时也试用 [ValdateInput(false)]或者[AllowHtml]的话,会抛出异常:有潜在危险的Request.Form。
4) 恶意文件上传
尽管我们目前已经学习了怎样保护你的输入框来以防攻击,但我们仍然漏掉了一个很重要的字段,那就是 文件上传控件。我们需要防止攻击者通过它来提交一下恶意代码。绝大多数的攻击者会尝试提交一个恶意文件,它会引起安全问题。攻击者可以改变文件后缀[tuo.exe to tuto.jpg],恶意文件被当作是图片文件被上传到服务器了。大部分的程序员只是检查了文件的后缀,然后保存在文件夹或者数据库之中。文件后缀是有效,但文件或许包含恶意的脚本。
Fig 29.This image shows how people try to upload files some try valid files and some invalid files.
解决方法:-
- 首先我们需要做的就是验证 file uploads
- 只允许上传设定后缀的文件
- 检查文件头.
首先,我将在视图上添加上传控件.
Adding file upload control
Fig 30.Adding File upload control on Add employee View.
现在已经在视图上加上了 文件上传组件,下一步要做的就是验证我们提交的文件。
在[HttpPost] Index 方法中验证上传文件
首先,我们验证一下文件的Content-Length,如果值为0,意味着用户没有上传文件。
如果Content-Length 大于0,那就是有上传文件。我们需要读取文件名,内容类型,文件字节。
[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 = FileUploadCheck.FileType.Image; // Setting Image type var result = FileUploadCheck.isValidFile(tempFileBytes, types, fileContentType); // Validate Header if (result) { 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); }
Till now it was basic validation we have done let’s Validate file which is uploaded for doing that I have written a static class with name FileUploadCheckin this class there are Various Method for validating different file type for now I am going to show you how to validate Images files and only allow image files only.
直到现在,我们做的都是基本的验证。为了验证我们上传的文件的作用,我写了一个静态类 FileUploadCheck。在这个类里,不同类型的文件类型有不同的验证方法。这里只展示验证图片文件。
FileUploadCheck 类
Fig 31.View of FileUploadCheck Class which is custom created for validating File uploads.
在上图中,有ImageFileExtension 枚举,包含了图片格式和文件类型。
If it has passed basic validation then we are going to call isValidFileMethod which take bytes, File type, FileContentType as input.
当已经通过基本的验证,我们调用isValidFileMethod方法,字节,文件类型,文件内容类型为参数输入。
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,根据不同的文件类型,它会调用调用另一静态方法。
如果文件类型是图片,会调用第一个方法 isValidImageFile ;文件类型是视频的话,会调用isValidVideoFile;同理,文件类型是PDF,则调用isValidPDFFile。
下面我们看一下isValidImageFile内部逻辑。
Below is complete code snippet of [isValidImageFile] Method.
在这个方法中,我们只允许文件后缀为[jpg, jpeg, png, bmp, gif]的图片文件。
Working of this isValidImageFilemethod
当我们调用这个方法时,会首先检查文件内容类型,从而设置 ImageFileExtension,之后会检查文件头鉴定文件是否真实有效。
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; }
Calling isValidFile method from Action Method
调用FileUploadCheck.isValidFile验证文件,如果图片文件有限,则会返回true,否,则返回false.
Calling isValidFile method from Action Method
We are going to call (FileUploadCheck.isValidFile) Method and then we are going to pass Parameter File Bytes, Types, FileContentType.
This Method will return Boolean value if it is valid file then true else it will return false.
stringfileName = upload.FileName; // getting File Name
stringfileContentType = upload.ContentType; // getting ContentType byte[] tempFileBytes = newbyte[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); //Calling isValidFile method
理解了逻辑代码了之后,我们来写一个demo看看它是如何运作的。
我们将要填写表单和上传一个图片文件。
Fig 32.Add Employee form View after adding file upload.
Choosing a valid .jpg file and check how it will validate in details
从磁盘中选择以.jpg为后缀的图片文件。
ig 33.Choosing File for upload.
Snapshot of Employee form after choosing a file.
在这里,我们选择了一个文件。
Fig 34.After Choosing File for upload.
Snapshot of debugging Index Post Action Method.
这里我们post提交了一个有文件的Employee 表单,来看看文件验证模块。
In this part we have posted an Employee form with a file here we can see have it is validating basic validations.
Fig 36.After Submitting Form for saving data, it shows real time value of file which we have uploaded.
Snapshot of FileUploadCheck Class while isVaildFile Method getsCalled.
In this part after calling isValidFile Method it going call another method according to its FileContentType.
Fig 37.Calling method inside FileUploadCheck Class according to File Type.
Snapshot of isVaildImageFile Method while checking Header Bytes.
In this method, it will check header bytes of theimage which is upload against the bytes which we have if it matches then it is a valid file else it is not avalid file.
Fig 38.Check header bytes of the image which is upload against the bytes which we have.
5) 版本信息泄露
版本信息可以被攻击者用来版本泄露的攻击。
不管什么时候浏览器发送HTTP请求到服务器,回应中,响应头包含了 [Sever,X-AspNet-Version,X-AspNetMvc-Version, X-Powered-By]等相关服务器信息。
Server暴露了目前服务器使用的web 服务器。
X-AspNet-Version 暴露了使用的Asp.net的版本。
X-AspNetMvc-Version暴露了使用的ASP.NET MVC版本。
X-Powered-By暴露了站点使用的.net 框架。
Fig 39.响应头暴露了版本信息.
解决方法:-
- 移除X-AspNetMvc-Version头
只需要在Global.asax文件中的Application start事件中设置[MvcHandler.DisableMvcResponseHeader = true;],X-AspNetMvc-Version头就不会展示出来了。
Fig 40.Setting property in Global.asax to removeX-AspNetMvc-Version from header.
Fig 41.Response after removing X-AspNetMvc-Version from header.
2)移除X-AspNet-Version and Server
为了移除Server头,需要在Global.asax文件中的Application_PreSendRequestHeaders事件中移除Response.Headers中的Server即可。
protected void Application_PreSendRequestHeaders() { Response.Headers.Remove("Server"); //Remove Server Header Response.Headers.Remove("X-AspNet-Version"); //Remove X-AspNet-Version Header }
Fig 42.Adding Application_PreSendRequestHeaders event in global.asax and then removing Response headers.
Fig 43.Response after removing X-AspNet-Version and Server from header.
3)移除 removingX-Powered-By头
在web.config文件中System.webServer节点中增加下面的标签即可。
<httpProtocol> <customHeaders> <remove name="X-Powered-By" /> </customHeaders> </httpProtocol>
Fig 44.Adding custom Header tag in Web.configfor removing Response headers.
Fig 45.Response after removing X-Powered-By from header.
6)SQL注入攻击
SQL注入是最危险的攻击之一。它在OWASP2013评选的10大漏洞中排在第一位。SQL注入攻击可以给高价值的数据给攻击者,会导致很大的安全漏洞。甚至有可能获得数据库的控制权。
在SQL注入中,攻击者通常尝试输入恶意的SQL 语句,它能在数据库中执行,然后返回不想外露的数据给攻击者。
Fig 46.Sql injection attack example which shows how attack mostly occurs if you are using inline queries.
Simple View which shows User data
View Shows Single Employee data based on EmployeeID as displayed in below Snapshot.
Fig 47.Employee View which displays user data.
Simple View which shows All User data after SQL Injectionattack
In this Browser View as attacker saw Application URL which contains some valuable data which is ID [http://localhost:3837/EmployeeList/index?Id=2] attacker triesSQL Injectionattack as shown below.
Fig 48.Employee View which displays all User data after SQL injection.
After trying permutation & combination of SQL injection attack , theattackergets access to all User data.