阻止 JavaScript 注入攻击

本教程的目的是解释如何在 ASP.NET MVC 应用程序中阻止 JavaScript 注入攻击。本教程讨论防止网站遭受 JavaScript 注入攻击的两种方法。我们将学习如何通过编码显示的数据防止 JavaScript 注入攻击。我们还将学习如何通过编码接受的内容防止 JavaScript 注入攻击。

什么是 JavaScript 注入攻击?

每当接受用户输入的内容并重新显示这些内容时,网站就很容易遭受 JavaScript 注入攻击。让我们研究一个容易遭受 JavaScript 注入攻击的具体应用程序。

假设已经创建了一个客户反馈网站,如图 1 所示。客户可以访问网站并输入对产品的反馈信息。当客户提交反馈时,反馈信息重新显示在反馈页面上。


 

图 1:客户反馈网站( 单击查看大图)



客户反馈网站使用程序清单 1 中的 controller。此 controller 包含两个名称为 Index()Create() 的操作。

程序清单 1 HomeController.cs


复制代码


using System;using System.Web.Mvc;
using CustomerFeedback.Models;

namespace CustomerFeedback.Controllers
{
     [HandleError]
     public class HomeController : Controller
     {
          private FeedbackDataContext db = new FeedbackDataContext();

          public ActionResult Index()
          {
               return View(db.Feedbacks);
          }

          public ActionResult Create(string message)
          {
               // Add feedback
               var newFeedback = new Feedback();
               newFeedback.Message = message;
               newFeedback.EntryDate = DateTime.Now;
               db.Feedbacks.InsertOnSubmit(newFeedback);
               db.SubmitChanges();

               // Redirect
               return RedirectToAction("Index");
          }
     }
}


Index() 方法显示 Index 视图。此方法将以前所有的客户反馈传递到 Index 视图,方法是使用 LINQ to SQL 查询从数据库检索反馈。

Create() 方法创建新的 Feedback 项并将其添加到数据库。客户输入到表单中的消息将传递到消息参数中的 Create() 方法。创建了一个 Feedback 项,消息被分配给 Feedback 项的 Message 属性。通过调用 DataContext.SubmitChanges() 方法,将 Feedback 项提交到数据库。最后,将访问者重定向到 Index 视图,该视图显示所有的反馈。

程序清单 2 中包含了 Index 视图。

程序清单 2 Index.aspx


复制代码


<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="CustomerFeedback.Views.Home.Index"%><%@ Import Namespace="CustomerFeedback.Models" %>

<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
     <h1>Customer Feedback</h1>
     <p>
          Please use the following form to enter feedback about our product.
     </p>

     <form method="post" action="/Home/Create">
          <label for="message">Message:</label>
          <br />
          <textarea name="message" cols="50" rows="2"></textarea>
          <br /><br />
          <input type="submit" value="Submit Feedback" />
     </form>

     <% foreach (Feedback feedback in ViewData.Model)
     {%>
          <p>
          <%=feedback.EntryDate.ToShortTimeString()%>
          --
          <%=feedback.Message%>
          </p>
     <% }%>

</asp:Content>


Index 视图有两部分。顶部包含实际的客户反馈表单。底部包含一个 For..Each 循环,该循环在所有之前的客户反馈中循环并显示每个反馈项的 EntryDate 和 Message 属性。

客户反馈网站是一个简单的网站。不幸的是,此网站容易遭受 JavaScript 注入攻击。

假设正在将以下文本输入到客户反馈表单中:


复制代码


<script>alert(“Boo!”)</script>


此文本表示显示警告消息框的 JavaScript 脚本。在某人将此脚本提交到客户反馈表单后,消息 Boo! 会在将来任何人访问客户反馈网站时显示,如图 2 所示。


图 2:JavaScript 注入( 单击查看大图)



现在,您对 JavaScript 注入攻击的第一反应也许是不理会。您可能认为 JavaScript 注入攻击不过是一种 无伤大雅 的攻击。您可能还认为别人不会通过 JavaScript 注入攻击搞破坏。

不幸的是,黑客会通过在网站中注入 JavaScript 进行破坏活动。使用 JavaScript 注入攻击可以执行跨站脚本 (XSS) 攻击。在跨站脚本攻击中,可以窃取保密的用户信息并将信息发送到另一个网站。

例如,黑客可以使用 JavaScript 注入攻击窃取来自其他用户浏览器的 Cookies 值。如果将敏感信息(如密码、信用卡帐号或社会保险号码)保存在浏览器 Cookies 中,那么黑客可以使用 JavaScript 注入攻击窃取这些信息。或者,如果用户将敏感信息输入到页面的表单字段中,而页面受到 JavaScript 攻击的危害,那么黑客可以使用注入的 JavaScript 获取表单数据并将其发送到另一个网站。

请高度重视。认真对待 JavaScript 注入攻击并保护用户的保密信息。在接下来的两部分中,我们将讨论防止 ASP.NET MVC 应用程序受到 JavaScript 注入攻击的两种技术。

方法 1:视图中的 HTML 编码

阻止 JavaScript 注入攻击的一种简单方法是重新在视图中显示数据时,用 HTML 编码任何网站用户输入的数据。程序清单 3 中更新的 Index 视图就使用了这种方法。

程序清单 3 Index.aspx(HTML 编码后)


复制代码


<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="CustomerFeedback.Views.Home.Index"%><%@ Import Namespace="CustomerFeedback.Models" %>

<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
     <h1>Customer Feedback</h1>
     <p>
          Please use the following form to enter feedback about our product.
     </p>

     <form method="post" action="/Home/Create">
          <label for="message">Message:</label>
          <br />
          <textarea name="message" cols="50" rows="2"></textarea>
          <br /><br />
          <input type="submit" value="Submit Feedback" />
     </form>

     <% foreach (Feedback feedback in ViewData.Model)
     {%>
          <p>
          <%=feedback.EntryDate.ToShortTimeString()%>
          --
          <%=Html.Encode(feedback.Message)%>
          </p>
     <% }%>

</asp:Content>


请注意,feedback.Message 的值在显示前使用以下代码进行 HTML 编码:


复制代码


<%=Html.Encode(feedback.Message)%>


使用 HTML 编码一个字符串的含意是什么呢?使用 HTML 编码字符串时,危险字符如 <> 被替换为 HTML 实体,如 &lt;&gt;。所以,当使用 HTML 编码字符串 <script>alert("Boo!")</script> 时,它将转换为 &lt;script&gt;alert("Boo!")&lt;/script&gt;。浏览器在解析编码的字符串时不再执行 JavaScript 脚本。而是显示无害的页面,如图 3 所示。


图 3:失败的 JavaScript 攻击(单击查看大图)



请注意,程序清单 3 中的 Index 视图只是 feedback.Message 被编码后的值。feedback.EntryDate 的值没有被编码。只需编码用户输入的数据。因为 EntryDate 的值是控制器生成的,所以不需要使用 HTML 编码此值。

方法 2:控制器中的 HTML 编码

除了在视图中显示数据时使用 HTML 编码数据,还可以在将数据提交到数据库之前使用 HTML 编码数据。第二种方法正是程序清单 4 中 controller 的情况。

程序清单 4 HomeController.cs(HTML 编码后)


复制代码


using System;using System.Web.Mvc;
using CustomerFeedback.Models;
namespace CustomerFeedback.Controllers
{
     [HandleError]
     public class HomeController : Controller
     {
          private FeedbackDataContext db = new FeedbackDataContext();

          public ActionResult Index()
          {
               return View(db.Feedbacks);
          }

          public ActionResult Create(string message)
          {
               // Add feedback
               var newFeedback = new Feedback();
               newFeedback.Message = Server.HtmlEncode(message);
               newFeedback.EntryDate = DateTime.Now;
               db.Feedbacks.InsertOnSubmit(newFeedback);
               db.SubmitChanges();

               // Redirect
               return RedirectToAction("Index");
          }
     }
}


请注意,Message 的值在提交到数据库之前是在 Create() 操作中经过 HTML 编码的。当在视图中重新显示 Message 时,Message 被 HTML 编码,因而不会执行任何注入到 Message 中的 JavaScript。

通常,人们喜欢使用本教程中讨论的第一种方法,而不喜欢使用第二种方法。第二种方法的问题在于在数据库中最终会保留 HTML 编码的数据。换言之,数据库中的数据会包含奇怪的字符。

这有什么坏处呢?如果需要用除网页以外的形式显示数据库数据,则将遇到问题。例如,不能轻易在 Windows Forms 应用程序中显示数据。

总结

本教程的目的是引起读者对 JavaScript 注入攻击的重视。教程讨论了防止 ASP.NET MVC 应用程序遭受 JavaScript 注入攻击的两种方法。可以使用 HTML 编码用户提交到视图中的数据,也可以使用 HTML 编码用户提交到控制器中的数据。