DOM ベース XSS 対策チートシート

OWASP 作成
ジャンプ先: 移動検索
Cheatsheets-header.jpg

最終改訂日 (yy/mm/dd): 2015/07/27

はじめに

クロスサイトスクリプティング (XSS) は、一般に次の 3 種類に分類されます。 反射型、格納型、および DOM ベースの XSS です。反射型 XSS と格納型 XSS については、XSS 対策チートシートで詳しく取り上げています。このチートシートでは、ドキュメントオブジェクトモデル (DOM) ベースの XSS について説明します。このチートシートは、XSS 対策チートシートの延長であり、その内容の理解を前提としています。

DOM ベースの XSS を理解するには、DOM ベースの XSS と反射型および格納型 XSS との基本的な違いを知る必要があります。最も大きな違いは、攻撃がどこでアプリケーションに挿入されるかです。反射型 XSS と格納型 XSS がサーバー側でのインジェクションの問題であるのに対し、DOM ベースの XSS はクライアント (ブラウザー) 側でのインジェクションの問題です。このコードはいずれもサーバー上で発生します。したがって、XSS に対する不備の種類にかかわらず、XSS からアプリケーションを守るのはアプリケーション所有者の責任です。また、XSS 攻撃は常にブラウザーで実行されます。反射型 / 格納型 XSS との違いは、攻撃がどこでアプリケーションに付加または挿入されるかという点です。反射型 / 格納型 XSS の場合、サーバー側でのリクエスト処理中に攻撃がアプリケーションに挿入され、信頼できない入力が動的に HTML に追加されます。DOM ベースの XSS の場合は、クライアントでの実行時に直接、アプリケーションに攻撃が挿入されます。

ブラウザーは HTML と他の関連コンテンツ (CSS、javascript など) をレンダリングするとき、入力の種類別にさまざまなレンダリングコンテキストを識別し、コンテキストごとに異なるルールに従います。レンダリングコンテキストは、HTML タグとその属性の解析に関連付けられています。レンダリングコンテキストの HTML パーサーが、データをどのようにページに表示、レイアウトするかや、どのように HTML、HTML 属性、URL、CSS の各標準コンテキストに分類するかを指示します。実行コンテキストの JavaScript パーサーまたは VBScript パーサーがスクリプトコードの実行に関連付けられます。各パーサーはスクリプトコードの実行方法について別々のセマンティクスを持っていますが、そのために、さまざまなコンテキストで一貫した、脆弱性を軽減するためのルール作りが難しくなっています。さらに、実行コンテキスト内のサブコンテキスト (HTML、HTML 属性、URL、および CSS) ごとにエンコード値の意味や扱いが異なっていることが、問題をいっそう複雑にしています。

HTML、HTML 属性、URL、および CSS チートシートの各コンテキストは、JavaScript 実行コンテキスト内から到達、設定できるため、この資料ではサブコンテキストと呼ぶことにします。JavaScript コードでは、JavaScript がメインコンテキストですが、攻撃者は適切なタグとコンテキストの終了文字とともに対応する JavaScript DOM メソッドを使用して、他の 4 つのコンテキストに攻撃を仕掛けることができます。

次に、JavaScript コンテキストと HTML サブコンテキストで発生する脆弱性の例を示します。

<script>
var x = ‘<%= taintedVar %>’;
var d = document.createElement(‘div’);
d.innerHTML = x;
document.body.appendChild(d);
</script>

実行コンテキストの個々のサブコンテキストを順番に見ていきましょう。

ルール 1: 信頼できないデータを実行コンテキスト内の HTML サブコンテキストに挿入する前に HTML エスケープし、JavaScript エスケープする

JavaScript 内で HTML コンテンツを直接レンダリングするためのメソッドや属性は複数あります。これらのメソッドは、実行コンテキスト内に HTML サブコンテキストを構成します。これらのメソッドに信頼できない入力が渡されると、XSS 脆弱性が生じるおそれがあります。次に例を示します。

危険な HTML メソッドの例

属性

element.innerHTML = “<HTML> Tags and markup”;
element.outerHTML = “<HTML> Tags and markup”;

メソッド

document.write(“<HTML> Tags and markup”);
document.writeln(“<HTML> Tags and markup”);

ガイドライン

DOM で HTML の動的更新を安全に行うには、次の例のように、信頼できない入力をすべて a) HTML エンコードし、さらに b) JavaScript エンコードすることをお勧めします。

element.innerHTML = “<%=Encoder.encodeForJS(Encoder.encodeForHTML(untrustedData))%>”;
element.outerHTML = “<%=Encoder.encodeForJS(Encoder.encodeForHTML(untrustedData))%>”;

document.write(“<%=Encoder.encodeForJS(Encoder.encodeForHTML(untrustedData))%>”);
document.writeln(“<%=Encoder.encodeForJS(Encoder.encodeForHTML(untrustedData))%>”);

注: Encoder.encodeForHTML() と Encoder.encodeForJS() は単なる概念的なエンコーダーです。実際のエンコーダーの各種オプションについては、このドキュメントで後述します。

ルール 2: 信頼できないデータを実行コンテキスト内の HTML 属性サブコンテキストに挿入する前に JavaScript エスケープする

実行コンテキスト内の HTML 属性サブコンテキストは、標準的なエンコードルールから逸脱しています。これは、HTML 属性から抜け出したり、XSS に導くおそれのある属性を追加したりしようとする攻撃を軽減するためには、HTML 属性レンダリングコンテキスト内で HTML 属性エンコードするルールが必要になるからです。DOM 実行コンテキスト内においては、コードを実行しない HTML 属性 (イベントハンドラー、CSS、URL 属性以外の属性) は JavaScript エンコードするだけでよいのです。

たとえば、HTML 属性内にある信頼できないデータ (データベース、HTTP リクエスト、ユーザー、バックエンドシステムなどからのデータ) は HTML 属性エンコードするのが一般的なルールとなっています。これはレンダリングコンテキスト内でデータを出力するときの措置としては適切ですが、実行コンテキスト内で HTML 属性エンコードを使用すると、アプリケーションのデータ表示が崩れてしまいます。

安全ながら表示が崩れる例

var x = document.createElement(“input”);
x.setAttribute(“name”, “company_name”);
// 次のコード行で companyName は信頼できないユーザー入力を表します。
// Encoder.encodeForHTMLAttr() は不要です。使用すると、二重エンコードになります。
x.setAttribute(“value”, ‘<%=Encoder.encodeForJS(Encoder.encodeForHTMLAttr(companyName))%>’); 
var form1 = document.forms[0];
form1.appendChild(x);

問題は、companyName の値が "Johnson & Johnson" のような場合です。入力テキストフィールドには、"Johnson &amp; Johnson" と表示されてしまいます。このような場合に適しているのは、JavaScript エンコードだけを使用し、攻撃者が一重引用符やインラインコードを閉じたり、HTML にエスケープして新しいスクリプト タブを開始したりできないようにすることです。

完全で機能的にも適切な例

var x = document.createElement(“input”);
x.setAttribute(“name”, “company_name”);
x.setAttribute(“value”, ‘<%=Encoder.encodeForJS(companyName)%>’);
var form1 = document.forms[0];
form1.appendChild(x);

重要な注意事項として、コードを実行しない HTML 属性を設定するときは、HTML 要素の object 属性で値を直接設定します。これにより、インジェクションの懸念を排除できます。

ルール 3: 信頼できないデータを実行コンテキスト内のイベントハンドラーサブコンテキストや JavaScript コードサブコンテキストに挿入するときは注意が必要

JavaScript コードに動的データを挿入するのは特に危険です。JavaScript エンコードされたデータについては、JavaScript エンコードが他のエンコードとは異なるセマンティクスを持つためです。多くの場合、JavaScript エンコードでは実行コンテキスト内での攻撃を阻止できません。たとえば、JavaScript エンコードされた文字列は、JavaScript エンコードされていても実行されます。

したがって、信頼できないデータを実行コンテキストに入れないことを第一にお勧めします。それがどうしても必要な場合のために、いくつか例を挙げて、機能する手法と機能しない手法について説明します。

var x = document.createElement("a");
x.href="#”;
// 下のコード行で、右側のエンコードされたデータ (setAttribute の第 2 引数) は
// 適切に JavaScript エンコードされているのに実行される、信頼できないデータの例です。
x.setAttribute("onclick", "\u0061\u006c\u0065\u0072\u0074\u0028\u0032\u0032\u0029");
var y = document.createTextNode("Click To Test");
x.appendChild(y);
document.body.appendChild(x);

setAttribute(name_string,value_string) メソッドは、 value_string を DOM 属性 name_string のデータ型に暗黙的に変換するため危険です。上記の場合、属性名は JavaScript イベントハンドラーです。したがって、属性値は暗黙的に JavaScript コードに変換されて評価されます。この場合、JavaScript エンコードでは DOM ベースの XSS は軽減されません。他の JavaScript メソッドで、コードを文字列型として取るものは上記と同様の問題をはらんでいます (setTimeout、setInterval、new 関数など)。これは、JavaScript エンコードで XSS が軽減される、HTML タグのイベントハンドラー属性での JavaScript エンコード (HTML パーサー) とはまったく対照的です。

<a id="bb" href="#" onclick="\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0029"> Test Me</a>

Element.setAttribute(...) を使用して DOM 属性を設定する代わりに、属性を直接設定する方法があります。イベントハンドラーの属性を直接設定すると、JavaScript エンコードにより DOM ベースの XSS を軽減できます。ただし、信頼できないデータをコマンド実行コンテキストに入れる設計は常に危険であることに注意してください。

   <a id="bb" href="#"> Test Me</a>

           //次のコードは、イベントハンドラーが文字列に設定されているため機能しません。"alert(7)" が JavaScript エンコードされています。
           document.getElementById("bb").onclick = "\u0061\u006c\u0065\u0072\u0074\u0028\u0037\u0029";
           
           //次のコードは、イベントハンドラーが文字列に設定されているため機能しません。
           document.getElementById("bb").onmouseover = "testIt";
           //次のコードは、"(" と ")" がエンコードされているため機能しません。"alert(77)" が JavaScript エンコードされています。
           document.getElementById("bb").onmouseover = \u0061\u006c\u0065\u0072\u0074\u0028\u0037\u0037\u0029;
           //次のコードは、";" がエンコードされているため機能しません。"testIt;testIt" が JavaScript エンコードされています。
           document.getElementById("bb").onmouseover = \u0074\u0065\u0073\u0074\u0049\u0074\u003b\u0074\u0065\u0073\u0074\u0049\u0074;
     
           //次のコードは、エンコードされた値が有効な変数名か関数参照なので機能します。"testIt" が JavaScript エンコードされています。
           document.getElementById("bb").onmouseover = \u0074\u0065\u0073\u0074\u0049\u0074;
           function testIt() {
               
               alert("I was called.");
           }

JavaScript の中で、JavaScript エンコードが有効な実行可能コードとして認められる場所は他にもあります。

for ( var \u0062=0; \u0062 < 10; \u0062++){
    \u0064\u006f\u0063\u0075\u006d\u0065\u006e\u0074                  
    .\u0077\u0072\u0069\u0074\u0065\u006c\u006e
    ("\u0048\u0065\u006c\u006c\u006f\u0020\u0057\u006f\u0072\u006c\u0064");
}
\u0077\u0069\u006e\u0064\u006f\u0077
.\u0065\u0076\u0061\u006c
\u0064\u006f\u0063\u0075\u006d\u0065\u006e\u0074
.\u0077\u0072\u0069\u0074\u0065(111111111);

または

var s = "\u0065\u0076\u0061\u006c";
var t = "\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0031\u0029";
window[s](t);

JavaScript は国際標準規格 (ECMAScript) に準拠しているため、JavaScript エンコードではプログラミングのコンストラクトや変数のほか、代替文字列の表示 (文字列エスケープ) でも国際文字に対応できます。

しかし、HTML エンコードの場合は正反対です。HTML タグ要素は厳密に定義されており、同じタグの代替表記には対応していません。そのため、HTML エンコードを使用して、開発者が <a> タグなどの代替表記を作成することはできません。

HTML エンコードが持つ安全化という性質

一般に、HTML エンコードは、HTML コンテキストや HTML 属性コンテキスト内にある HTML タグを無害化する機能を果たします。 実際に機能する例 (HTML エンコードなし) を次に示します。

<a href=”…” >

通常にエンコードされた例を次に示します (機能しません)。

&#x3c;a href=… &#x3e;

JavaScript エンコードされた値との根本的な違いを強調するために HTML エンコードされた例を次に示します (機能しません)。

<&#x61; href=…>

もしも HTML エンコードが JavaScript エンコードと同じセマンティクスに従うならば、上記の行は機能してリンクをレンダリングする可能性があります。この違いから、JavaScript エンコードは XSS からの防御において HTML エンコードほど有効な手段とはなりません。

ルール 4: 信頼できないデータを実行コンテキスト内の CSS 属性サブコンテキストに挿入する前に JavaScript エスケープする

通常、JavaScript を CSS コンテキストから実行するには、CSS url() メソッドに javascript:attackCode() を渡すか、直接実行する JavaScript コードを渡して CSS expression() メソッドを呼び出す必要があります。私の経験上、実行コンテキスト (JavaScript) からの expression() 関数の呼び出しは無効になったようです。CSS url() メソッドへの攻撃を軽減するには、CSS url() メソッドに渡すデータを必ず URL エンコードしてください。

document.body.style.backgroundImage = "url(<%=Encoder.encodeForJS(Encoder.encodeForURL(companyName))%>)";

懸案事項: まだ、DOM JavaScript コードから expression() 関数を動作させることができていません。対策が必要です。

ルール 5: 信頼できないデータを実行コンテキスト内の URL 属性サブコンテキストに挿入する前に、URL エスケープし、さらに JavaScript エスケープする

実行コンテキストでもレンダリングコンテキストでも URL を解析するロジックは同じようです。したがって、実行 (DOM) コンテキストにおける URL 属性のエンコードルールの変更点はわずかです。

var x = document.createElement(“a”);
x.setAttribute(“href”, ‘<%=Encoder.encodeForJS(Encoder.encodeForURL(userRelativePath))%>’);
var y = document.createTextElement(“Click Me To Test”);
x.appendChild(y);
document.body.appendChild(x);

このコードでは、完全修飾 URL を使用するとリンクが壊れます。プロトコル識別子 ("http:" または "javascript:") のコロンが URL エンコードされることで、"http" および "javascript" プロトコルの呼び出しが妨げられるからです。

ルール 6: DOM にデータを入力するときには安全な JavaScript 関数またはプロパティを使用する

信頼できないデータを DOM に入力する最も基本的で安全な方法は、安全な代入プロパティである textContent を使用することです。次に、安全な使用方法の例を示します。

<script>
element.textContent = “<%=untrustedData%>”;  //コードを実行しません。
</script>

JavaScript を使用してセキュアなアプリケーションを開発するためのガイドライン

DOM ベースの XSS は攻撃対象領域が広く、ブラウザー間の標準化も行われていないため、攻撃を軽減するのは極めて困難です。以下のガイドラインは、開発者が XSS を防止できるように、Web ベースの JavaScript アプリケーション (Web 2.0) を開発する際の指針を提供することを目的としています。

1.信頼できないデータは、表示可能なテキストとしてのみ扱うことをお勧めします。JavaScript コード内では、信頼できないデータをコードやマークアップとしては絶対に扱わないでください。
2.信頼できないデータをアプリケーションに挿入するときは、必ず JavaScript エンコードし、引用符付き文字列として区切ってください (Jim Manico、Robert Hansen)。

var x = “<%=encodedJavaScriptData%>”;

3.動的インターフェイスの構築には、document.createElement("…")、element.setAttribute("…","value")、element.appendChild(…) などを使用してください。element.setAttribute が安全なのは、限られた数の属性に対してだけであることに注意してください。危険な属性には、onclick、onblur など、コマンド実行コンテキストであるすべての属性が含まれます。安全な属性の例として、align、alink、alt、bgcolor、border、cellpadding、cellspacing、class、color、cols、colspan、coords、dir、face、height、hspace、ismap、lang、marginheight、marginwidth、multiple、nohref、noresize、noshade、nowrap、ref、rel、rev、rows、rowspan、scrolling、shape、span、summary、tabindex、title、usemap、valign、value、vlink、vspace、width などが挙げられます。
4.以下の HTML レンダリングメソッドは使用しないでください。

  1. element.innerHTML = “…”;
  2. element.outerHTML = “…”;
  3. document.write(…);
  4. document.writeln(…);

5.JavaScript コード内の信頼できないデータのデータフローを把握してください。上記のメソッドを使用する必要がある場合は、信頼できないデータを必ず HTML エンコードしてから、JavaScript エンコードします (Stefano Di Paola)。
6.渡されたデータを暗黙的に eval() するメソッドが多数存在します。このようなメソッドに渡す信頼できないデータは、すべて文字列区切り記号で区切り、クロージャで囲むか、用途に応じて N 段階まで JavaScript エンコードし、カスタム関数で囲むよう注意してください。 必ず上記の手順 4 に従って、信頼できないデータをカスタム関数内の危険なメソッドに渡さないようにするか、エンコードのレイヤーを余分に 1 つ追加して信頼できないデータを処理するようにします。

エンクロージャの使用 (Gaz 氏による推奨)

次の例は、クロージャを使用して二重の JavaScript エンコードを回避する方法を示しています。

setTimeout((function(param) { return function() {
         customFunction(param);
         }
})("<%=Encoder.encodeForJS(untrustedData)%>"), y);

他の代替手法として、N 段階のエンコードを使用する方法があります。

N 段階のエンコード

次のようなコードの場合は、入力データを二重に JavaScript エンコードするだけですみます。

setTimeout(“customFunction(‘<%=doubleJavaScriptEncodedData%>’, y)”);
function customFunction (firstName, lastName)
      alert("Hello" + firstName + " " + lastNam);
}

doubleJavaScriptEncodedData では、(実行時に) 復号される JavaScript エンコードの第 1 レイヤーが一重引用符で囲まれています。次に、setTimeout() の暗黙の eval() が JavaScript エンコードの第 2 レイヤーを復号し、正しい値を customFunction に渡します。二重に JavaScript エンコードするだけでよい理由は、customFunction 関数自体は明示的または暗黙的に eval() を呼び出す別のメソッドに入力を渡さないからです。"firstName" を、明示的または暗黙的に eval() を呼び出す別の JavaScript メソッドに渡す場合は、上の <%=doubleJavaScriptEncodedData%><%=tripleJavaScriptEncodedData%> に変える必要があります。

実装の重要な注意事項として、JavaScript コードで二重または三重にエンコードされたデータを文字列比較に使用する場合は、そのデータが if 比較に渡されるまでに evals() を通過した回数と、値が JavaScript エンコードされた回数によって、値の解釈が異なる場合があります。

"A" が二重に JavaScript エンコードされた場合、次の if チェックは偽を返します。

var x = "doubleJavaScriptEncodedA";  //\u005c\u0075\u0030\u0030\u0034\u0031
if (x == "A") {
   alert("x is A");
} else if (x == "\u0041") {
   alert("This is what pops");
}

ここから、興味深い設計のポイントが導き出されます。エンコードを適用して前述の問題を防ぐ適切な方法としては、データがアプリケーションに入力される出力コンテキストについては、サーバー側でエンコードし、信頼できないデータが渡される個々のサブコンテキスト (DOM メソッド) については、クライアント側でエンコードするのが理想的です (ESAPI4JS などの JavaScript エンコード ライブラリを使用)。ESAPI4JS (http://bit.ly/9hRTLH) と jQuery Encoder (https://github.com/chrisisbeef/jquery-encoder/blob/master/src/main/javascript/org/owasp/esapi/jquery/encoder.js) の 2 つは、Chris Schmidt 氏によって開発されたクライアント側エンコードライブラリです。
次に、これらの使用例をいくつか紹介します。

var input = “<%=Encoder.encodeForJS(untrustedData)%>”;  //サーバー側エンコード

window.location = ESAPI4JS.encodeForURL(input);  //URL エンコードが JavaScript 内で実行されます

document.writeln(ESAPI4JS.encodeForHTML(input));  //HTML エンコードが JavaScript 内で実行されます

当グループでよく指摘されることですが、JavaScript ライブラリは攻撃者によって改ざんされる可能性があるため、エンコード用の JavaScript ライブラリを信頼するのは問題があります。選択肢の 1 つは、JavaScript ライブラリが不変プロパティに対応できるように ECMAScript 5 まで待つことです。
Gaz (Gareth) 氏が示すもう 1 つの選択肢は、特定のコードコンストラクトを使用して匿名クロージャで可変性を制限する方法です。

次に例を示します。

function escapeHTML(str) {
     str = str + "";
     var out = "";
     for(var i=0; i<str.length; i++) {
         if(str[i] === '<') {
             out += '&lt;';
         } else if(str[i] === '>') {
             out += '&gt;';
         } else if(str[i] === "'") {
             out += '&#39;'; 
         } else if(str[i] === '"') {
             out += '&quot;';                        
         } else {
             out += str[i];
         }
     }
     return out;                    
}


Chris Schmidt 氏が別の JavaScript エンコーダー実装を http://yet-another-dev.blogspot.com/2011/02/client-side-contextual-encoding-for.html にまとめています。

6.動的な信用できないデータの使用は、右側の演算だけに限定してください。また、アプリケーションに渡される可能性のあるデータで、コードらしきものには注意してください (locationeval() など) 。(Achim)

var x = “<%=properly encoded data for flow%>”;

さまざまなオブジェクト属性をユーザーの入力に応じて変更する場合は、1 段階の間接参照を使用します。

次のようなコードの代わりに、

window[userData] = “moreUserData”;

次のようなコードを使用します。

if (userData===”location”) {
   window.location = “static/path/or/properly/url/encoded/value”;
}

7.DOM で URL エンコードするときは、文字セットの問題に注意してください。JavaScript DOM では文字セットが明確に定義されていません (Mike Samuel)。

8.object[x] アクセサーを使用するときは、プロパティオブジェクトへのアクセスを制限してください(Mike Samuel)。要するに、信頼できない入力と特定のオブジェクトプロパティとの間で 1 段階の間接参照を使用してください。
次に示すのは、型マッピングの使用時に起きる問題の例です。

var myMapType = {};
myMapType[<%=untrustedData%>] = “moreUntrustedData”;

上のコードを記述した開発者は myMapType オブジェクトに追加のキー付き要素を追加しようとしていますが、これは、攻撃者によって myMapType オブジェクトの内部属性や外部属性を改ざんするために利用されるおそれがあります。

9.JavaScript API が危険にさらされにくくするために、JavaScript を ECMAScript 5 キャノピまたはサンドボックス内で実行します (Gareth Heyes、John Stevens)。

10.JSON を eval() してネイティブ JavaScript オブジェクトに変換しないでください。代わりに、JSON.toJSON()JSON.parse() を使用します (Chris Schmidt)。

DOM ベースの XSS 軽減に関連する一般的な問題

複雑なコンテキスト

多くの場合、コンテキストは必ずしも簡単に識別できるとは限りません。

<a href=”javascript:myFunction(‘<%=untrustedData%>’, 'test');”>Click Me</a>
...
<script>
Function myFunction (url,name) {
    window.location = url;
}
</script>

上の例では、信頼できないデータがレンダリング URL コンテキスト (<a> タグの href 属性) 内で始まり、その後、JavaScript 実行コンテキスト (javascript: プロトコルハンドラー) に移行しています。さらに、この JavaScript 実行コンテキストでは、信頼できないデータを実行 URL サブコンテキスト (myFunction の window.location) に渡しています。このデータは JavaScript コードに挿入されて URL サブコンテキストに渡されているので、適切なサーバー側エンコードは次のようになります。

<a href=”javascript:myFunction(‘<%=Encoder.encodeForJS( ↩
              Encoder.encodeForURL(untrustedData))%>’, 'test');”>Click Me</a>
…

または、ECMAScript 5 を不変の JavaScript クライアント側エンコードライブラリとともに使用している場合は、次のように記述できます。


<a href=”javascript:myFunction(‘<%=Encoder.encodeForJS(untrustedData)%>’, 'test');”>Click Me</a>
...
<script>
Function myFunction (url,name) {
    var encodedURL = ESAPI4JS.encodeForURL(url);  //URL encoding using client-side scripts
    window.location = encodedURL;
}
</script>

エンコードライブラリの不整合

オープンソースのエンコードライブラリはいくつも存在します。

  1. ESAPI
  2. Apache Commons String Utils
  3. Jtidy
  4. 各社独自の実装

ブラックリストに基づいて動作するものもあれば、"<" や ">" などの重要な文字を無視するものもあります。ESAPI は、ホワイトリストに基づいて動作し、英数字以外の文字をすべてエンコードする数少ないライブラリの 1 つです。 それぞれのコンテキストで脆弱性を悪用するためにどの文字が使用できるかを熟知したエンコードライブラリを使用することが大切です。必要とされる適切なエンコードについて誤解が蔓延しています。

エンコードに関する誤解

セキュリティに関する多くのトレーニングカリキュラムや論文では、XSS を解消するためにともかく HTML エンコードを使用するよう勧めています。JavaScript パーサーは HTML エンコードを理解できないので、これは論理的に賢明なアドバイスのように思えます。しかし、Web アプリケーションが返すページで、コンテンツタイプの "text/xhtml" かファイルタイプ拡張子の "*.xhtml" を使用している場合、HTML エンコードが XSS の軽減に役立たないことがあります。

次に例を示します。

<script>
&#x61;lert(1);
</script>

上の HTML エンコードされた値は、依然として実行可能です。さらに忘れてはならないのは、DOM 要素の value 属性を使用してエンコードを取得すると、エンコードが失われるという点です。

ページとスクリプトの例を見てみましょう。

<form name=”myForm” …>
  <input type=”text” name=”lName” value=”<%=Encoder.encodeForHTML(last_name)%>”>
…
</form>
<script>
var x = document.myForm.lName.value;  //値の取得時にエンコードが復号化されます
document.writeln(x);  //これで、lName に渡されたどのコードも実行可能です。
</script>

最後に、JavaScript の通常は安全ないくつかのメソッドが、特定のコンテキストでは危険になりえるという問題があります。

通常は安全なメソッド

安全と見なされている属性の 1 つとして、innerText が挙げられます。一部の論文やガイドでは、innerHTML における XSS 攻撃を軽減するために、innerHTML の代替として innerText の使用を推奨しています。しかし、innerText を適用するタグによっては、コードが実行される可能性があるのです。また、innerText は非標準であり、FireFox でサポートされていないことにも注意してください。

<script>
var tag = document.createElement(“script”);
tag.innerText = “<%=untrustedData%>”;  //コードを実行します 
</script>

Authors and Contributing Editors

Jim Manico - jim[at]owasp.org
Abraham Kang - abraham.kang[at]owasp.org
Gareth (Gaz) Heyes
Stefano Di Paola
Achim Hoffmann - achim[at]owasp.org
Robert (RSnake) Hansen
Mario Heiderich
John Steven
Chris (Chris BEEF) Schmidt
Mike Samuel
Jeremy Long
Eduardo (SirDarkCat) Alberto Vela Nava
Jeff Williams - jeff.williams[at]owasp.org
Erlend Oftedal

Other Cheatsheets

Developer Cheat Sheets (Builder)

Assessment Cheat Sheets (Breaker)

Mobile Cheat Sheets

OpSec Cheat Sheets (Defender)

Draft Cheat Sheets