次の方法で共有


JavaScript セキュリティ

Web から Windows 8 へ: セキュリティ

Tim Kulp

 

数年前、ゴルフを習おうと思い立ちました。地元のゴルフ練習場でレッスンを申し込むまで、ゴルフ クラブを手に取ったことすらありませんでした。最初のレッスンで、インストラクターはこれまでにレッスンを受けたりゴルフをしたことはあるかとたずねました。「ありません」と答えると、「それは良かった。スイングの悪い癖を直さなくていいからね」と言われました。

ブラウザーから Windows ストア アプリに移行する際、Web 開発者はそれまでの癖を持ち込みます。Web 開発者はこれまで習得した JavaScript の知識を活かせますが、一部の機能は新しくなっているため、考え方を変える必要があります。大きく変わった機能の 1 つがセキュリティです。多くの Web 開発者には、「JavaScript は簡単に迂回できるから問題ない」といった理由で、アプリケーションのサーバーに対するセキュリティを軽視する傾向があります。また、Web クライアント側のセキュリティ機能は、Web アプリケーションの全体的なセキュリティを強化するものではなく、使いやすさを高めるものだと考えがちです。

Windows 8 では、JavaScript はアプリの全体的なセキュリティにとって重要な役割を果たします。そのため、データのセキュリティを確保し、入力を検証し、悪意がある可能性を持つコンテンツを分離するために必要な仕掛けを用意する必要があります。今回の記事では、Web 開発から持ち込む癖を直し、HTML5、JavaScript、および Windows ランタイムのセキュリティ機能を使用して、よりセキュリティの高い Windows ストア アプリを作成可能にする方法を紹介します。

入力の検証

Web 開発者は、「JavaScript の検証は使いやすくするためのもので、アプリのセキュリティを強化するものではない」と考えがちです。

Windows 8 の開発者であれば、「HTML5 と JavaScript による検証は、悪意のあるコンテンツがアプリに侵入するのを防ぐ最前線の防御だ」と考えてください。

従来の Web アプリにとって、JavaScript は多くの場合サーバーへのゲートウェイにすぎません。入力の検証や保存など、データを扱う重要な操作は、すべてサーバーで行われます。悪意を持った攻撃者は、クライアント側のあらゆる保護を迂回する手製の HTTP 要求を送信し、ブラウザー上でまたは直接 JavaScript を無効にできます。Windows ストア アプリにはサーバーが存在しないため、開発者はデータを操作する前にサーバー上でユーザー入力が検証されることを当てにはできません。入力の検証に関しては、JavaScript と HTML5 を使って独自に行います。

ソフトウェアのセキュリティでは、入力の検証はデータの整合性を確保するための重要な要素です。入力を検証しなければ、攻撃者はあらゆる入力フィールドを Windows ストア アプリへの攻撃の糸口として使用します。『Writing Secure Code 第 2 版 - プログラマのためのセキュリティ対策テクニック』(Microsoft Press、2004 年) の著者、Michael Howard と Steve Lipner は、今では入力の管理の決まり文句になっている「すべての入力は適切だと証明されるまでは有害であると見なしなさい」と記しています。

「正しいデータである」と証明されるまではデータを信用すべきではありません。アプリを構築する際、開発者は特定のフィールドのデータがどのようになっていれば正しいか (許可リスト)、または最低でも何が含まれてはいけないか (拒否リスト) を把握します。入力の検証では、入力を既知の正しいデータに限定できるときは、常に許可リストを使用します。正しいデータのみを許可すれば、正しくないと考えられる未知のデータや未確認のデータを見過ごす可能性が少なくなります。

制限、拒否、および校正

入力を正しいことがわかっているデータに制限すると、ユーザーに対するリスクをどのように軽減できるのでしょう。図 1 に示すように、入力の検証を 3 段階で行い、悪意のあるコンテンツがアプリに侵入するリスクを軽減します。

Input Validation (Image Based on Figure 4.4 from Chapter 4, “Design Guidelines for Secure Web Applications,” of “Improving Web Application Security: Threats and Countermeasures” at bit.ly/emYI5A)
図 1 入力の検証 (この図は、bit.ly/emYI5A の「Web アプリケーション セキュリティ強化: 脅威とその対策」の第 4 章「セキュリティ保護された Web アプリケーションの設計ガイドライン」の図 4.4 に基づきます)

入力の検証では、まず「正しいことがわかっているデータ」に制限します。HTML5 に精通していれば、新しい入力型と属性に関する知識を利用して、Windows ストア アプリに入力されるデータを限定できます。Web と Windows 8 の大きな違いは、Windows ストア アプリには背後で入力を検証するサーバーが存在しないことです。データの制限は、HTML5 または JavaScript で行う必要があります。

HTML5 を使用すると、各フィールドのデータを正しいデータに簡単に制限できます。今回の説明の例として、ユーザーの健康に関する個人情報を格納する架空の Contoso Health アプリを使用します。このアプリのプロファイル ページには、ユーザー名、電子メール アドレス、体重、身長の各情報を入力するフィールドと、汎用情報のメモ フィールドがあります。開発者は、各フィールドの正しいデータが (一般に) 次のようなものであると認識しています。

  • [名前]: 45 文字以内で、いくつか特殊文字を含んだアルファベット文字で構成されます。名前の基準は、このアプリが米国市場をターゲットとすることに基づきます。
  • [電子メール アドレス]: 入力は有効な電子メール アドレス形式でなければなりません。
  • [体重] と [身長]: [体重] はポンド、[身長] はフィート、インチで表される数値データです。
  • [メモ]: 標準の Contoso HTML エディターを使用した HTML コンテンツです。

[名前] フィールドの入力要素では、有効な文字種と値の長さを制限する必要があります。この制限は、pattern と title という input タグの 2 つの新しい属性を使用して行います。

pattern は、入力されるデータが従わなければならない正規表現です。MSHTML (Windows 8 で HTML5 アプリに使用するレンダリング エンジン) は、フィールドに入力されるデータが正規表現と一致しているかどうかを検証します。ユーザーが正規表現のパターンに従わないデータを入力すると、フォームの送信が失敗し、無効なフィールドを修正するようユーザーに求めます。たとえば、[名前] フィールドはアルファベット文字とスペースから構成され、文字の長さは 3 ~ 45 でなければならないとすると、次の pattern 値を使用してこの制限を表します。

<input type="text" id="txtName" name="txtName"
  pattern="^[A-Za-z ]{3,45}$" title="" />

title は、システムが想定していることをユーザーに伝えるために使用します。[名前] フィールドの場合、「名前は 3 ~ 45 文字のアルファベット文字とスペースでなければなりません」といったメッセージを使って想定するパターンを説明することになります。何が有効かわからないで入力が無効であると知らされるほどユーザーを不愉快にすることはありません。何が許可されているかがわかるようにすることがユーザーに親切です。title 属性は、その名のとおり、フィールドに想定している入力を説明するメッセージです。

使用できる文字や長さを含むデータ フィールドのパターンを決めるのは難しい場合があります。正規表現のサンプルは多くの優れたオンライン リソースから見つかりますが、常に組織のセキュリティ チームと相談して、従うべき標準があるかどうかを確認します。セキュリティ チームがないか、セキュリティ チームに標準がない場合は、RegExLib.com (英語) などのリソースに、データ検証に使用できる正規表現のすばらしいライブラリがあります。

フィールドの中には、数値、日時、電子メール アドレスなど具体的なデータ型を持つものもあります。ここでも HTML5 の email、phone、date、number などの新しい input type が役に立ちます。MSHTML はこのようなデータ入力型 (input type) 属性を使用して、正規表現や JavaScript のコードを一切使用しないで、ユーザーが有効なデータを入力したかどうかを確認します。input 要素の type 属性は新しいデータ型を処理します (他の型とその使い方については、bit.ly/OH1xFf (英語) をご覧ください)。たとえば、プロファイル ページ用の電子メール アドレスをチェックするには、次の例のように、type 属性を email 設定します。

<input type="email" id="txtEmail" name="txtEmail" />

このフィールドは、有効な電子メール アドレス形式に従って入力した場合のみ、値を受け入れます。MSHTML が有効な電子メール アドレスだと認識しない場合は、ユーザーがフォームを送信しようとするとフィールドに検証エラーが表示されます。HTML5 の新しい input type 属性を使用すると、複雑な JavaScript 検証に取り組む必要なく、想定しているデータに制限できます。

一部の新しい入力型には、新しい min 属性と max 属性を使用して、範囲を制限することも可能です。一例として、ビジネス規則によってアプリに入力されるユーザーの身長を 3 ~ 8 フィートに制限する必要があるとします。この場合 [身長] フィールドを次のように範囲制限します。

<input type="number" id="txtHeight" name="txtHeight" min="3" max="8" />

ここで示した例では HTML5 の input タグで 4 つの手法を使用してデータを制限しています。長さ (pattern を使用)、形式 (再度 pattern を使用)、データ型 (新しい input type を使用)、および範囲 (min/max を使用) を検証して、正しいことがわかっているデータに制限できます。すべての属性と型が、送信前に修正を促すわけではありません。そこで ASP.NET で Page.IsValid を使用するのと同様に、checkValidity メソッドを使用してフォームのコンテンツを検証するようにします (https://msdn.microsoft.com/ja-jp/library/ie/hh673544(v=vs.85).aspx#dom_methods_input_val)。JavaScript を使うだけでも、このようなデータ制限が可能ではないかと感じるかもしれません。もちろん可能です。ただし、HTML5 の属性を使用すると、複雑な作業をすべて MSHTML エンジンに任せることで、開発者が管理する必要があるコード全体が少なくなります。

"拒否" は、不適切であることがわかっている (つまり、拒否リストの) 入力を却下します。拒否の良い例は、作成する Web アプリに接続できない IP アドレスの拒否リストを作成することです。拒否リストは、ブロックする必要があるなんらかの範囲が決まっている場合に役立ちます。一例として、開発チームなどのグループに電子メールを送信する際に、開発チームの電子メール リストから特定の個人を除外するとします。この例では、開発チームのリストの中で拒否する電子メール アドレスがわかっています。ソフトウェアのセキュリティを確保する場合は、拒否 (拒否リスト) よりも制約 (許可リスト) に重点を置くことをお勧めします。不適切だとわかっているデータは、攻撃者がソフトウェアの防御をかいくぐるために創意工夫を凝らすため、絶えず変化していくことを念頭に置いてください。上記の例でも、新しい開発者が開発チームに加わり、電子メールに含めるべきかどうかを入念に検証する必要がある場合が考えられます。拒否リストに数千も項目を含めるのに比べると、長期的に見て制約の方がずっと管理しやすく、メンテナンスが容易なリストになります。

データには正しいことがわかっているものと不適切だとわかっているものが両方含まれている場合があります。その例の 1 つが HTML コンテンツです。タグには表示が認められるものと認められないものがあります。不適切であることがわかっているデータをフィルターで取り除いたり無効にしたりして、認められたデータを許可するプロセスを入力の校正と呼びます。Contoso Health アプリの [メモ] フィールドがこの好例です。ユーザーは HTML エディターを使用して HTML タグを入力できますが、アプリに入力が表示されるときに特定の HTML タグのみがレンダリングされます。入力の校正は、悪意があるかもしれないデータを受け取り、その中から安全でないコンテンツを除外し、明確に認められていない部分をレンダリングしないようにして安全にします。Windows ストア アプリは、(innerHTML の代わりに) innerText を使用して HTML 要素の値を設定し、HTML コンテンツを HTML として解釈する代わりにテキストとしてレンダリングすることでこれを行います (アプリで script タグの innerText に JavaScript を設定すると、実行可能なスクリプトが生成されます)。JavaScript は、toStaticHTML という校正のための別の使いやすいツールも提供します。

以下にプロファイル ページの btnSave_Click ハンドラーのサンプル コードを示します。

function btnSave_Click(args) {
  var taintedNotes = document.getElementById("txtNotes").value;
  var sanitizedNotes = window.toStaticHTML(taintedNotes);
  document.getElementById("output").innerHTML = sanitizedNotes;
}

ユーザーが次の文字列を

<strong>testing!</strong><script>alert("123! ");</script>

txtNotes に入力すると、window.toStaticHTML メソッドは script タグを取り除いて、表示が認められる strong タグのみを残します。toStaticHTML を使用すると承認済みのセーフ リスト (許可リストのもう 1 つの使用例) にないすべてのタグと未知の属性を取り除きます。toStaticHTML メソッドの出力には、正しいことがわかっているデータのみが残ります。承認済みのタグ、属性、CSS 規則、およびプロパティの完全な一覧については、https://msdn.microsoft.com/ja-jp/library/windows/apps/hh465388.aspx を参照してください。

入力検証は、悪意のあるコンテンツがシステムに侵入するリスクを軽減します。HTML5 と toStaticHTML を使用すると、サーバーが介入しなくても、アプリは入力を正しいデータに制限し、悪意があるかもしれないコンテンツを削除または無効にできます。

これで Contoso Health は有効なデータを取得するようになりましたが、医療情報や金融情報などの機密データはどのように扱えばよいでしょうか。

機密データ ストレージ

Web 開発者は、「ストレージはセキュリティを確保できないためクライアントに機密データを格納してはいけない」と考えます。

Windows 8 の開発者であれば、「機密データは Windows ランタイムで暗号化して安全に格納できる」と考えてください。

ここまでは、Contoso Health アプリで基本的なプロファイル情報を取得しました。開発を続けていると、ビジネス スポンサーから病歴フォームが要求されました。このフォームには、最近の検診など、ユーザーがこれまで行ってきた医療行為が格納されます。Web 開発の古いルールによれば、ユーザーの病歴などの機密情報をクライアントに保存することはデータが漏えいする可能性があるため、適切とは言えません。Windows ストア アプリ開発では、Windows ランタイムのセキュリティ機能を使用して機密情報をローカルに保存できます。

ユーザーの病歴を保護するため、Contoso Health アプリでは WinRT データ保護 API を使用します。暗号化だけをデータ保護の対策にすることは望ましくありません (暗号化だけを使用するといった単一防御ではなく、セキュリティを階層化する多層防御を検討してください)。必要なときにだけデータにアクセスする、機密データをキャッシュ外で管理するなど、機密データに関する他のベスト プラクティスも検討します。機密データの多くの考慮事項を一覧にした優れたリソースの 1 つは、MSDN ライブラリの記事「Web アプリケーション セキュリティ強化: 脅威とその対策」(https://msdn.microsoft.com/ja-jp/library/aa302418.aspx) です。このドキュメントは Web 開発のベスト プラクティスに重点を置いていますが、あらゆる種類の開発に応用できるすばらしい基礎知識を多数提供しています。

Contoso Health アプリの病歴ページには、btnAddItem というボタンを用意します。ユーザーが btnAddItem をクリックすると、アプリは病歴フォームに入力したデータを暗号化します。病歴情報を暗号化するには、アプリは組み込みの WinRT データ保護 API を使用します。シンプルな暗号化システムにより、開発者はキー管理のオーバーヘッドを感じることなく、データをすばやく暗号化できます。btnAddItem クリック イベント用の空のイベント ハンドラーから始めます。Contoso Health ではフォームの情報を集め、JSON オブジェクトに格納します。このイベント ハンドラーに、JSON オブジェクトをすばやく構築するため次のコードを追加します。

var healthItem = {
  "prop1": window.toStaticHTML(document.getElementById("txt1").value),
  "prop2": window.toStaticHTML(document.getElementById("txt2").value)
};

healthItem オブジェクトは、ユーザーがフォームに入力した病歴記録を表します。healthItem の暗号化では、まず DataProtectionProvider のインスタンスを作成します。

var dataProtectionProvider =
  Windows.Security.Cryptography.DataProtection.DataProtectionProvider(
  "LOCAL=user");

DataProtectionProvider コンストラクター (暗号化用) は、データ保護を何に関連付けるかを決める引数文字列を受け取ります。この場合、コンテンツをローカル ユーザーに対して暗号化します。ローカル ユーザーを対象に設定する代わりに、コンピューター、Web 資格情報のセット、Active Directory セキュリティ プリンシパルなどいくつかの設定対象があります。保護記述子のオプションの一覧については、デベロッパー センター トピック「保護記述子」(bit.ly/QONGdG、英語) を参照してください。使用する保護記述子は、アプリの要件によって変わります。これで、データ保護プロバイダーはデータを暗号化する準備ができましたが、データは少し変更が必要です。暗号化アルゴリズムは JSON ではなくバッファーを使用し、次の手順で healthItem をバッファーとしてキャストします。

var buffer =
  Windows.Security.Cryptography.CryptographicBuffer.convertStringToBinary(
    JSON.stringify(healthItem),
    Windows.Security.Cryptography.BinaryStringEncoding.utf8);

CryptographicBuffer には、暗号化と暗号化解除で使用するバッファーを操作する多くのオブジェクトやメソッドがあります。これらの最初のメソッドは convertStringToBinary で、文字列 (この場合、JSON オブジェクトの文字列版) を受け取り、エンコードしたバッファーに変換します。使用するエンコードは、Windows.Security.Cryptography.BinaryStringEncoding オブジェクトで設定します。今回の例では、データのエンコードとして UTF8 を使用します。convertStringToBinary メソッドは、文字列データと指定されたエンコードに基づいてバッファーを返します。バッファー暗号化の準備ができ、データ保護プロバイダーのインスタンスを作成したら、protectAsync メソッドを呼び出してバッファーを暗号化します。

dataProtectionProvider.protectAsync(buffer).then(
  function (encryptedBuffer) {
     SaveBufferToFile(encryptedBuffer);
});

encryptedBuffer 引数は、protectAsync メソッドの出力として暗号化されたバッファーを含みます。つまり、ストレージへの格納に使用できる暗号化されたデータを含みます。ここから、encryptedBuffer は SaveBufferToFile メソッドに渡し、暗号化したデータをアプリのローカル フォルダーのファイルに書き込みます。

healthItem の暗号化は、3 行のコードに分類できます。

  1. データ保護プロバイダーのインスタンスを作成する
  2. データをバッファーに変換する
  3. protectAsync を呼び出して、データを暗号化する

データの暗号化解除も、同様にシンプルです。変更点は、DataProtectionProvider の空のコンストラクターを使用して、protectAsync メソッドの代わりに unprotectAsync メソッドを使用することだけです。GetBufferFromFile メソッドは、SaveBufferToFile メソッドで作成したファイルの encryptedBuffer 変数を読み込みます。

function btnLoadItem_Click(args) {
  var dataProtectionProvider =
    Windows.Security.Cryptography.DataProtection.DataProtectionProvider();
  var encryptedBuffer = GetBufferFromFile();
  dataProtectionProvider.unprotectAsync(encryptedBuffer).then(
    function (decryptedBuffer) {
      // TODO: Work with decrypted data
    });
}

開発者は、WinRT 以外の JavaScript で暗号化を使用できるでしょうか。もちろんできます。それは、優れたデータ保護を提供する 3 行のコードと同じくらい簡単でしょうか。いいえ。ブラウザーでの暗号化のベスト プラクティスには、秘密キーを秘密に保つ、高水準の暗号化に必要なアルゴリズムのファイル サイズを管理するなど、多くの課題があります。WinRT データ保護 API と Windows.Security.Cryptography 名前空間で提供されている他の暗号化ツールは、データの保護を簡略化します。Windows ランタイムのセキュリティ機能を使用して、開発者は暗号化キーを管理しやすく保ったまま、機密データを Windows ストア アプリに安心して保存できます。

ローカル コンテキストと Web コンテキストの違い

Web 開発者は、「Web アプリは、スクリプトを呼び出すアプリと同じ場所から外部スクリプト参照を実行する」ことを考えます。

Windows 8 開発者であれば、「Windows ストア アプリは、ローカルのアプリ パッケージと外部スクリプト参照を切り離す」ことを考えてください。

Web 2.0 には、自身のサイトや他人のサイト (マッシュアップを通じて) から、またはユーザー操作で利用できる、熟練の開発者が開発したコンテンツがあります。Web では、コンテンツは実際だれでも無料で利用できるため、開発者はスクリプト参照とサードパーティの API データを使用します。コンテンツ配信ネットワーク (CDN) と、Bing Maps などのオンライン サービスにより、コード ライブラリや大規模データ リポジトリを管理するオーバーヘッドをなくし、Web アプリに機能を簡単にスナップインできるようにします。オーバーヘッドが減るのは良いことですが、この利点にもいくつかリスクが伴います。

たとえば、健康ソフトウェア業界の Contoso パートナーの 1 社に Litware Inc があるとします。Litware 社は、新しい Exercise API をリリースしており、Contoso Health の開発者に毎日のエクササイズのデータ フィードを利用するキーを提供しています。Contoso Health が Web アプリであれば、開発チームは次のようなスクリプト参照を使用してこの Exercise API を実装します。

<script src="https://api.litware.com/devkey/exercise.js"></script>

Contoso の開発者は、Litware 社が優れたコンテンツを提供することを信頼しており、優れたセキュリティの実践を行っていることがわかっています。残念なことに、不満を抱いている開発者によって Litware 社のサーバーが侵害され、exercise.js に変更が加えられ、起動スクリプトで「Contoso Health はメンテナンスを実行する必要があります。次のメンテナンス アプリをダウンロードしてください」というメッセージを含むポップアップが表示されるようになりました。ユーザーはこれを正規のメッセージと考え、マルウェアをダウンロードしてしまいました。Contoso の開発者は、「Litware は優れた検証を使用するのに、どうしてこのような違反が起きたのだろう」と途方に暮れます。

Web では、今回説明した方法で参照されるスクリプトは、同じサイト上のスクリプトと同じ場所で実行されます。つまり、exercise.js (JavaScript として実行) は、他のスクリプト オブジェクトと同様に DOM ツリーに問題なくアクセスできます。既に説明したように、これは深刻なセキュリティの問題につながる可能性があります。このリスクを軽減するため、Windows 8 はアプリのリソースを 2 つのコンテキストに分離します (図 2 参照)。

Local vs. Web Context Features (Mashed from “Features and Restrictions by Context” [bit.ly/NZUyWt] and “Secure Development with HTML5” [bit.ly/JOoMOS])
図 2 ローカル コンテキストと Web コンテキストの機能の比較 (「各コンテキストの機能と制限」(https://msdn.microsoft.com/ja-jp/library/windows/apps/hh465373.aspx) と「HTML5 を使用したセキュリティ開発」(http://www.scribd.com/doc/66045883/Secure-Development-of-Metro-Apps-With-Html5、英語) から抜粋)

ローカル コンテキストは、Windows ランタイムとアプリ パッケージに含まれるすべてのリソース (HTML、スクリプト、CSS、アプリ状態ディレクトリに格納されたアプリ データなど) にアクセスできますが、リモートの HTML、JavaScript、または CSS にはアクセスできません (上記の exercise.js の例のように)。Windows 8 のトップ レベル アプリは、常にこのローカル コンテキストで実行します。図 2 の ms-appx:// はローカル コンテキストでコンテンツを解決するのに使用されます。このスキームを使用して、ローカル コンテキストで実行するアプリ パッケージのコンテンツを参照します。多くの場合は、3 つ目のスラッシュを付けて (ms-appx:///)、パッケージの完全名を参照します。Web 開発者にとってこの方法は、3 つ目のスラッシュがローカル ファイル システムを参照する file:// protocol を使用するのに似ています (file://REMOTE COMPUTER/ の代わりに file://END USER's COMPUTER/ とします)。

Web コンテキストでは、開発者は Iframe を使ってリモート コンテンツを Windows ストア アプリに提供できます。Web ブラウザーの Iframe と同様に、Iframe 内で実行するコンテンツは、Windows ランタイムと JavaScript 用 Windows ライブラリのいくつかの機能など、外部リソースへのアクセスが制限されます (完全な一覧は、https://msdn.microsoft.com/ja-jp/library/windows/apps/hh465373.aspx#winjs_for_webcontext を参照してください)。Web コンテキストの目的は、開発者が Bing Maps などのサードパーティ API を参照したり、CDN のライブラリをアプリに提供したりできるようにすることです。

Iframe のソースとして http:// か https:// を使用すると、Iframe のコンテンツが Web コンテキストに自動的にキャストされます。Iframe でも、ms-appx または ms-appx-web を使用しているときは、アプリ パッケージ内のリソースを指すことができます。Iframe のソースが ms-appx:// スキームを参照する場合、Iframe のコンテンツはローカル コンテキストで実行されます。これにより開発者は、ローカル コンテキストの機能 (Windows ランタイム、Windows JavaScript API など) へのアクセスを維持したまま、アプリ パッケージのリソースを Iframe に組み込めます。利用できるもう 1 つのスキームは ms-appx-web:// で、ローカル アプリ パッケージのコンテンツを Web コンテキストで実行できるようになります。このスキームは、リモート コンテンツをマークアップ内に組み込む必要がある場合に役立ち、Contoso Health アプリを使用しているユーザーの場所に基づいて、地元の病院についての (Bing 検索 API の) Bing の検索結果を追加することなどが可能になります。なお、HTML5 で Iframe を使用するときは、必ず、アプリの保護強化のために sandbox 属性を使用できることを思い出してください。この属性により、Iframe 内のコンテンツのスクリプト実行が制限されます。sandbox 属性の詳細については、bit.ly/Ppbo1a (英語) を参照してください。

図 3 に、ローカル コンテキストと Web コンテキストで使用されるさまざまなスキームを、使用例と共に示します。

図 3 スキームとコンテキストの例

スキーム コンテンツの場所 コンテキスト 使用するタイミング
ms-appx:// アプリ パッケージ ローカル

<iframe

 src="ms-appx:///1.html"

></iframe>

Windows ランタイムまたは Windows JavaScript API すべてにアクセスする必要があるコンテンツを Iframe に読み込むとき
ms-appx-web:// アプリ パッケージ Web

<iframe

 src="ms-appx-web:///2.html"

></iframe>

マッピング ウィジェットや検索結果を表示するなど、Windows ストア アプリ インターフェイスの一部としてリモート ソースのコンテンツを使用するとき
http:// リモート Web

<iframe

 src="http://host/3.html"

></iframe>

Web ページや他のサーバーのスクリプト ファイルなどのリモート コンテンツを参照するとき

Iframe が属するコンテキストは、その中のコンテンツがどのように参照されているかに基づきます。つまり、コンテキストはスキームによって決まります。Windows 8 で使用するスキームの詳細については、https://msdn.microsoft.com/ja-jp/library/windows/apps/hh781215.aspx を参照してください。

このセクション冒頭の Litware社 のハッキングのシナリオを思い出してください。Windows 8 のコンテキストの分離は、Windows ランタイムにも Contoso Health 用のアプリ データにもアクセス権がない Web コンテキストへのクロスサイト スクリプト攻撃を制限するのに役立ちます。Web コンテキストでは、ローカル コンテキストを変更はできません。コンテキスト間のコミュニケーションは可能ですが、どのような種類のコミュニケーションを行うかを管理できます。

コンテキスト間のコミュニケーション

トップレベルのドキュメントは、Web コンテキストで実行する Iframe とどのようにコミュニケーションするのでしょう。HTML5 の postMessage 機能を使用して、Windows ストア アプリはコンテキスト間でデータを受け渡すことができます。これにより、開発者は 2 つのコンテキストがコミュニケーションを取る方法を構造化し、正しいことがわかっているプロバイダー (ここでも許可リスト) のみがローカル コンテキストとコミュニケーションを取れるようにします。Web コンテキストで実行する必要があるページは、Iframe に http://、https://、または ms-appx-web:// を設定した src 属性を付けて参照します。

Contoso Health アプリでは、システムは Litware Exercise API からフィットネスのヒントを取得します。Contoso Health の開発チームは litwareHelper.html ページを構築しました。このページを使用して jQuery $ajax オブジェクト経由で Exercise API とコミュニケーションを取ります。リモート リソース (exercise.js) を利用するので、litwareHelper.html は Web コンテキストで実行する必要があります。つまり、Iframe で実行する必要があります。Iframe の設定は、ページが参照される方法を除けば、他のすべての Web アプリと同じです。litwareHelper.html ページはローカル アプリ パッケージの一部ですが、Web コンテキストで実行する必要があるため、次のように ms-appx-web を使用して読み込みます。

<iframe id="litwareHelperFrame” src="ms-appx-web:///litwareHelper.html"></iframe>

開発チームはローカル コンテキスト ページに次の関数を追加して、Web コンテキスト ページにデータの要求を送信します。

function btnGetFitTips_Click() {
  var msg = {
    term: document.getElementById("txtExerciseSearchTerm").value,
    itemCount: 25  }
  var msgData = JSON.stringify(msg);
  var domain = "ms-appx-web://" + document.location.host;
  try {
    var iframe = document.getElementById("litwareHelperFrame");
    iframe.contentWindow.postMessage(msgData, domain);
  }
  catch (ex) {
    document.getElementById("output").innerText = "Error has occurred!";
  }
}

receiveMsg メソッドは、ローカル コンテキストからのメッセージを処理します。receiveMsg の引数は、メッセージの送信先、メッセージの送信元、および他の情報部分を含む、postMessage イベント (この場合は msgData 変数) に提供されるデータです (図 4 参照)。

図 4 receiveMsg による処理

function receiveMsg(e) {
  if (e.origin === "ms-appx://" + document.location.host) {
    var output = null;
    var parameters = JSON.parse(e.data);
    var url = "https://api.litware-exercise.com/data/" 
      + parameters.term +
      "/count/" + parameters.itemCount;
    var options = {
      dataType: "jsonp",
      jsonpCallback: "jsonpCallback",
      success: function (results) {
        output = JSON.stringify(results.items);
        window.parent.postMessage(output, "ms-appx://" 
        + document.location.host);
      },
      error: function (ex) {
        output = ex;
      }
    };
    $.ajax(url, options);
  }
}

receiveMsg での最初の手順は、postMessage の送信元の確認です。これは、メッセージが想定する送信元から送信されたものであることを確認する重要なセキュリティ チェックです。e.origin は postMessage の送信者のドメインとスキームを確認します。つまり、ms-appx (ローカル コンテキストのアドレス) を確認することになります。Litware API から JSON データを集めた後、アプリは postMessage コマンドを使用して window.parent に結果を返します。receiveMsg では ms-appx にドメインを設定していることに注目してください。これはメッセージの送信先アドレスで、データがローカル コンテキストに返されることを示します。Iframe からのデータは、ローカル コンテキストのリソースが使用する必要があります。開発チームは、次のように、processResult 関数を追加して、Web コンテキストからのデータを取得してローカル コンテキストに返します。

function processResult(e) {
  if (e.origin === "ms-appx-web://" + document.location.host) {
    document.getElementById("output").innerText = e.data;
  }
}

繰り返しになりますが、メッセージ イベントの送信元を必ず確認して、承認された場所 (つまり、許可リストに登録された場所) から送信されたデータのみを処理するようにします。送信元は Web コンテキスト スキーム、つまり processResult メソッドの ms-appx-web であることに注意します。スキームの切り替えは、開発者が見過ごし、デバッグ中にメッセージがどこへ行ったのかがわからなくなる落とし穴になる場合があります。

最後に、Web コンテキストからデータを取得してローカル コンテキスト ページに返すため、メッセージ イベント用のイベント ハンドラーを追加します。app.onactivated メソッドでは、次のように、window オブジェクトにイベント リスナーを追加します。

window.addEventListener('message', processResult, false);

既定でローカル コンテキストと Web コンテキストに分離されることで、Windows ストア アプリ外部のソースからコードを偶発的に実行するリスクを緩和できます。postMessage を使用して、外部スクリプトとアプリを構成するローカル スクリプトの間のコミュニケーション チャネルを提供できます。

Web から Windows 8 へ: 古い習慣に代わる新しいツール

Web 開発者は、Windows ストア アプリのセキュリティを構築するのにこれまで使い慣れたツールと新しいツールを使用できるようになります。HTML5 の入力検証などの既存のスキルを使用して、アプリに提供するデータの整合性を確保します。データ保護 API (Windows ランタイムの新機能) などの新しいツールは、簡単に実装できる強力な暗号化により、ユーザーの機密データを保護します。postMessage を使用することで、アプリは Web 上にある多数の JavaScript ライブラリやレガシ コードを利用でき、意図しないコード挿入からユーザーを保護できます。これらのすべての要素を組み合わせて機能させることで、JavaScript で見過ごされがちなセキュリティという重要なものを提供します。

Windows 8 は Web 開発者に、古い習慣を見直す機会を提供します。JavaScript は、もはや使いやすさを増すだけのものとして無視される、サーバーのファサードではありません。JavaScript、Windows ランタイム、および MSHTML は、Windows ストア アプリにセキュリティ機能を構築するのに必要なツールを提供します。サーバーは必要ありません。Web 開発者として利用できる幅広いスキルを持っていても、古い習慣を見直しWindows 8 が開く新しい世界を学ぶきっかけにしてください。

Tim Kulp は、メリーランド州ボルティモアの FrontierMEDEX で開発チームを率いています。彼は、ブログ (seccode.blogspot.com、英語) とツイッター (Twitter.com/seccode、英語) で、コード、セキュリティ、およびボルティモアの飲食店について話しています。

この記事のレビューに協力してくれた技術スタッフの Scott Graham に心より感謝いたします。