JavaScript や HTML5 を安全に使うため、気を付けるべきポイントについて書いています。
目次
1. 基礎知識
発生しやすい脆弱性
- DOM based XSS (Cross-Site Scripting)
- Stored DOM based XSS (Cross-Site Scripting)
- window.localStorage を経由する, etc.
- Client-side Open Redirect (クライアント側のオープンリダイレクト)
- CSRF (クロスサイト・リクエスト・フォージェリ)
- アクセス制御や認可制御の欠落
- Ajaxデータの漏えい
- etc.
信頼できない値はどこからくるのか?(ソース)
- windowオブジェクトやdocumentオブジェクトのプロパティ
- コンテンツをロードした際のURIの構成要素
- document.URL *
- document.location.pathname *
- document.location.search *
- document.location.hash
- document.location.href *
- コンテンツのURIが書かれていたリンク元ページのURI
- document.referrer *
- Cookie
- document.cookie
- ウィンドウ間/フレーム間の値受渡しに使われる変数
- window.name
- postMessage(arg) 受信側の関数引数 f(arg) のarg.data
- etc.
- コンテンツをロードした際のURIの構成要素
- input fields
- Web Storage
- IndexedDB
- JSON services
- XMLHttpRequest.responseText
- etc.
※ * がついたプロパティは、URLエンコードされた値がセットされる。
信頼できない値が出力されうるところ(シンク)
(1) 信頼できない第三者スクリプトを含むおそれのあるコンテンツのロード
- iframe.src = html-uri
- script.src = script-uri
- etc.
(2) 文字列がコードとして実行されるところ
信頼できない文字列を使って以下の処理を実行させないこと。
- eval(string)
- execScript (IE10まで?)
- new Function(string)
- setTimeout(string, time)
- setInterval(string, time)
- location.replace/location.assign
- script.innerHTML = code-string
- scriptタグのsrc属性値を編集する全ての処理
- イベントハンドラを編集する全ての処理
- Functionコンストラクタ
- jQuery関連
- jQuery()
- $()
- $.html()
- etc.
(3) オープンリダイレクトに使用されうるところ
- location.href
- location.assign()
(4) <script>タグが効力をもつ文脈への値の出力
- document.write(text)
参考
- OWASP Top 10 for JavaScript – A2: Cross Site Scripting – XSS
いろいろなエスケープ処理
HTMLエスケープ
- 「DOM based XSS (Cross-Site Scripting) 対策」の項目を参照。
URLエンコードの例
GETパラメータ値に対するエンコード
- RFC 3986 に従ったエンコードを行う。
// encodeURIComponent関数を使用する(コロンやスラッシュも変換してくれる) var param_value = encodeURIComponent(param_value);
- より厳密に RFC 3986 に従ったエンコードを行う場合は、以下の関数を用意して使用する。
function fixedEncodeURIComponent (str) { return encodeURIComponent(str).replace(/[!'()*]/g, function(c) { return '%' + c.charCodeAt(0).toString(16); }); } var param_value = fixedEncodeURIComponent(param_value);
参照元
POSTパラメータ値に対するエンコード
- W3C による application/x-www-form-urlencoded の仕様に従う。
// encodeURIComponent関数の適用に加えて、%20 を + に変換する var param_value = encodeURIComponent(param_value).replace(/%20/g, '+');
文字列リテラルのエスケープシーケンス
- 以下の Table 4 にエスケープが必要な記号が書いてあるので、これに従ってエスケープする。
- 7 Lexical Conventions # Ⓣ Ⓔ ① Ⓐ — Annotated ES5
<script>タグの中に直接 JavaScriptコードを書く場合
- 文字列リテラル内に “
</script
” という文字列を記述する場合、”<\/script
” と記述する必要がある。
参考
URLに関する処理
- 特定の URLについて何か処理したい場合は、大した処理ではないと思うような場合であっても、URL()コンストラクタを使って URLオブジェクトを生成し、そのオブジェクトの各プロパティを使うのが無難である。(例えば、URLは判定するような処理だとか、相対URLを絶対URLにする処理をしたい場合)
- 但し、IEはこの機能が未サポートなので、以下のような polyfill を使う。
- パラメータ部分の処理には、URLSearchParams()コンストラクタ を使う。
URL()コンストラクタの使い方
処理対象となるURL文字列を URLオブジェクトにしたい場合、以下のように書けばよい。
処理対象となるURL文字列
は、相対URLでもよいし、”//” から始まるURL文字列であってもよい。もちろん絶対URLでもよい。
var url_string = 処理対象となるURL文字列; // どんな形式のURLがセットされているか分からない。
var url_base = "http://www.example.com"; // ベースとなる絶対URL, 通常は現在のURL(location.href や document.baseURI)をセットすればよい。
var urlObject = new URL( url_string, url_base );
console.log( urlObject.href ); // hrefプロパティで 絶対URLが取得できます。
参考
URLのスキーマやドメインをチェックする処理
- 相対URLも絶対URLに変換してからドメインをチェックする。
- URL()コンストラクタを使う。
URLのパース処理
URLをパースする処理を書く場合は、自前のコードで行うのではなく、ブラウザの機能(URLオブジェクトや a要素オブジェクト)を使う。- URL()コンストラクタを使う。
URLSearchParams()コンストラクタの使用例
以下は、URLSearchParams() – Web APIs | MDN からの引用です(コメントは書き換えています)。
// url.search をコンストラクタに渡し、URLSearchParamsのオブジェクトを生成します
const url = new URL('https://example.com?foo=1&bar=2');
const params1 = new URLSearchParams(url.search);
// URLオブジェクトから直接 URLSearchParams を取得します
const params1a = url.searchParams
// 文字列から URLSearchParamsオブジェクトを生成します
const params2 = new URLSearchParams("foo=1&bar=2");
const params2a = new URLSearchParams("?foo=1&bar=2");
// 配列から URLSearchParamsオブジェクトを生成します
const params3 = new URLSearchParams([["foo", "1"], ["bar", "2"]]);
// 連想配列から URLSearchParamsオブジェクトを生成します
const params4 = new URLSearchParams({"foo": "1", "bar": "2"});
参考
2. オープンリダイレクト
対策
- 遷移先のURLを固定リストで持ち、この中からURLを取り出して使用する。
- 遷移先URLを生成する際、先頭に自サイトのドメインを付加する。
- Chrome,FirefoxではURLオブジェクトを利用してオリジンを確認する。
参考
- PHPデベロッパーのためのJavaScriptセキュリティ入門
3. DOM based XSS (Cross-Site Scripting)
ウェブブラウザ側において、JavaScript コードが生成した DOM を HTMLレンダリングする際に問題(XSS)が発生する脆弱性。
そのため、ウェブサーバーとウェブブラウザ間のHTTP(S)通信内容を単純に読み取るだけでは、問題を検知することはできない。
HTMLを生成するメソッド・処理
- .innerHTML, .outerHTML に文字列をセットする。
- createElement
- document.write/document.writeln
- jQuery’s selector, $()
- jQuery’s .html() メソッド
jQueryでHTMLエスケープされない出力メソッド
以下のメソッドに渡す文字列は事前にHTMLエスケープ処理が必要である。
- .after()
- .append()
- .appendTo()
- .before()
- .html()
- .insertAfter()
- .insertBefore()
- .prepend()
- .prependTo()
- .replaceAll()
- .replaceWith()
- .unwrap()
- .wrap()
- .wrapAll()
- .wrapInner()
- .prepend()
- etc.
参考
- OWASP Top 10 for JavaScript – A2: Cross Site Scripting – XSS
対策
- HTMLの要素を生成する時は、DOM操作用のメソッドやプロパティを使用する。
- createElement(), createTextNode(), appendChild(), insertBefore(), setAttribute(), etc.
テキストノードをDOM APIを介して操作することで安全にHTMLを生成
var div = document.getElementById("msg"); var text = document.createTextNode( some_text ); div.appendChild( text );
DOM APIを経由して属性値を設定する
var f = document.getElementById("form"); var elm = document.createElement( "input" ); elm.setAttribute( "type", "text" ); elm.setAttribute( "value", some_text ); f.appendChild( elm );
textContentを使う例
document.querySelector('#foo').textContent = 信用できない文字列;
- コンテキスト(文脈)に応じたエスケープ処理を行う。
- URLを指定する属性値は、http あるいは https に限定する。
// URL が"http://"または"https://"で始まっている場合のみ処理する var urlObj = new URL( URL文字列, ベースとなる絶対URL文字列 ); if (urlObj.protocol.match(/^https?:/)){ var elm = document.getElementById("link"); var text = document.createTextNode(urlObj.href); elm.appendChild(text); elm.setAttribute("href", urlObj.href); }
- JavaScriptライブラリの問題の場合は、ライブラリをアップデートする。
- jQueryを使う場合、デフォルトでは、.html()ではなく .text() を使う(こちらだとHTMLエスケープしてくれる)。
- underscore.js を使う場合、デフォルトでは <%= %> ではなく <%- %> を使う(こちらだとHTMLエスケープしてくれる)。
- 使用しているテンプレートフレームワークのエスケープ処理を確認する。(必要な文字全てにエスケープがされているか?)
- フレームワークのセキュリティ機能ではデフォルト設定を使用する。
- 正しい Content-Types を指定し、適切な JSONエスケープを行う。
- evalのような危険な関数に信用できない値を渡さないようにする。
- jQueryのようなAPIで信用できない値を使用する場合は気を付ける。テストを行う。
- XSS攻撃を伴った自動テストを行う。悪意を持ったデータを使って、コードとテンプレートの自動テストを行う。
- 複雑なコンテキストには、jQuery encoder を使う。
- 未検証
- Content Security Policy を利用する。
ウェブサーバから取得したHTML文字列を使用したい場合の対策
- 接続するサーバを限定する。
- そのために、接続先URLの生成処理で対策を行う。方法は「オープンリダイレクト」の対策と同じ。
クライアント側で HTML文字列を生成して表示する場合の対策
方法1: HTML文字列をサニタイズしてくれるライブラリを使う
例
方法2: HTMLをパースし、生成されたDOMツリーから許可した要素のみ取り出すコードを書く
以下のページにコード例が載っています。
サーバーからJSONデータを返す場合の注意点
- サーバー側
- Content-Type ヘッダを指定する。
Conetnt-Type: application/json; charset=utf-8
- X-Content-Type-Options ヘッダを指定し、IEにコンテンツスニッフィングをやめさせる。
- 但し、IE7までは効かない。
X-Content-Type-Options: nosniff
- Content-Type ヘッダを指定する。
- クライアント側
- 受け取った文字列から、JSON.parse()を使ってJSONを生成する。
サーバー側で出力された文字列内に、JavaScriptコードとして実行されてしまう文字が含まれている場合
例えば Vue.js では、{{
と }}
で囲まれた文字列を JavaScriptコードとして実行してしまう。
参考
対策その1
data
属性(通常、ここならJavaScriptコードとして実行されないため)にHTMLエスケープした文字列を出力しておき、クライアント側で JavaScriptを使ってその文字列を取得し目的の位置に出力する。
対策その2
- JavaScriptを実行する仕組みを持ったライブラリ(例えば Vue.js)が持っているHTMLエスケープ手段を利用する。もしくは、そのライブラリのJavaScript実行機能を無効にする。
HTMLエスケープ方法
- jQueryのtextメソッドを使う。
- HTMLエスケープ関数を自作する。HTMLエスケープ関数の例1
function escapeHTML(s) { return s.replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """ ) .replace(/'/g, "'" ); }
HTMLエスケープ関数の例2
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 if(str[i] === '&') { out += '&'; } else { out += str[i]; } } return out; }
参考
- OWASP Top 10 for JavaScript – A2: Cross Site Scripting – XSS
4. HTML5関連
WebSocket
- JavaScriptにおける双方向通信機能
- ws:, wss: というスキームを使用する
セキュリティ
- 受信したデータは信用しない。チェックする。
- 重要な情報はTLS(wss://)を使用する。
var ws = new WebSocket("wss://example.com/");
- Cookie
- secure属性がない場合
- http://example.com/ で発行されたCookieは、
- https://example.com/
- ws://example.com:8080/websocket
- wss://example.com:8081/websocket
- などで共有される
- http://example.com/ で発行されたCookieは、
- secure属性がセットされている場合
- https://example.com/ で発行されたCookieは、
- wss://example.com:8081/websocket
- と共有される
- https://example.com/ で発行されたCookieは、
- secure属性がない場合
- どのサーバーにでもWebSocketの接続を行うことができ、HTTPリクエストは偽造できてしまうため、何らかの認証処理を独自に追加する必要がある(しかも、Cookieまで送られてしまう)。
Web Messaging (Cross Document Messaging) (XDM) (window.postMessage())
- ウィンドウ間でメッセージのやり取りを行う機能
- 異なるオリジンの間で通信が可能
セキュリティ
- 受信側
- 送信側のオリジンをチェックすること。
- 受信したデータをバリデーションすること。
Web Storage
- 同一生成元が一致しないとデータにアクセスできない。
- IEは port を無視するらしい。
- IE8は更にschemeも無視するらしい。
Web Storage の種類
- sessionStorage
- ウィンドウ、タブ単位のみ有効なストレージ
- 一時的なデータの保存に利用される。
- localStorage
- データを永続的に保存するストレージ
セキュリティ
- 機密情報を保存しないこと。
- 永続させる必要がないデータは sessionStorage に保存する。
- データが必要なくなったら、ちゃんと削除する(特にlocalStorage)。
- ログアウト時など
- ユーザー別にデータを保存し、そのデータを他のユーザーと共有しない場合、意図しないアクセスができないようにする。
- setItemメソッドの keyパラメータに、ユーザーIDを含める。
- 未ログイン状態のアクセス時には、データを削除するようにする。
- JavaScriptからの読取りを防ぐ仕組みがない。そのような場合は、CSPなどを使う。
- 1つのオリジンで複数のアプリケーションをホストしないこと(データが共有されてしまう可能性があるため)。
参考
Web Database (Client-side databases)
以下の2種類がある。
- Indexed Database
- key-value型
- JavaScriptのオブジェクトを保存する
- Web SQL Database
- リレーショナルデータベース
セキュリティ
- 機密情報は格納しないこと。
- 格納されているデータが安全であると仮定してはいけない。
Web Workers
- バックグラウンドでJavaScriptを動かす仕組み。
セキュリティ
- 意図しないJavaScriptコードが実行されないように注意する。
- メイン処理側から受信したデータは信用しない。チェックする。
- Data URL Scheme が使える?
- new Worker, importScripts
XMLHttpRequestによるクロスドメイン通信
- サーバー側でCORS(Cross-Origin Resource Sharing)の仕様を実装しておくことにより、クロスドメイン通信できる。
- クロスドメイン通信を許可するドメインを制限することができる。
セキュリティ
- 認証の仕組みではないことに注意する。認証する場合は、別の仕組みが必要である。
- CRSF(クロスサイト・リクエスト・フォージェリ)への対策は、トークンを使った通常の方法で問題ない。
参考
- Cross-Origin Resource Sharing
- CORS(Cross-Origin Resource Sharing)について整理してみた | Developers.IO
Canvasタグの crossorigin属性によるクロスドメイン通信
- 「XMLHttpRequestによるクロスドメイン通信」と同様
オフライン Webアプリケーション
- オフラインでもアプリケーションが使えるように、リソースをキャッシュする機能
セキュリティ
- 重要な情報を扱う場合は、HTTPSを利用する。
5. HTTPレスポンスヘッダーでのセキュリティ対策
- CSP(Content Security Policy)
- X-Frame-Options
- Clickjackingを防ぐ。
- X-Content-Type-Options
- ウェブブラウザ (IE) にコンテンツスニッフィングをやめさせる。
- X-XSS-Protection(XSS Filter)
6. メモ
- コンテキストに応じたエンコーディング(エスケープ)を行う。
- 文字列からコードを生成しないこと。
7. 参考
- OWASP Top 10 for JavaScript – A2: Cross Site Scripting – XSS
- http://erlend.oftedal.no/blog/?blogid=127
- 更新日付:2012-05-30 23:31
- 重要
- XSS (Cross Site Scripting) Prevention Cheat Sheet – OWASP
- DOM based XSS Prevention Cheat Sheet – OWASP
- writings/javascript-security-cheat-sheet.md at master · eoftedal/writings
- Client-side JavaScript Vulnerabilities
- Land of Magical Cats: An overview of DOM XSS
- http://sec.omar.li/2012/05/overview-of-dom-xss.html
- 日付:2012-05-26
- IPA 独立行政法人 情報処理推進機構:IPAテクニカルウォッチ 『DOM Based XSS』に関するレポート
- IPA 独立行政法人 情報処理推進機構:情報セキュリティ技術動向調査(2011 年上期)
- HTML5のセキュリティ もうちょい詳しく- HTML5セキュリティその3 : JavaScript API
- Webアプリ開発者のためのHTML5セキュリティ入門
- HTML5 を利用したWeb アプリケーションのセキュリティ問題に関する調査報告書
- https://www.jpcert.or.jp/research/html5.html
- 日付:2014-07-30
- HTML5 Security Cheat Sheet – OWASP
- PHPデベロッパーのためのJavaScriptセキュリティ入門
- http://utf-8.jp/public/2016/0521/phpconfuk.pdf
- 日付:2016-05-21
- 実例に学ぶXSS脆弱性の発見と修正方法/line_dm 16 20160916 how to find and fix xss // Speaker Deck
- JavaScriptセキュリティの基礎知識:連載|gihyo.jp … 技術評論社
- http://gihyo.jp/dev/serial/01/javascript-security
- 日付:2016-06-14 から
関連
こちらの記事もご覧ください。
[…] laboradian.com […]
[…] ラボラジアン外部リンク 9 Users 39 PocketsJavaScript とHTML5のセキュリティ対策https://laboradian.com/sec-js-html5/JavaScript や HTML5 を安全に使うため、気を付けるべきポイントについて書いていま […]