JavaScript インジェクション攻撃を防ぐ (C#)

投稿者: Stephen Walther

JavaScript インジェクション攻撃とクロスサイト スクリプティング攻撃が発生しないようにします。 このチュートリアルでは、Stephen Walther が、コンテンツを HTML エンコードすることで、これらの種類の攻撃を簡単に打破できる方法について説明します。

このチュートリアルの目的は、ASP.NET MVC アプリケーションで JavaScript インジェクション攻撃を防ぐ方法について説明することです。 このチュートリアルでは、JavaScript インジェクション攻撃から Web サイトを保護するための 2 つの方法について説明します。 表示するデータをエンコードして JavaScript インジェクション攻撃を防ぐ方法について説明します。 また、受け入れるデータをエンコードして JavaScript インジェクション攻撃を防ぐ方法についても説明します。

JavaScript インジェクション攻撃とは

ユーザー入力を受け入れて、そのユーザー入力を再表示すると、JavaScript インジェクション攻撃を受けた Web サイトを開くことになります。 JavaScript インジェクション攻撃を受けた実際のアプリケーションを調べてみましょう。

お客様フィードバック Web サイトを作成したとします (図 1 を参照)。 お客様は、Web サイトにアクセスし、製品を使用したエクスペリエンスに関するフィードバックを入力できます。 お客様がフィードバックを送信すると、フィードバック ページにそのフィードバックが再表示されます。

Customer Feedback Website

図 01: お客様フィードバック Web サイト (クリックするとフルサイズの画像が表示されます)

お客様フィードバック Web サイトでは、リスト 1 の controller を使用します。 この controller には、Index()Create() という名前の 2 つのアクションが含まれています。

リスト 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 ビューが表示されます。 このメソッドでは、データベースからフィードバックを取得して (LINQ to SQL query クエリを使用)、前にお客様から寄せられたフィードバックをすべて Index ビューに渡します。

Create() メソッドを使用すると、新しいフィードバック項目が作成されて、データベースに追加されます。 お客様がフォームに入力したメッセージは、メッセージ パラメータの Create() メソッドに渡されます。 フィードバック項目が作成され、メッセージがフィードバック項目の Message プロパティに割り当てられます。 フィードバック項目は、DataContext.SubmitChanges() メソッド呼び出しを使用してデータベースに送信されます。 最後に、訪問者は、すべてのフィードバックが表示される Index ビューにリダイレクトされます。

Index ビューはリスト 2 に含まれています。

リスト 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 ビューには 2 つのセクションがあります。 上部のセクションには、実際のお客様フィードバック フォームが含まれています。 下部のセクションには、前にお客様から寄せられたフィードバック項目をすべてループする For..Each ループが含まれているとともに、各フィードバック項目の EntryDate プロパティと Message プロパティが表示されます。

お客様フィードバック Web サイトは簡易的な Web サイトです。 残念ながら、この Web サイトは、JavaScript インジェクション攻撃を受けやすいです。

お客様フィードバック フォームに次のテキストを入力するとします。

<script>alert("Boo!")</script>

このテキストは、アラート メッセージ ボックスを表示する JavaScript スクリプトを表しています。 他のユーザーがこのスクリプトをフィードバック フォームに送信すると、メッセージ「Boo!」が、別のユーザーが今後お客様フィードバック Web サイトにアクセスするたびに表示されるようになります (図 2 を参照)。

JavaScript Injection

図 02: JavaScript インジェクション (クリックするとフルサイズの画像が表示されます)

JavaScript インジェクション攻撃に対する最初の対応が効いていなかった可能性があります。 JavaScript インジェクション攻撃を、単なる "改ざん" 攻撃の一種であると考えているかもしれません。 JavaScript インジェクション攻撃をはたらいても、大きな悪事を犯すことはできないだろうと思っているかもしれません。

残念ながら、ハッカーは、JavaScript を Web サイトに挿入することで、深刻な悪事を犯すことができます。 JavaScript インジェクション攻撃を使用すると、クロスサイト スクリプティング (XSS) 攻撃を実行できます。 クロスサイト スクリプティング攻撃では、機密のユーザー情報を盗んで別の Web サイトに送信します。

たとえば、ハッカーは、他のユーザーからブラウザーの Cookie の値を盗むために、JavaScript インジェクション攻撃を利用します。 パスワード、クレジット カード番号、社会保障番号などの機密情報がブラウザーの Cookie に格納されていれば、ハッカーは JavaScript インジェクション攻撃を利用してこの情報を盗むことができます。 または、JavaScript 攻撃で侵害されたページに含まれるフォーム フィールドにユーザーが機密情報を入力すると、ハッカーは、挿入された JavaScript を利用してそのフォーム データを取得し、別の Web サイトに送信することができます。

"恐れてください"。 JavaScript インジェクション攻撃を深刻に受け止めて、ユーザーの機密情報を保護してください。 次の 2 つのセクションでは、JavaScript インジェクション攻撃から ASP.NET MVC アプリケーションを保護するために使用できる 2 つの手法について説明します。

アプローチ 1: View で HTML エンコードする

JavaScript インジェクション攻撃を防ぐ 1 つの簡単な方法として、ビューでデータを再表示するときに Web サイト ユーザーが入力したデータを 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;) に置き換えられます。 そのため、文字列 <script>alert("Boo!")</script> が HTML エンコードされると、&lt;script&gt;alert(&quot;Boo!&quot;)&lt;/script&gt; に変換されます。 エンコードされた文字列は、ブラウザーによって解釈されるときに JavaScript スクリプトとして実行されなくなります。 代わりに、図 3 のような無害なページが表示されます。

Defeated JavaScript Attack

図 03: 無効化された JavaScript 攻撃 (クリックするとフルサイズの画像が表示されます)

リスト 3 の Index ビューで feedback.Message の値のみがエンコードされていることに注目してください。 feedback.EntryDate の値はエンコードされません。 行う必要があるのは、ユーザーが入力したデータのエンコードのみです。 EntryDate の値はコントローラー内に生成されるため、この値を HTML エンコードする必要はありません。

アプローチ 2: コントローラーで HTML エンコードする

ビューにデータを表示するときにデータを HTML エンコードする代わりに、データをデータベースに送信する直前にデータを HTML エンコードできます。 この 2 つ目のアプローチは、リスト 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");
          }
     }
}

メッセージの値が、Create() アクション内で値がデータベースに送信される前に HTML エンコードされていることに注目してください。 メッセージがビューに再表示されると、メッセージは HTML エンコードされ、メッセージに挿入された JavaScript は実行されません。

通常は、この 2 つ目のアプローチよりも、このチュートリアルで説明している 1 つ目のアプローチを優先する必要があります。 この 2 つ目のアプローチには、データベース内のデータが HTML エンコードされてしまうという問題があります。 言い換えると、データベースのデータが文字化けしてしまうということです。

なぜこれがいけないのでしょうか? データベースのデータを Web ページ以外に表示する必要がある場合に、問題が生じます。 たとえば、Windows フォーム アプリケーションにデータを簡単に表示することができなくなります。

まとめ

このチュートリアルの目的は、JavaScript インジェクション攻撃によって起こりうる事態を恐れてもらうことでした。 このチュートリアルでは、ASP.NET MVC アプリケーションを JavaScript インジェクション攻撃から保護するためのアプローチとして、ユーザーが送信したデータをビューで HTML エンコードするアプローチと、ユーザーが送信したデータをコントローラーで HTML エンコードするアプローチの 2 つアプローチについて説明しました。