この文書は2016年以降更新されていません
DOM ベース XSS 対策チートシート
最終改訂日 (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 & 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 パーサー) とはまったく対照的です。
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 エンコードを使用して、開発者が HTML エンコードが持つ安全化という性質一般に、HTML エンコードは、HTML コンテキストや HTML 属性コンテキスト内にある HTML タグを無害化する機能を果たします。 実際に機能する例 (HTML エンコードなし) を次に示します。
<a href=”…” >
通常にエンコードされた例を次に示します (機能しません)。
<a href=… >
JavaScript エンコードされた値との根本的な違いを強調するために HTML エンコードされた例を次に示します (機能しません)。
<a href=…>
もしも HTML エンコードが JavaScript エンコードと同じセマンティクスに従うならば、上記の行は機能してリンクをレンダリングする可能性があります。この違いから、JavaScript エンコードは XSS からの防御において HTML エンコードほど有効な手段とはなりません。 ルール 4: 信頼できないデータを実行コンテキスト内の CSS 属性サブコンテキストに挿入する前に JavaScript エスケープする通常、JavaScript を CSS コンテキストから実行するには、CSS 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 コード内では、信頼できないデータをコードやマークアップとしては絶対に扱わないでください。 var x = “<%=encodedJavaScriptData%>”;
3.動的インターフェイスの構築には、
5.JavaScript コード内の信頼できないデータのデータフローを把握してください。上記のメソッドを使用する必要がある場合は、信頼できないデータを必ず HTML エンコードしてから、JavaScript エンコードします (Stefano Di Paola)。 エンクロージャの使用 (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); }
実装の重要な注意事項として、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 まで待つことです。 次に例を示します。
function escapeHTML(str) { str = str + ""; var out = ""; for(var i=0; i<str.length; i++) { if(str[i] === '<') { out += '<'; } else if(str[i] === '>') { out += '>'; } else if(str[i] === "'") { out += '''; } else if(str[i] === '"') { out += '"'; } else { out += str[i]; } } return out; }
6.動的な信用できないデータの使用は、右側の演算だけに限定してください。また、アプリケーションに渡される可能性のあるデータで、コードらしきものには注意してください ( 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”;
上のコードを記述した開発者は 9.JavaScript API が危険にさらされにくくするために、JavaScript を ECMAScript 5 キャノピまたはサンドボックス内で実行します (Gareth Heyes、John Stevens)。 10.JSON を DOM ベースの XSS 軽減に関連する一般的な問題複雑なコンテキスト多くの場合、コンテキストは必ずしも簡単に識別できるとは限りません。 <a href=”javascript:myFunction(‘<%=untrustedData%>’, 'test');”>Click Me</a> ... <script> Function myFunction (url,name) { window.location = url; } </script>
上の例では、信頼できないデータがレンダリング 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>
エンコードライブラリの不整合オープンソースのエンコードライブラリはいくつも存在します。
ブラックリストに基づいて動作するものもあれば、"<" や ">" などの重要な文字を無視するものもあります。ESAPI は、ホワイトリストに基づいて動作し、英数字以外の文字をすべてエンコードする数少ないライブラリの 1 つです。 それぞれのコンテキストで脆弱性を悪用するためにどの文字が使用できるかを熟知したエンコードライブラリを使用することが大切です。必要とされる適切なエンコードについて誤解が蔓延しています。 エンコードに関する誤解セキュリティに関する多くのトレーニングカリキュラムや論文では、XSS を解消するためにともかく HTML エンコードを使用するよう勧めています。JavaScript パーサーは HTML エンコードを理解できないので、これは論理的に賢明なアドバイスのように思えます。しかし、Web アプリケーションが返すページで、コンテンツタイプの "text/xhtml" かファイルタイプ拡張子の "*.xhtml" を使用している場合、HTML エンコードが XSS の軽減に役立たないことがあります。 次に例を示します。
<script> alert(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 EditorsJim Manico - jim[at]owasp.org |
Other CheatsheetsDeveloper Cheat Sheets (Builder)
Assessment Cheat Sheets (Breaker)
Mobile Cheat Sheets OpSec Cheat Sheets (Defender) Draft Cheat Sheets
|