65.9K
CodeProject 正在变化。 阅读更多。
Home

MVC 应用程序中的验证和安全

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (8投票s)

2015 年 9 月 6 日

CPOL

13分钟阅读

viewsIcon

23313

MVC 应用程序中的验证和安全

引言

如今,任何应用程序中的验证都至关重要,开发人员在开发任何此类关键和敏感应用程序时都必须保持警惕。黑客无处不在,要阻止他们,限制他们将无意义的数据发布到您的应用程序中。攻击非常脆弱,因此任何应用程序的安全防护都是必不可少的。

security1

应用程序中的安全检查和实现必须保持警惕和活跃,以应对攻击。让我们开始学习我们可以在 MVC 应用程序中拥有的不同类型的验证。

服务器端验证

让我们从简单的服务器端验证开始。当我们向服务器发送数据并期望响应时,通常是在提交表单数据时,就需要进行服务器端验证。表单数据提交通常非常脆弱。攻击者在这里更容易进行攻击。因此,我们需要在服务器端检查我们接收到的数据是否有效。因此,服务器端验证可以在一定程度上防止无意义的输入数据。让我们讨论如何使用视图模型**显式**地进行验证。让我们讨论如何
显式意味着,在用户提交表单后,我们会在服务器端检查输入的数据是否有效,然后将验证消息作为响应回复给用户。
假设我们有一个用于用户注册应用程序的模型。模型如下所示

public class RegistrationViewModel(){
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address1 { get; set; }
    public string Address2 { get; set; }
    public string TelNo { get; set; }
}

因此,在视图/UI 中,用户将看到上述标签和相应的文本框用于输入。应用程序中的 Razor 引擎视图页面看起来像

@model ServerSideValidationDemo.Models.RegistrationViewModel
@{
 ViewBag.Title = "Registration Page";
}

@using (Html.BeginForm())
{
 <fieldset>
 <ul>
 <li>
 @Html.LabelFor(m => m.FirstName)
 @Html.TextBoxFor(m => m.FirstName, new { maxlength = 50 })
 @Html.ValidationMessageFor(m => m.FirstName)
 </li>
 <li>
 @Html.LabelFor(m => m.LastName)
 @Html.PasswordFor(m => m.LastName, new { maxlength = 50 })
 @Html.ValidationMessageFor(m => m.LastName)
 </li>
 <li>
 @Html.LabelFor(m => m.Address1)
 @Html.PasswordFor(m => m.Address1, new { maxlength = 50})
 @Html.ValidationMessageFor(m => m.Address1)
 </li>
 <li>
 @Html.LabelFor(m => m.Address2)
 @Html.TextAreaFor(m => m.Address2, new { maxlength = 200 })
 @Html.ValidationMessageFor(m => m.Address2)
 </li>
 <li>
 @Html.LabelFor(m => m.TelNo)
 @Html.TextBoxFor(m => m.MobileNo, new { maxlength = 10 })
 @Html.ValidationMessageFor(m => m.MobileNo)
 </li>
</ul>
 <input type="submit" value="Submit" />
 </fieldset>
}

因此,上面的片段将为用户带来 UI,用户可以在其中提交输入并单击submit。在 Razor 视图页面中,您可以看到 HTML 助手ValidationMessageFor。此助手会在各自的model属性处显示服务器验证后作为响应返回的验证消息。例如,我们希望用户将Model属性中的名字作为必填项,那么在验证后,助手将在名字文本框旁边显示验证消息
现在让我们看一下Submit单击后会调用的Action代码片段。

[HttpPost]
public ActionResult UserRegistration(RegistrationViewModel registerModel){
     if (string.IsNullOrEmpty(registerModel.FirstName))
         {
             ModelState.AddModelError("FirstName", "Please enter your first name");
         }
     if (!string.IsNullOrEmpty(registerModel.TelNo))
         { 
             Regex telNoRegex= new Regex("^9\d{9}$");
             if (!telNoRegex.IsMatch(registerModel.TelNo))
             ModelState.AddModelError("TelNo", "Please enter correct format of Telephone Number");
         }
     if(ModelState.IsValid){
         return View("Sucess");  //Returns user to success page
         }
      else {
         return View();          //Returns user to the same page back again
        }
 }

在解释上面的代码片段之前,让我们先了解一下在Submit单击后它是如何被调用的。
@using (Html.BeginForm()),即使不指定Action和控制器,它也能做到。这实际上会在内部调用当前 URL 的Post方法,即查找当前 URL 的相应操作名称上的HttpPost属性。因此,通过这种方式,将调用UserRegistration的 post 方法,并且它还会将所需的视图模型发布到操作,从而获取用户输入的数值。

在调用Action结果方法后,会显式地检查属性。在这里,我们检查用户是否输入了名字。如果用户跳过了名字文本框并提交,那么我们将向用户返回一条验证消息,提示“请输入您的名字”。此验证检查在用户添加名字之前不会让用户提交输入。同样,电话号码也使用给定的正则表达式(针对印度电话号码)进行了验证。
以上就是关于显式验证的所有内容。

现在,由于我们正在开发 MVC 应用程序,它提供了预定义的属性,可用于验证我们的发布数据。这些属性称为数据注解属性。让我们看看它们的使用方法如下
数据注解的使用可以广泛进行,以避免控制器 post 操作过载,并显式检查每个属性。视图模型中的数据注解属性如下所示

public class RegistrationViewModel(){
    [Required]
    [Display(Name = "First name")]
    [StringLength(50, ErrorMessage = 
          "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address1 { get; set; }
    public string Address2 { get; set; }
    [Required(ErrorMessage = "Please Enter Telephone Number")]
    [Display(Name = "Tele Phone")]
    [RegularExpression(""^9\d{9}$"", ErrorMessage = "Please Enter Correct format of Telephone No.")]
    public string TelNo { get; set; }
}

上面的视图模型使用了数据注解属性,并提供了所有必需的属性验证。让我们逐一讨论

因此,在这里,视图页面将与上面相同的页面一样。当我们提交时,调用的操作 post 方法将不同,即代码量会少得多。让我们看一下下面的操作

[HttpPost]
 public ActionResult UserRegistration(RegistrationViewModel registerModel){
    if (ModelState.IsValid)
      {
         return View("Success");//Return to the success 
      }
    else
      {
         return View();         //Return back to the same view 
      }
 }

因此,数据注解在这里使得它非常简单易用。
接下来是另一个安全漏洞,即**跨站请求伪造攻击**,可以使用简单的 Fiddler 轻松攻击。当我们发布任何数据时,我们可以轻松地操纵一个用户使用 fiddler 发布的数据,并将其注入应用程序,甚至可以将其搞砸。这确实非常危险。让我们看看如何防止跨站请求伪造攻击。

防止跨站请求伪造攻击

在 MVC 应用程序中,在提交表单数据时,如果理解得当,可以很容易地防止此类请求。MVC 在相应的操作上提供了[ValidatAntiForgeryToken]属性。让我们先看看代码片段中的流程。
首先,我们需要在 Razor 视图页面中放置AntiForgeryToken助手,如下所示

@using(Html.Form("UserRegistration", "Register")) { 
    @Html.AntiForgeryToken() 
    //Then here normal rest form as mentioned above    
 }

然后,在“Register”控制器中,为“UserRegistration”(POST)操作添加如下属性

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult UserRegistration(RegistrationViewModel registerModel){
   //ModeState Valid check and rest code goes here
}

好的,我们已经看到了需要做的两个简单步骤。现在让我们理解它是如何完成的。当我们不放置这些属性时会发生什么?我们的控制器有多脆弱,攻击者会在多大程度上影响应用程序。假设我们正在一个编辑页面上,用户试图编辑他的某些登录信息。现在,来自第三方域的攻击者会托管一个简单的 HTML,该 HTML 会将一些信息发布到用户原本要访问的同一个编辑操作。然后,如果用户不小心导航到攻击者设置的 HTML 页面,用户就会在不知不顾中向服务器发布不需要的数据,并正常保存到数据库。在这里,攻击者可能会用自己的信息替换电子邮件地址或任何其他敏感信息,并检索用户的数据。轰!这真是崩溃,或者说是垃圾!

因此,我们需要做的是,检查发送到服务器操作的请求是否来自用户登录的同一个域!为此,我们需要一些标头或属性,在发出请求时会被映射,如果匹配,则允许发布,否则让身份验证失败。

这实际上就是该属性的作用。ValidateAntiForgeryToken实际上会向传入请求设置一个名为__RequestVerificationToken的 cookie,并且在用户登录的域上也会设置相同的__RequestVerificationToken。因此,当来自同一域的请求发出时,请求标头 cookie 应该匹配并允许用户发布数据,但当来自第三方域的请求发出时,请求将被拒绝,因为__RequestVerificationToken将永远不匹配,身份验证失败,从而防止任何攻击者的跨站请求伪造。

另一个容易受到此类攻击并可能破坏应用程序安全的概念是**SQL 注入**。让我们简要讨论一下。:)

SQL 注入攻击和预防技术

SQL 注入攻击到底是什么? thinkzoo
SQL 注入是一种愚弄和操纵应用程序数据库的攻击。这是通过用户在提交方法中的恶意输入实现的,如果发布的数据在执行为 SQL 查询之前未经验证。这确实非常危险,它会让攻击者获取所有敏感数据,甚至删除所有表中的记录,截断它们,只需发布一个简单的查询而不是实际数据。
在此,攻击者的目标是将他们的查询发布到应用程序中,让服务器运行并给他响应,如果服务器端没有处理的话。让我们看看如何
假设我们有一个 SQL 查询来选择房屋名称并显示。查询会是

var sqlTobeExecuted = "SELECT HouseID, HouseName"+
    "FROM House " +
    "WHERE Address LIKE '" + searchInputParam +"%';
SqlDataAdapter da = new SqlDataAdapter(sqlTobeExecuted , DbCommand);

上面的查询不是参数化的,而是用于获取房屋详细信息的简单string格式的查询。现在假设攻击者将searchInputParam(它最初来自用户的textbox输入)发布为

‘ UNION SELECT id,name FROM sysobjects;–

注意,当它被传递给string查询后,语句会变成

SELECT HouseID,HouseName FROM House
WHERE Address LIKE ” UNION SELECT id,name FROM sysobjects;–%’

searchInputParam中的第一个单引号关闭了 SQL 查询中的Like参数,双破折号“--”注释掉了其余的查询。因此,这将列出所有HouseNames以及数据库中存在的所有表。他们还可以获取sysObjectid,并使用Id检索他们想要的数据库表的列名。假设有一个名为Users的表。显然,该表将包含所有用户详细信息。因此,攻击者可以使用id和表名,通过以下查询检索Users表的列名

‘ UNION SELECT name FROM syscolumns WHERE id = “USERID”;–

因此,整个数据库可以在一次点击中暴露给恶意用户。为了防止这种情况……

身份验证与授权

这两者在任何应用程序中都非常非常非常……重要,而且它们是两个完全不同的概念,但都用于解决同一件事,即安全性。当我们开发安全应用程序时,登录至关重要。因此,正确地对用户进行应用程序身份验证和授权用户访问应用程序的特定部分是具有挑战性的。通常,表单身份验证在 MVC 应用程序中实现。在web.config文件中,设置了以下配置

<authentication mode="Forms" /><forms loginUrl="~/Account/Login" timeout="2880" /></authentication>

这还不能设置身份验证。您需要进行更多设置。这涉及到大量的配置。MVC 中的WebSecurity使实现简单安全。它提供了表和哈希。它提供的哈希是单向的,非常安全。您可以从以下链接了解更多关于在 MVC 中实现 Web 安全的信息
MVC 中的 Web 安全.

因此,在设置了此身份验证之后,重要的事情是授权,可以在控制器级别、操作级别提供,可以自定义以检查访问级别以及会话。只有Authorize属性才能检查会话,我们可以自定义以检查屏幕的角色和访问级别。

[Authorize]
 public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    }

在上面的代码片段中,整个控制器都已授权。也就是说,控制器中的每个方法都将首先被授权。

public class HomeController : Controller
    {
      public ActionResult Index()
        {
            return View();
        }
       [Authorize]
       public ActionResult GetHome(){
         return View()
       }
    }

这里,只有GetHome操作方法被授权,而不是整个控制器。
因此,身份验证和授权是非常重要的因素,应该加以考虑。

MVC 中的更多安全注意事项

根据 OWASP 安全要点,建议隐藏我们使用的 MVC 版本以及 ASP.NET 版本,切勿通过标头公开版本。

X-AspNet-Version 4.0.30319
X-AspNetMvc-Version 5.0

我们需要隐藏出现在开发者表的网络选项卡中的版本。
让我们了解如何从标头中删除 ASP.NET 和 ASP.NET MVC 的版本。

  • Required:此属性强制用户输入该特定属性的值然后提交表单,否则会显示“名字是必填的。”。请注意,上一个消息将是默认错误消息,而对于电话号码,将显示自定义错误消息。
  • Display(Name = ):此属性设置模型属性的标签。我们只需要指定
    @Html.LabelFor(m=>m.TeleNo)。这将显示属性的指定自定义标签,在此将显示“电话”。
  • RegularExpression:此属性非常有用,尤其是在我们有电子邮件地址、电话号码和表单中特定密码表达式的属性时。我们只需在属性中指定表达式,它就会验证用户的输入,并指出是否有效,否则显示错误消息。
    • **加密**关键和敏感数据,如密码、信用卡信息和其他详细信息,以便在某种程度上,即使他们设法获取了详细信息,也无法解密。使用单向哈希加密记录数据。
    • **使用参数化查询**而不是string,以避免直接将用户输入的值注入查询,正如我们在上一节中所看到的。参数化查询将防止恶意输入,它看起来如下
      var commandToBeExecuted = "SELECT HouseID, HouseName FROM House"+
          "WHERE Address  Like @Address";
      SqlCommand cmd = new SqlCommand(commandToBeExecuted , conn);
      cmd.Parameters.Add("@Address",address);

      正如我们在上面的查询中看到的,我们避免直接传递string输入,而是使用 SQL 查询中的参数,这将防止此类攻击。

    • **使用参数化存储过程**,这也可以防止并被证明是此类注入恶意攻击的好解决方案。此外,建议不要完全/盲目地信任它们,因此最好检查用户输入中的敏感数据,然后执行存储过程。这可以在开发人员认为存在漏洞的可能性时完成。
    • **Entity Framework & LINQ**,这里值得注意的是,在使用 LINQ to entity 时,查询生成不使用基于string的方法,而是使用对象模型 API,因此不容易受到 SQL 注入攻击。
    • ASP.NET 版本:要隐藏 ASP.NET 的 X 版本,我们使用下面的Web.Config更改。
      <system.web>
          <httpRuntime enableVersionHeader ="false" />

      上面的设置将隐藏 ASP.NET 版本。

    • ASP.NET MVC 版本:要隐藏 ASP.NET MVC 的 X 版本,我们在Global.asaxApplication_Start方法中使用以下更改。代码片段如下
      protected void Application_Start()
              {
                  MvcHandler.DisableMvcResponseHeader = true;

      这将隐藏网络选项卡中 ASP.NET MVC 版本的标头。

    • 最后,当应用程序发生未处理的异常时,可能会暴露给用户“黄色屏幕死机”。因此,建议有一个自定义错误页面,当发生异常时,用户将在此着陆。让我们看看如何
      Web 配置中的自定义错误需要打开。自定义错误的模式有三种。它们是
      1. On
        • 防止在出现异常时显示的堆栈跟踪
        • 还允许向最终用户显示自定义错误页面
        • 显示给远程客户端和本地用户的自定义错误页面
      2. 关闭
        • 使最终用户能够查看异常的描述以及完整的堆栈跟踪
        • 显示给远程和本地客户端的 ASP.NET 错误/异常和堆栈跟踪
      3. 仅远程
        • 从开发人员的角度来看,这是最好的,因为它允许远程客户端查看自定义错误消息/页面。
        • 允许本地用户/特别是开发人员查看 ASP.NET 错误
        • 这是默认值。

      用于自定义错误元素的另一个属性是defaultredirect。当发生异常时,它用于将用户重定向到默认页面。

      <customerrors defaultredirect="/Error/index" mode="">
      <error statuscode="400" redirect="NotFound.htm">
      <error statuscode="500" redirect="InternalServerError.htm">

      还可以使用以下代码片段在全局、应用程序级别处理异常

      protected void Application_Error(Object sender, EventArgs e)
      {
          Exception ex = Server.GetLastError();  //self explanatory gets the most recent error
          Server.ClearError();  //self explanatory clears the error 
          //(Required to clear as otherwise user gets to see the default ASP.NET error handlers)
          Response.Redirect(""); //default redirect. 
      }

      有关详细信息,您可以访问:自定义错误

结论

因此,安全性和验证是如今任何应用程序都必须实现的重要功能。据《福布斯》报道,一天有 30,000 个网站被黑。这真是一个惊人的数字。在当今世界,大部分敏感信息都存储在云端。因此,在任何应用程序中,数据都有很大可能被暴露和黑客攻击。所以,安全是一个主要问题,需要非常小心地处理。

希望以上讨论了一些主要观点,并可以在任何应用程序中实现,以避免任何形式的泄露。

参考文献

我怎么能错过我学习的参考资料,并获得了与世界分享的机会。

历史

  • 2015 年 9 月 6 日:初始版本
© . All rights reserved.