ASP.NET Core でクロスサイトスクリプティング (XSS) を防止するPrevent Cross-Site Scripting (XSS) in ASP.NET Core

作成者: Rick AndersonBy Rick Anderson

クロスサイトスクリプティング (XSS) はセキュリティの脆弱性であり、攻撃者がクライアント側のスクリプト (通常は JavaScript) を web ページに配置できるようにします。Cross-Site Scripting (XSS) is a security vulnerability which enables an attacker to place client side scripts (usually JavaScript) into web pages. 他のユーザーが影響を受けたページを読み込むと、攻撃者のスクリプトが実行され、攻撃者が s およびセッショントークンを盗むことができ cookie ます。また、DOM 操作を通じて web ページの内容を変更したり、ブラウザーを別のページにリダイレクトしたりすることができます。When other users load affected pages the attacker's scripts will run, enabling the attacker to steal cookies and session tokens, change the contents of the web page through DOM manipulation or redirect the browser to another page. XSS 脆弱性は一般的に、アプリケーションがユーザー入力を受け取り、それを検証、エンコード、またはエスケープせずにページに出力するときに発生します。XSS vulnerabilities generally occur when an application takes user input and outputs it to a page without validating, encoding or escaping it.

XSS からアプリケーションを保護するProtecting your application against XSS

基本的なレベルの XSS では、アプリケーションを欺いして、 <script> レンダリングされたページにタグを挿入したり、イベントを要素に挿入したりし On* ます。At a basic level XSS works by tricking your application into inserting a <script> tag into your rendered page, or by inserting an On* event into an element. 開発者は、次の防止手順を使用して、アプリケーションに XSS が導入されないようにする必要があります。Developers should use the following prevention steps to avoid introducing XSS into their application.

  1. 次の手順を実行しない限り、信頼されていないデータを HTML 入力に入れないでください。Never put untrusted data into your HTML input, unless you follow the rest of the steps below. 信頼されていないデータとは、攻撃者によって制御される可能性があるすべてのデータのことです。攻撃者としてデータベースからデータをソース化したとしても、アプリケーションを侵害できない場合でも、データベースを侵害する可能性があります。Untrusted data is any data that may be controlled by an attacker, HTML form inputs, query strings, HTTP headers, even data sourced from a database as an attacker may be able to breach your database even if they cannot breach your application.

  2. HTML 要素内に信頼できないデータを配置する前に、html がエンコードされていることを確認します。Before putting untrusted data inside an HTML element ensure it's HTML encoded. HTML エンコーディングはなど < の文字を受け取り、lt; のように安全な形式に変更し & ます。HTML encoding takes characters such as < and changes them into a safe form like &lt;

  3. 信頼できないデータを HTML 属性に配置する前に、HTML がエンコードされていることを確認します。Before putting untrusted data into an HTML attribute ensure it's HTML encoded. HTML 属性のエンコードは、HTML エンコーディングのスーパーセットであり、"and" などの追加の文字をエンコードします。HTML attribute encoding is a superset of HTML encoding and encodes additional characters such as " and '.

  4. 信頼されていないデータを JavaScript に配置する前に、実行時に取得する内容を持つ HTML 要素にデータを配置します。Before putting untrusted data into JavaScript place the data in an HTML element whose contents you retrieve at runtime. これが不可能な場合は、データが JavaScript でエンコードされていることを確認します。If this isn't possible, then ensure the data is JavaScript encoded. Javascript エンコードでは、JavaScript では危険な文字が使用され、その16進数で置き換えら < れます。たとえば、としてエンコードさ \u003C れます。JavaScript encoding takes dangerous characters for JavaScript and replaces them with their hex, for example < would be encoded as \u003C.

  5. 信頼されていないデータを URL クエリ文字列に含める前に、URL がエンコードされていることを確認してください。Before putting untrusted data into a URL query string ensure it's URL encoded.

を使用した HTML エンコード RazorHTML Encoding using Razor

RazorMVC で使用されるエンジンでは、そのような処理を回避することが難しい場合を除き、変数からのすべての出力ソースが自動的にエンコードされます。The Razor engine used in MVC automatically encodes all output sourced from variables, unless you work really hard to prevent it doing so. ディレクティブを使用すると、常に HTML 属性のエンコード規則が使用さ @ れます。It uses HTML attribute encoding rules whenever you use the @ directive. Html 属性のエンコードは HTML エンコーディングのスーパーセットなので、HTML エンコーディングと HTML 属性のどちらを使用する必要があるかについては、それを気にする必要はありません。As HTML attribute encoding is a superset of HTML encoding this means you don't have to concern yourself with whether you should use HTML encoding or HTML attribute encoding. 信頼されていない入力を JavaScript に直接挿入しようとする場合は、HTML コンテキストでのみ @ を使用する必要があります。You must ensure that you only use @ in an HTML context, not when attempting to insert untrusted input directly into JavaScript. タグヘルパーでは、タグパラメーターで使用する入力もエンコードされます。Tag helpers will also encode input you use in tag parameters.

次のビューを実行し Razor ます。Take the following Razor view:

@{
       var untrustedInput = "<\"123\">";
   }

   @untrustedInput

このビューは、 Untrustedinput 変数の内容を出力します。This view outputs the contents of the untrustedInput variable. この変数には、XSS 攻撃で使用されるいくつかの文字が含まれてい < > ます。This variable includes some characters which are used in XSS attacks, namely <, " and >. ソースを調べると、次のようにエンコードされた出力が表示されます。Examining the source shows the rendered output encoded as:

&lt;&quot;123&quot;&gt;

警告

MVC ASP.NET Core は、 HtmlString 出力時に自動的にエンコードされないクラスを提供します。ASP.NET Core MVC provides an HtmlString class which isn't automatically encoded upon output. これは XSS 脆弱性を公開するため、信頼されていない入力と組み合わせて使用しないでください。This should never be used in combination with untrusted input as this will expose an XSS vulnerability.

を使用した JavaScript のエンコード RazorJavaScript Encoding using Razor

JavaScript に値を挿入して、ビューで処理することが必要になる場合があります。There may be times you want to insert a value into JavaScript to process in your view. これには、2 つの方法があります。There are two ways to do this. 値を挿入する最も安全な方法は、タグのデータ属性に値を配置し、JavaScript で値を取得することです。The safest way to insert values is to place the value in a data attribute of a tag and retrieve it in your JavaScript. たとえば、次のように入力します。For example:

@{
    var untrustedInput = "<script>alert(1)</script>";
}

<div id="injectedData"
     data-untrustedinput="@untrustedInput" />

<div id="scriptedWrite" />
<div id="scriptedWrite-html5" />

<script>
    var injectedData = document.getElementById("injectedData");

    // All clients
    var clientSideUntrustedInputOldStyle =
        injectedData.getAttribute("data-untrustedinput");

    // HTML 5 clients only
    var clientSideUntrustedInputHtml5 =
        injectedData.dataset.untrustedinput;

    // Put the injected, untrusted data into the scriptedWrite div tag.
    // Do NOT use document.write() on dynamically generated data as it
    // can lead to XSS.

    document.getElementById("scriptedWrite").innerText += clientSideUntrustedInputOldStyle;

    // Or you can use createElement() to dynamically create document elements
    // This time we're using textContent to ensure the data is properly encoded.
    var x = document.createElement("div");
    x.textContent = clientSideUntrustedInputHtml5;
    document.body.appendChild(x);

    // You can also use createTextNode on an element to ensure data is properly encoded.
    var y = document.createElement("div");
    y.appendChild(document.createTextNode(clientSideUntrustedInputHtml5));
    document.body.appendChild(y);

</script>

上記のマークアップでは、次の HTML が生成されます。The preceding markup generates the following HTML:

<div id="injectedData"
     data-untrustedinput="&lt;script&gt;alert(1)&lt;/script&gt;" />

<div id="scriptedWrite" />
<div id="scriptedWrite-html5" />

<script>
    var injectedData = document.getElementById("injectedData");

    // All clients
    var clientSideUntrustedInputOldStyle =
        injectedData.getAttribute("data-untrustedinput");

    // HTML 5 clients only
    var clientSideUntrustedInputHtml5 =
        injectedData.dataset.untrustedinput;

    // Put the injected, untrusted data into the scriptedWrite div tag.
    // Do NOT use document.write() on dynamically generated data as it can
    // lead to XSS.

    document.getElementById("scriptedWrite").innerText += clientSideUntrustedInputOldStyle;

    // Or you can use createElement() to dynamically create document elements
    // This time we're using textContent to ensure the data is properly encoded.
    var x = document.createElement("div");
    x.textContent = clientSideUntrustedInputHtml5;
    document.body.appendChild(x);

    // You can also use createTextNode on an element to ensure data is properly encoded.
    var y = document.createElement("div");
    y.appendChild(document.createTextNode(clientSideUntrustedInputHtml5));
    document.body.appendChild(y);

</script>

上記のコードでは、次の出力が生成されます。The preceding code generates the following output:

<script>alert(1)</script>
<script>alert(1)</script>
<script>alert(1)</script>

警告

JavaScript で信頼されていない入力を連結し DOM 要素を作成したり、 document.write() 動的に生成されたコンテンツで使用したりしないでください。Do NOT concatenate untrusted input in JavaScript to create DOM elements or use document.write() on dynamically generated content.

コードが DOM ベースの XSS に公開されないようにするには、次のいずれかの方法を使用します。Use one of the following approaches to prevent code from being exposed to DOM-based XSS:

  • createElement() また、またはノードなどの適切なメソッドまたはプロパティを使用してプロパティ値を割り当て node.textContent= ます。InnerText = '。createElement() and assign property values with appropriate methods or properties such as node.textContent= or node.InnerText=`.
  • document.CreateTextNode() 適切な DOM の場所に追加します。document.CreateTextNode() and append it in the appropriate DOM location.
  • element.SetAttribute()
  • element[attribute]=

コード内のエンコーダーへのアクセスAccessing encoders in code

HTML、JavaScript、および URL エンコーダーは、次の2つの方法でコードで使用できます。 依存関係の挿入 を使用して挿入することも、名前空間に含まれる既定のエンコーダーを使用することもでき System.Text.Encodings.Web ます。The HTML, JavaScript and URL encoders are available to your code in two ways, you can inject them via dependency injection or you can use the default encoders contained in the System.Text.Encodings.Web namespace. 既定のエンコーダーを使用する場合、安全として扱う文字範囲に適用したものは有効になりません。既定のエンコーダーでは、可能な限り安全なエンコーディング規則が使用されます。If you use the default encoders then any you applied to character ranges to be treated as safe won't take effect - the default encoders use the safest encoding rules possible.

DI を使用して構成可能なエンコーダーを使用するには、コンストラクターが htmlencoderJavaScriptEncoder 、および urlencoder パラメーターを必要に応じて受け取る必要があります。To use the configurable encoders via DI your constructors should take an HtmlEncoder, JavaScriptEncoder and UrlEncoder parameter as appropriate. 次に例を示します。For example;

public class HomeController : Controller
   {
       HtmlEncoder _htmlEncoder;
       JavaScriptEncoder _javaScriptEncoder;
       UrlEncoder _urlEncoder;

       public HomeController(HtmlEncoder htmlEncoder,
                             JavaScriptEncoder javascriptEncoder,
                             UrlEncoder urlEncoder)
       {
           _htmlEncoder = htmlEncoder;
           _javaScriptEncoder = javascriptEncoder;
           _urlEncoder = urlEncoder;
       }
   }

エンコード URL パラメーターEncoding URL Parameters

信頼できない入力を含む URL クエリ文字列を値として作成する場合は、を使用して UrlEncoder 値をエンコードします。If you want to build a URL query string with untrusted input as a value use the UrlEncoder to encode the value. たとえば、オブジェクトに適用されたFor example,

var example = "\"Quoted Value with spaces and &\"";
   var encodedValue = _urlEncoder.Encode(example);

エンコード後、エンコード値の変数にはが含まれ %22Quoted%20Value%20with%20spaces%20and%20%26%22 ます。After encoding the encodedValue variable will contain %22Quoted%20Value%20with%20spaces%20and%20%26%22. スペース、引用符、句読点、およびその他の安全でない文字は、16進数値にパーセントでエンコードされます。たとえば、スペース文字は %20 になります。Spaces, quotes, punctuation and other unsafe characters will be percent encoded to their hexadecimal value, for example a space character will become %20.

警告

URL パスの一部として信頼されていない入力は使用しないでください。Don't use untrusted input as part of a URL path. 常に信頼できない入力をクエリ文字列値として渡します。Always pass untrusted input as a query string value.

エンコーダーのカスタマイズCustomizing the Encoders

既定では、エンコーダーは基本的なラテン Unicode 範囲に限定されたセーフリストを使用し、その範囲外のすべての文字を同等の文字コードとしてエンコードします。By default encoders use a safe list limited to the Basic Latin Unicode range and encode all characters outside of that range as their character code equivalents. この動作は、 Razor エンコーダーを使用して文字列を出力するため、taghelper と HtmlHelper のレンダリングにも影響します。This behavior also affects Razor TagHelper and HtmlHelper rendering as it will use the encoders to output your strings.

この理由は、未知のブラウザーバグや今後のブラウザーバグから保護するためのものです (以前のブラウザーのバグは、英語以外の文字の処理に基づいて解析されました)。The reasoning behind this is to protect against unknown or future browser bugs (previous browser bugs have tripped up parsing based on the processing of non-English characters). Web サイトで、中国語、キリル語などのラテン文字以外の文字を頻繁に使用する場合、これは必要な動作ではないと思います。If your web site makes heavy use of non-Latin characters, such as Chinese, Cyrillic or others this is probably not the behavior you want.

エンコーダーセーフリストをカスタマイズして、起動時にアプリケーションに適した Unicode 範囲をに含めることができ ConfigureServices() ます。You can customize the encoder safe lists to include Unicode ranges appropriate to your application during startup, in ConfigureServices().

たとえば、既定の構成を使用する場合は、 Razor 次のように htmlhelper を使用します。For example, using the default configuration you might use a Razor HtmlHelper like so;

<p>This link text is in Chinese: @Html.ActionLink("汉语/漢語", "Index")</p>

Web ページのソースを表示すると、次のように、中国語のテキストがエンコードされた状態で表示されます。When you view the source of the web page you will see it has been rendered as follows, with the Chinese text encoded;

<p>This link text is in Chinese: <a href="/">&#x6C49;&#x8BED;/&#x6F22;&#x8A9E;</a></p>

エンコーダーによって安全として扱われる文字を拡大するには、のメソッドに次の行を挿入し ConfigureServices() startup.cs ます。To widen the characters treated as safe by the encoder you would insert the following line into the ConfigureServices() method in startup.cs;

services.AddSingleton<HtmlEncoder>(
     HtmlEncoder.Create(allowedRanges: new[] { UnicodeRanges.BasicLatin,
                                               UnicodeRanges.CjkUnifiedIdeographs }));

この例では、セーフリストを拡大して、CjkUnifiedIdeographs という Unicode 範囲を追加しています。This example widens the safe list to include the Unicode Range CjkUnifiedIdeographs. レンダリングされた出力は次のようになります。The rendered output would now become

<p>This link text is in Chinese: <a href="/">汉语/漢語</a></p>

セーフリストの範囲は、言語ではなく、Unicode コードグラフとして指定されます。Safe list ranges are specified as Unicode code charts, not languages. Unicode 規格には、文字を含むグラフを検索するために使用できるコード表の一覧があります。The Unicode standard has a list of code charts you can use to find the chart containing your characters. 各エンコーダー、Html、JavaScript、および Url は、個別に構成する必要があります。Each encoder, Html, JavaScript and Url, must be configured separately.

注意

セーフリストのカスタマイズは、DI によって供給されるエンコーダーにのみ影響します。Customization of the safe list only affects encoders sourced via DI. 既定のを使用してエンコーダーに直接アクセスした場合は System.Text.Encodings.Web.*Encoder.Default 、基本的なラテン only セーフセーフセーフポイントが使用されます。If you directly access an encoder via System.Text.Encodings.Web.*Encoder.Default then the default, Basic Latin only safelist will be used.

エンコードを行う場所Where should encoding take place?

一般に、エンコードは出力時に行われ、エンコードされた値をデータベースに格納しないようにすることをお勧めします。The general accepted practice is that encoding takes place at the point of output and encoded values should never be stored in a database. 出力の時点でエンコードを使用すると、たとえば HTML からクエリ文字列の値まで、データの使用を変更することができます。Encoding at the point of output allows you to change the use of data, for example, from HTML to a query string value. また、検索の前に値をエンコードしなくてもデータを簡単に検索でき、エンコーダーに加えられた変更やバグ修正を利用できます。It also enables you to easily search your data without having to encode values before searching and allows you to take advantage of any changes or bug fixes made to encoders.

XSS 防止手法としての検証Validation as an XSS prevention technique

検証は、XSS 攻撃を制限するうえで役に立つツールです。Validation can be a useful tool in limiting XSS attacks. たとえば、文字0-9 のみを含む数値文字列は、XSS 攻撃をトリガーしません。For example, a numeric string containing only the characters 0-9 won't trigger an XSS attack. ユーザー入力で HTML を受け入れると、検証がより複雑になります。Validation becomes more complicated when accepting HTML in user input. HTML 入力の解析は、不可能な場合には困難です。Parsing HTML input is difficult, if not impossible. Markdown は、埋め込まれた HTML を取り除くパーサーと組み合わせることで、リッチな入力を受け入れるためのより安全な選択肢となります。Markdown, coupled with a parser that strips embedded HTML, is a safer option for accepting rich input. 検証だけに依存しない。Never rely on validation alone. 検証やサニタイズが実行されているかどうかにかかわらず、常に信頼されていない入力を出力の前にエンコードします。Always encode untrusted input before output, no matter what validation or sanitization has been performed.