AngularJSを1.0系から1.2系にアップグレードした時にハマりがちなのがSCE(Strict Contextual Escaping)ではないでしょうか。
AngularJS 1.2ではSCEがデフォルトで有効になったため、アップグレードしたによりこれまで動いていたアプリが動かなくなる可能性があります。
例えば、「ng-bind-htmlにバインドしているHTMLが表示されなくなった」とか「iframeの内容が表示されなくなった」なんてことが起きたら、まずはconsole.logを見てみましょう。
下記のようなログが出力されていれば、SCEが有効になったことが原因で要素が表示されていないと考えられます。
Attempting to use an unsafe value in a safe context.
Blocked loading resource from url not allowed by $sceDelegate policy.
よく分からないからとりあえず無効にしてしまえっと、以下のようにするのは簡単ですが、これではセキュアじゃありませんね。
angular.module('myApp', []).config(function($sceProvider) {
$sceProvider.enabled(false);
});
というわけで、セキュアなアプリを作るためにも、まずはSCEが何者なのかを見ていきましょう。
SCE(Strict Contextual Escaping)とは
Strict Contextual Escapingって言われても、よい日本語訳が思いつきませんね・・・
要は、セキュリティ対策として、サニタイズされていないHTML要素や、信頼されていないページの要素を表示するのを防止するための機能です。
SCEのような機能がない場合は、安全な要素の変数名にsafe_
やesc_
みたいなプレフィックスをつけて、プレフィックスがついていない変数が表示されていないかどうかをgrepで調べる方法を使ったりします。
しかし、それではサニタイズのうっかり忘れなどを防ぐことができません。
そこで、信頼されている安全なHTMLやURLに信頼済みのマークをつけるようにして、マークのついていない要素を表示できないようにしたり、ブラックリストに含まれているページの内容を表示できないようにしたりするのがSCEの主な機能です。
AngularJS内の実装を見てみると、それほど特別な処理をしているわけではなく、信頼済みコードはTrustedValueHolderType
でラップされていて、バインドするときはそれをアンラップしてるだけです。
あくまでも、SCEがサニタイズやエスケープを自動でやってくれるわけではないので、開発者が何らかのライブラリを使ってサニタイズしたり、信頼できるサイトのURLかどうかをチェックする必要があります。
開発者はどうすればいいの?
実際のところ、信頼済みマークをつけなければいけない箇所はそれほど多くありません。
AngularJSでは、{{ }}
で文字列を出力した場合は自動的にエスケープされますし、aタグのhref/ngHrefやimgタグのsrc/ngSrcも自動的にサニタイズしてくれます。
主に対応が必要なのは、ngBindHtmlディレクティブとimgタグ以外(video, iframe, objectなど)のsrc/ngSrcです。
それぞれ対応方法を見てみましょう。
ngBindHtml
ngBindHtmlでHTML要素をバインドするには、下記のように$sce.trustAsHtml
を使って信頼済みのマークをつけます。
angular.module("myApp.controllers", [])
.controller("MyCtrl", function($scope, $sce) {
var sanitizedHtml = /* サニタイズ済みのHTML要素 */
$scope.userHtml = $sce.trustAsHtml(sanitaizedHtml);
});
これで、以下のようにHTML要素をバインドすることができます。
<div ng-bind-html="userHtml">
なお、AngularJS 1.0では、HTML要素をバインドするためにngBindHtmlとngBindHtmlUnsafeという2種類のディレクティブが用意されていましたが、AngularJS 1.2からはngBindHtmlに1本化され、ngSanitizeを依存モジュールに加える必要もなくなりました。
src/ngSrc
続いて、img以外のタグ(iframe, object, videoなど)のsrc/ngSrcに指定するURLです。
さて、aタグのhrefやimgタグのsrcは自動でサニタイズしてくれるのに、それ以外のタグではなぜ信頼済みマークをつける必要があるのでしょうか?
それは、例えばiframeを使うとクロスサイトリクエストフォージェリなどの危険性があるため、URLをサニタイズするだけでは不十分だからです。
なので、アプリケーションの中に埋め込んでも問題ないサイトのURLかどうかを確認する必要があります。
対応方法は2通りあります。
1つ目の方法はngBindHtmlと同じように、$sce.trustAsResourceUrl
を使ってマークする方法です。
angular.module("myApp.controllers", [])
.controller("MyCtrl", function($scope, $sce) {
var trustedUrl = /* 信頼できるサイトのURL */
$scope.url = $sce.trustAsResourceUrl(trustedUrl);
});
<iframe src="{{url}}">
もう1つの方法は、ホワイトリストとブラックリストを使う方法です。
下記のようにホワイトリストにURLを登録すれば、$sce.trustAsResourceUrl
を呼び出す必要はなく、src/ngSrcにバインドすることができます。
angular.module('myApp', [])
.config(function ($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
'self',
'http://example.com/**'
]);
});
その他
ngIncludeやtemplateUrlで指定するURLは、通常アプリをホストしているドメインと同じになると思いますが、もし外部のサイトのURLを指定する場合は、ホワイトリストに追加するようにします。
自作ディレクティブで信頼済みのHTMLバインドしている場合は、そのHTMLをDOMにパースするために$parse
の代わりに$sce.parseAsHtml
を使います。
より詳しく知りたい場合は公式ページを確認してください。