異なるセキュリティサンドボックス内のコンテンツのクロススクリプト

Adobe AIR 1.0 およびそれ以降

ランタイムセキュリティモデルでは、発信元が異なるコードを分離します。異なるセキュリティサンドボックス内のコンテンツをクロススクリプトすると、あるセキュリティサンドボックス内のコンテンツから、別のサンドボックス内の選択されたプロパティおよびメソッドにアクセスできるようになります。

AIR セキュリティサンドボックスおよび JavaScript コード

AIR では、あるドメインのコードが別のドメインのコンテンツを操作するのを避けるために、同一生成元のポリシーを適用します。すべてのファイルが生成元に基づいたサンドボックス内に配置されます。通常、アプリケーションサンドボックス内のコンテンツは、同一生成元の原則を破って、アプリケーションインストールディレクトリの外部から読み込まれたコンテンツをクロススクリプトすることはできません。ただし、AIR には非アプリケーションコンテンツのクロススクリプトを可能にするいくつかのテクニックがあります。

まず、フレームまたはインラインフレームを使用してアプリケーションコンテンツを異なるセキュリティサンドボックスにマップする方法があります。アプリケーションのサンドボックス領域から読み込まれたページは、リモートドメインから読み込まれたページのように動作します。例えば、アプリケーションコンテンツを example.com ドメインにマップすると、そのコンテンツでは example.com から読み込まれたページ間のクロススクリプトを実行できます。

このテクニックはアプリケーションコンテンツを異なるサンドボックスに配置するので、このコンテンツ内のコードは評価されたストリング内のコード実行に関する制限も受けなくなります。このサンドボックスのマッピングを使用すると、リモートコンテンツをクロススクリプトする必要がない場合でも制限を緩和できます。この方法でのコンテンツのマッピングは、多くの JavaScript フレームワークの中の 1 つを操作する場合や、評価するストリングに依存する既存のコードを操作する場合に特に有用です。ただし、コンテンツがアプリケーションサンドボックスの外部で実行される場合には、信頼できないコンテンツが挿入されて実行される可能性があります。この付加的なリスクに対して、考慮し、防御する必要があります。

同時に、別のサンドボックスにマップされるアプリケーションコンテンツは AIR API へのアクセス権を失うので、サンドボックスのマッピングを使用してアプリケーションサンドボックスの外部で実行されるコードに AIR 機能を公開することはできません。

別のクロススクリプトの方法としては、非アプリケーションサンドボックス内のコンテンツと、アプリケーションサンドボックス内のその親ドキュメントの間に サンドボックスブリッジ と呼ばれるインターフェイスを作成します。サンドボックスブリッジを使用すると、親コンテンツが定義したプロパティとメソッドに子コンテンツからアクセスしたり、子コンテンツで定義したプロパティとメソッドに親コンテンツからアクセスしたりできます。

最後の方法として、アプリケーションサンドボックス(および必要に応じて他のサンドボックス)からクロスドメインの XMLHttpRequest を実行できます。

詳しくは、 HTML の frame エレメントと iframe エレメント Adobe AIR の HTML セキュリティ および XMLHttpRequest オブジェクト を参照してください。

アプリケーションコンテンツの非アプリケーションサンドボックスへの読み込み

アプリケーションインストールディレクトリ以外から読み込まれたコンテンツのクロススクリプトをアプリケーションコンテンツで安全に実行できるようにするには、 frame エレメントまたは iframe エレメントを使用して、アプリケーションコンテンツを外部コンテンツとして同じセキュリティサンドボックスに読み込みます。リモートコンテンツのクロススクリプトは必要ないが、アプリケーションサンドボックス外のアプリケーションのページを読み込む必要がある場合は、元のドメインとして http://localhost/ または問題のない他の値を指定して、同じテクニックを使用できます。

AIR では、frame エレメントに sandboxRoot および documentRoot という新しい属性を追加しています。これにより、フレームに読み込まれるアプリケーションファイルを非アプリケーションサンドボックスにマップするかどうかを指定できます。 sandboxRoot URL の下のパスに解決されるファイルは、代わりに documentRoot ディレクトリから読み込まれます。セキュリティのため、この方法で読み込まれるアプリケーションコンテンツは、実際に sandboxRoot URL から読み込まれているかのように扱われます。

sandboxRoot プロパティは、フレームコンテンツを配置するサンドボックスおよびドメインの決定に使用する URL を指定します。 file: http: または https: URL スキームを使用する必要があります。相対的な URL を指定する場合、コンテンツはアプリケーションサンドボックス内に留まります。

documentRoot プロパティでは、フレームコンテンツの読み込み元のディレクトリを指定します。 file: app: または app-storage: URL スキームを使用する必要があります。

次の例では、アプリケーションの sandbox サブディレクトリにインストールされたコンテンツをマップし、リモートサンドボックスおよび www.example.com ドメインで実行します。

<iframe 
    src="http://www.example.com/local/ui.html"  
    sandboxRoot="http://www.example.com/local/"  
    documentRoot="app:/sandbox/"> 
</iframe>

ui.html ページでは、次のスクリプトタグを使用してローカル sandbox フォルダーから javascript ファイルを読み込むことができます。

<script src="http://www.example.com/local/ui.js"></script>

また、次のようなスクリプトタグを使用してリモートサーバーのディレクトリからコンテンツを読み込むこともできます。

<script src="http://www.example.com/remote/remote.js"></script>

sandboxRoot URL は、リモートサーバー上の同じ URL にあるコンテンツをマスクします。上の例では、AIR によって要求がローカルアプリケーションディレクトリに再マップされるので、 www.example.com/local/ (またはそのサブディレクトリ)にあるリモートコンテンツにはアクセスできません。要求は、ページナビゲーションからの派生か、XMLHttpRequest からの派生か、コンテンツを読み込む他の手段からの派生かによって、再マップされます。

サンドボックスブリッジインターフェイスの設定

アプリケーションサンドボックス内のコンテンツが非アプリケーションサンドボックス内のコンテンツによって定義されたプロパティやメソッドにアクセスする必要がある場合、または非アプリケーションコンテンツがアプリケーションサンドボックス内のコンテンツによって定義されたプロパティとメソッドにアクセスする必要がある場合に、サンドボックスブリッジを使用できます。子ドキュメントの window オブジェクトの childSandboxBridge プロパティと parentSandboxBridge プロパティを使用してブリッジを作成します。

子サンドボックスブリッジの確立

childSandboxBridge プロパティを使用すると、子ドキュメントで親ドキュメント内のコンテンツにインターフェイスを公開できます。インターフェイスを公開するには、 childSandbox プロパティを子ドキュメント内の関数またはオブジェクトに設定します。設定すると、親ドキュメント内のコンテンツからオブジェクトまたは関数にアクセスできます。次の例では、子ドキュメントで実行中のスクリプトで、関数とプロパティを 1 つずつ含むオブジェクトをその親に公開する方法を示します。

var interface = {}; 
interface.calculatePrice = function(){ 
    return ".45 cents"; 
} 
interface.storeID = "abc" 
window.childSandboxBridge = interface;

この子コンテンツが「子」の ID を割り当てられたインラインフレームに読み込まれていれば、次のようにして、フレームの childSandboxBridge プロパティを読み取ることによって親コンテンツからインターフェイスにアクセスできます。

var childInterface = document.getElementById("child").contentWindow.childSandboxBridge; 
air.trace(childInterface.calculatePrice()); //traces ".45 cents" 
air.trace(childInterface.storeID)); //traces "abc"

親サンドボックスブリッジの確立

parentSandboxBridge プロパティを使用すると、親ドキュメントで子ドキュメント内のコンテンツにインターフェイスを公開できます。インターフェイスを公開するには、親ドキュメントで子ドキュメントの parentSandbox プロパティを親ドキュメント内に定義された関数またはオブジェクトに設定します。設定すると、子ドキュメント内のコンテンツからオブジェクトまたは関数にアクセスできます。次の例では、親フレームで実行中のスクリプトで、関数を含むオブジェクトを子ドキュメントに公開する方法を示します。

var interface = {}; 
interface.save = function(text){ 
    var saveFile = air.File("app-storage:/save.txt"); 
    //write text to file 
} 
document.getElementById("child").contentWindow.parentSandboxBridge = interface;

このインターフェイスを使用すると、子フレーム内のコンテンツで、テキストを save.txt という名前のファイルに保存できますが、ファイルシステムに他の方法ではアクセスできません。子コンテンツは、save 関数を次のように呼び出します。

var textToSave = "A string."; 
window.parentSandboxBridge.save(textToSave);

アプリケーションコンテンツは、他のサンドボックスにできる限り範囲の狭いインターフェイスを公開する必要があります。非アプリケーションコンテンツは、偶発的に、または悪意によってコードが挿入される可能性があるので、本来、信頼できないものです。親サンドボックスブリッジによって公開するインターフェイスの誤用を防ぐ適切な安全対策を実施する必要があります。

ページ読み込み時の親サンドボックスブリッジへのアクセス

子ドキュメント内のスクリプトから親サンドボックスブリッジにアクセスするには、スクリプトを実行する前にブリッジを設定する必要があります。新しいページの DOM が作成された後で、スクリプトが解析されるか DOM エレメントが追加される前に、ウィンドウ、フレームおよびインラインフレームのオブジェクトは、 dominitialize イベントを送出します。 dominitialize イベントを使用して、子ドキュメント内のすべてのスクリプトがアクセスできるページ構成シーケンスで、前もってブリッジを確立できます。

次の例は、子フレームから送出された dominitialize イベントに対応して親サンドボックスブリッジを作成する方法を示しています。

<html> 
<head> 
<script> 
var bridgeInterface = {}; 
bridgeInterface.testProperty = "Bridge engaged"; 
function engageBridge(){ 
    document.getElementById("sandbox").contentWindow.parentSandboxBridge = bridgeInterface; 
} 
</script> 
</head> 
<body> 
<iframe id="sandbox" 
            src="http://www.example.com/air/child.html"  
            documentRoot="app:/" 
            sandboxRoot="http://www.example.com/air/" 
            ondominitialize="engageBridge()"/> 
</body> 
</html>

次の child.html ドキュメントは、子コンテンツから親サンドボックスブリッジにアクセスする方法を示しています。

<html> 
    <head> 
        <script> 
            document.write(window.parentSandboxBridge.testProperty); 
        </script>   
    </head>   
    <body></body> 
</html>

フレームではなく、子ウィンドウ上で dominitialize イベントをリッスンするには、次のようにして、 window.open() 関数によって作成された新しい子ウィンドウオブジェクトにリスナーを追加する必要があります。

var childWindow = window.open(); 
childWindow.addEventListener("dominitialize", engageBridge()); 
childWindow.document.location = "http://www.example.com/air/child.html";

この場合、アプリケーションコンテンツを非アプリケーションサンドボックスにマップする方法はありません。このテクニックは、 child.html がアプリケーションディレクトリの外部から読み込まれる場合にのみ有用です。ウィンドウ内のアプリケーションコンテンツを非アプリケーションサンドボックスにマップすることはできますが、フレームを使用して子ドキュメントを読み込んで目的のサンドボックスにマップする中間ページを最初に読み込む必要があります。

HTMLLoader クラスの createRootWindow() 関数を使用してウィンドウを作成する場合、新しいウィンドウは createRootWindow() の呼び出し元のドキュメントの子ではありません。したがって、呼び出し元ウィンドウから、新しいウィンドウに読み込まれる非アプリケーションコンテンツへのサンドボックスブリッジを作成することはできません。その代わり、フレームを使用して子ドキュメントを読み込む新しいウィンドウ内の中間ページを読み込む必要があります。その後、新しいウィンドウの親ドキュメントから、フレームに読み込まれた子ドキュメントへのブリッジを確立できます。