AngularJSとサーバーサイドテンプレートの混在とngNonBindable
Posted: Updated:
Angularとサーバーサイドテンプレートの混在
先日リリースされた某サービス(他社)がAngularを使っていて、XSSがボロボロ出てくるだとか、{{var}}
な形式で値を入力するとng-template側でテンプレーティングされるだとかの話がありました。
詳しくは見ていないので、今回の話とまったく同じかは把握していませんが、サーバーサイドテンプレートを混在させると、次のようなことが起こりえます。
例えばejsとAngular
サンプルとしてスカスカなControllerを用意します。
angular.module('app', []).controller('AcmeCtrl', function($scope) { $scope.foo = 'bar'; });
ejsは次のようなテンプレートになっているとします。
<%= title %>
- {{item.name}}
ejsにおける <%= title %>
には {{foo}}
という文字列が入っていたとして、それが処理されたた時点では次のようになります。
{{foo}}
- {{item.name}}
ng-controller 配下にあるため {{foo}}
が展開されて <h2>bar</h2>
となります。
このとき、Angularも基本的にはHTMLエスケープをしてくれるので、即座に任意のスクリプトを挿入されるタイプのXSSになることはありませんが、スコープ内の値を変にお漏らしされてしまうと、大変に恥ずかしいものがあります。
また、保持はしたいけど画面に表示されて欲しくはない、際どい情報がうっかりとScope内で $parent
など含めて辿れる位置に存在していても、困ったことになります。
スコープ内の関数を実行
では、次のようなパターンではどうでしょうか。
angular.module('app', []).controller('AcmeCtrl', function($scope) { $scope.foo = 'bar'; $scope.onClick = function() { alert('hello!'); }; });
<%= title %>
- {{item.name}}
このとき、ejsの <%= title %>
に {{onClick()}}
という文字列が入っていると、評価時に $scope.onClick()
が実行されてalertが飛び出すことになります。前例と比べると、危険な感じになってきました。ユーザーの意志で行われるべきアクションが、第三者によって強制的に実行されてしまう可能性が出てきます。
回避策
今回の問題を回避する上で、最も望ましいのはAngularJSとサーバーサイドテンプレーティングを混在させない設計です。しかし、そうはなっていないケースもあることでしょう。そのような場合には、次のような回避策が考えられます。
ngNonBindable
フロントエンド的に、最も手っ取り早いのは ngNonBindable を利用することです。
<%= title %>
- {{item.name}}
先程の例に ng-non-bindable
を加えた例です。こうすると、<h2 />
の中はAngularの評価から逃れられるため、<%= title %>
にAngularが評価可能な文字列が入っていたとしても無視されます。
手っ取り早いのはよいのですが、結局、過度に混在したときに面倒くさい=人為的ミスで対応の抜け漏れが発生しやすいという点で、あまり良い手段とは言えません。
サーバーサイドでエスケープ
もうひとつの手段は、サーバーサイドテンプレートで出力する際に、HTML文字列のエスケープに加えて {{
と }}
も何らかAngularが評価不能な形にエスケープしてしまうことです。
ちなみに、このときのInterpolate記号は、AngularJS: API: $interpolateProviderで startSymbol
と endSymbol
として個別に変更することも可能です。
参考
サーバーサイドから本当に {{not-interpolate}}
な文字列を出力したい場合に、Angularからの評価を逃れる手段がないとかエスケープがほんにゃらかんにゃらについて、詳しくは参考Issueにおける議論を見て下さい。
- XSS issues with server-rendered Angular templates · Issue #5601 · angular/angular.js
- AngularJS: API: ngNonBindable