脆弱性"&'<<>\ Advent Calendar 2016 2日目の記事です。枠がたくさん空いているのでエントリーをお待ちしております。
まとめ
AngularJSのテンプレートには(例えHTMLエスケープをしていても)信用できない文字列を出力してはいけない。
AngularJSとは
使ったことはないので、この節の説明は怪しい。
Google製のMVW(Model-View-Whatever(?))フレームワーク。ウェブアプリで「表示」の部分を作るのに使う。あるデータを表示するとき、例えばjQueryならば、データが変更されたときにDOMを書き換えてデータを出力するという処理を書く。私の意図は、「このデータはここに表示する」ということであって、データが変更される場所ごとに出力を変更する処理を書くのはとても面倒。出力するだけならばまだ良いけど、HTMLの<input>
などで、逆にDOMが変更されたときはデータの変更もしたいという要求があるとなおさら。そこでAngularJSを使うと、HTMLタグに専用の属性を付けたり、{{hoge}}
などを使うことで、「このデータとこの表示が紐付く」という意図を素直に表現できる。上記の公式サイトのサンプルが分かりやすい。
現在の1系列の最新版は1.6。安定版は1.5。Angular2もある。名前からJSが消えて、中身もだいぶ変わっているらしいので、この記事は1系列の話のみ。
テンプレートへの信用できない文字列の出力
今どきのフレームワークなので、AngularJSでHTMLタグを出力しても、そのタグが解釈されるようなことはない。問題はAngularJSのテンプレート(ng-app
属性を付けたHTML要素)中にユーザー入力などの信用できない文字列を出力するとき。
もちろん、最初からAngularJSを使ってウェブアプリを作っていれば、そんなことをする必要は無い。見ての通りAngularJSはHTMLとの親和性がとても高いので、既存のウェブアプリにAngularJSで何らかの機能を追加しようとして、このような状態になるのだと思う。HTMLとして正しくエスケープしていても、{{hoge}}
がAngularJSのキーワードとして解釈される。
実際にそんなページを用意した。
versionのところに、ver
の内容を(HTMLエスケープして)そのまま出力している。{{1+1}}
が解釈されて2
となっている。色々試してみてほしい。
AngularJSのsandbox
{{}}
の中身がそのまま評価されるのならば、alert(0)
でalertが出るかと思いきや、そんなことはない。この中ではwindow
には触れないようになっている。eval
も動かない。こういうときに使える''.constructor.constructor('alert(0)')()
という便利な技がある。文字列のconstructor
はString()
で、String()
のconstructor
はFunction()
なので、文字列を渡すと関数が生成されるらしい。ところが、これもAngularJSでは動かない。AngularJSがめちゃくちゃ頑張っていて、各種の式や要素の参照はできるのに、危険なコードは動かないようになっている。何度も回避策が見つかっているけれど、その度にsandobxを改良している。
PortSwigger Web Security Blog: XSS without HTML: Client-Side Template Injection with AngularJS
このサイトの末尾の「List of Sandbox bypasses」に攻防の歴史がまとめられている。
IPAへの脆弱性報告
「AngularJSのsandboxの新たな回避策を見つけた!」という話ならすごいけれど、そんなことはなく、古いAngularJSを使っているウェブサイトを見つけた。
私「このサイトに脆弱性があります」
IPA「これ(古い)AngularJSの脆弱性なの? サイトの脆弱性なの? AngularJSのバージョンを上げれば直るの?」
私「AngularJSのバージョンを上げれば直るけど、そもそもテンプレート中にユーザー入力を埋め込むのが悪いし、AngularJSの脆弱性なんだろうか……? あ、今見たらサイトのAngularJSのバージョンが上がっていました」
ということで、良く分からないまま取り扱い終了になった。
AngularJS 1.6
Angular 1.6 - Expression Sandbox Removal
と、こんな感じの記事を書こうと思って、AngularJSのサイトを見ていたところ、1.6ではこのsandboxを無くしたと書かれていた。「そもそもsandboxを導入したのが間違いだった」的なことも書かれている。まったくもってその通りだと思う。「テンプレート中には信用できない文字列は出力しないでね。でも、一応、念のため、出力しても大丈夫にしておくよ」だとユーザーはそれを当てにしてしまうと。
window
などに触れないのはそのままだけど、sandboxは無くなったので、もしテンプレート中に好きな文字列が出力できるのならば、任意のスクリプトを実行できる。テンプレート中にユーザー入力を出力しているのに、うっかりAngularJSのバージョンを1.6に上げてしまうと、とても危険。
対策
ドキュメントに書かれているとおり、ng-non-bindable
の中に出力すれば良い。
<div style="color: white" ng-non-bindable>
AngularJS version: <?php echo h($ver) ?>
</div>
http://u1tramarine.blue/angular/ の末尾に実際に白文字で埋め込んでいる。{{}}
がそのまま表示されるはず。