そろそろ押さえておきたい angularjsのセキュリティ
DESCRIPTION
ng-mtg#6 AngularJS勉強会の発表資料です。TRANSCRIPT
そろそろ押さえておきたい
AngularJSのセキュリティ
ng-mtg#6 AngularJS勉強会
2014年7月25日
(1.3.0-beta.16対応版)
西村 宗晃 a.k.a. nishimunea
html5j Webプラットフォーム部 部員
HTML5 Experts.jp コントリビューター
セキュリティキャンプ全国大会 2014 講師
FxOS コードリーディング 部員
1
2
3
4
ノウハウを共有して、様々なプラットフォームで
動かすための知識を培おうではないか!
WebはPCの世界を抜け出し
様々なプラットフォームで動き始めています
by めこ部長
本日お話する内容
5
AngularJSで対策できる脆弱性とその実装方法
• DOM Based XSS
• Cross-Site Request Forgery (CSRF)
AngularJSでは対策できない脆弱性 (スコープ外)
• サーバ側での対策が必要となる脆弱性
• ブラウザやプロトコル由来の脆弱性
※CSRFはサーバ側での対策を要しますが今回の発表ではスコープ外とします
DOM Based XSS
XSSの種類
• サーバ側で発生するXSS
- 反射型XSS
- HTTPのリクエストに含まれるスクリプトが、 レスポンスのHTMLにそのまま埋め込まれることで発生
- 持続型XSS
- HTTPのリクエストに含まれるスクリプトが一旦サーバに保存され、 そのデータを元にHTMLを出力する際にスクリプトが埋め込まれることで発生
• クライアント側で発生するXSS
- DOM based XSS
- 外部から取得したデータを元にJSでHTMLを動的生成する際に、 データに含まれるスクリプトがDOMツリーへ出力されることで発生
7 ※DOM based XSSも反射型と持続型に分類すべきという意見もあります
https://www.owasp.org/images/f/f4/ASDC12-Unraveling_some_of_the_Mysteries_around_DOMbased_XSS.pdf
参考:angularjs.orgにあった反射型XSS
8 ※撮影のためIEのXSSフィルターを無効化しています。本件はGoogleに報告し、既に修正済みです
そのままレスポンスとして出力される
URLに仕込んだスクリプトが… <script>alert(‘XSS’)</script>
DOM Based XSS発生のメカニズム
9
Web Messaging
$location
Referrer
Web Socket
$cookies
$http, $resource
ngModel
Web Storage
ngBindHtml
ngInclude
element.innerHTML
document.write
jQLite.html
jQLite.append
window.eval
new Function
XSS
スクリプトを 含むデータ
データバインドで DOMを生成
攻撃者 被害者
データの入力経路 DOMツリーへの出力経路
※図中の入力経路、出力経路は一例です
DOM Based XSSの対策
10
• 動的にDOMを生成する際は信頼できるデータのみを使用する
- そのままDOMに挿入しても安全だと開発者が保証できるデータのみを使用する
• それ以外の場合は、データを出力するコンテキストに応じた対策を施す
- HTMLの要素内容としてデータを出力する場合
- createTextNodeでテキストノードとして出力する(またはHTMLエスケープ)
- HTMLマークアップとして出力する際は、スクリプトが実行される 恐れの無い要素と属性のみを許可する
- HTML要素の属性値としてデータを出力する場合
- setAttributeで属性値として出力する(またはHTMLエスケープ)
- イベントハンドラ属性などのスクリプトが実行可能な属性値は動的に生成しない
- URLを出力する際は安全なURLであることを検証する
- http(s)://で始まるURLかホワイトリストに該当するオリジンのみを許可する
Strict Contextual Escaping (SCE)
11
• コンテキストに応じたエスケープ機能を提供するAngularJSの機能
• AngularJS v1.2以降ではデフォルトで有効
利用可能なコンテキスト コンテキストの適用例
HTML <div ng-bind-html=" HTML "></div>
CSS <div style=" CSS ">
URL <a href=" URL ">Link</a>
リソースURL <script src=" リソースURL ">
JS <div onclick=" JS ">
※ここで言うエスケープとは指定されたコンテキストでデータが適切に 表示されるように文字列を変換する処理の総称を意味します。
詳細は https://docs.angularjs.org/api/ng/service/$sceを参照
明示的な信頼に基づくエスケープ処理
12
• 安全が保証できる値に明示的な信頼を与えることができる
- 信頼されていない値は自動的にエスケープして出力される
- 逆に言えば、安全が保証できない値に信頼を与えることは脆弱性につながる
var input = '<a href="javascript:alert(1)">Link</a>';
console.log($sce.getTrusted($sce.HTML, input));
var trustedData = $sce.trustAs($sce.HTML, input);
console.log($sce.getTrusted($sce.HTML, trusted));
エスケープして出力される <a>Link</a>
信頼を与えた値はそのまま出力される <a href="javascript:alert(1)">Click</a>
値に明示的な信頼を与える
データバインド時にコンテキストを自動判別
• $sceを中心に複数のモジュールが連携してデータのコンテキストを自動判別
13
$compile
• テンプレートを解析してexpression展開箇所のコンテキストを判別。判別したコンテキストに応じたエスケープ処理を$interpolateに指示
• 各ディレクティブのtemplateURLを$sceでエスケープ
• ディレクティブの属性値に含まれるURLを$$sanitizeUriでエスケープ
• イベントハンドラ属性のexpression展開を検出&回避
$parse • expressionを解析して危険なオブ
ジェクトの参照を検出&回避
$$sanitizeUri
• URLのエスケープ機能を提供
$interpolate
• コンテキストに応じたexpressionの解析機能を提供
• expressionで展開されるデータを$sceでエスケープ
$sce
• リソースURLとHTMLのエスケープ機能を提供
$sanitize
• リソースURLとHTMLのエスケープ機能を提供
• HTMLの属性値に含まれるURLを$$sanitizeUriでエスケープ
$sceDelegate
• $sceの主要機能を実装
プロバイダ 使用関係
• AngularJSではデータをHTMLとして出力する手段が制限されている
- HTMLを出力できるのはng-bind-htmlと<iframe srcdoc>のみ
- その他のデータバインドはデータをテキスト値として出力する
- 例:ng-bind, <div>{{expression}}</div>
HTMLに対するデータバインド
14
• スクリプトの実行可能なHTML要素や属性は除去される(サニタイズ)
- 内部処理に$sanitizeを使うのでngSanitizeモジュールをDIする必要がある
- スクリプトが実行される危険性のあるHTML要素や属性は削除される
- 例:<script>alert(‘XSS’);</script><b>Hello</b> World
危険な要素は除去される
var app = angular.module('myApp', ['ngSanitize'] );
※ngSanitizeモジュールをDIしないとunsafeエラーが発生します https://docs.angularjs.org/#!/error/$sce/unsafe
HTMLに対するデータバインド
15
• サニタイズにはngSanitizeのホワイトリストが使用される
- ホワイトリストに無いHTML要素や属性は削除される
- 安全な要素でもホワイトリストに含まれていないものがある
- <video>などはリストに無いので削除対象となる
- 属性値に不正なURLが含まれる場合は属性ごと削除される
- 全てのURLが検査対象ではない
- background, cite, href, longdesc, src, usemap属性のみが検査対象
- つまり<html manifest>, <video poster>などのURLは検査対象とならない
- 例:<a href="javascript:alert('XSS');" >Link</a>
※ホワイトリストの詳細は$sanitizeのスクリプトコードを参照 https://github.com/angular/angular.js/blob/master/src/ngSanitize/sanitize.js
href属性はホワイトリストで許可されているが 不正なURLが含まれているので削除される
HTMLに対するデータバインド
16
• HTMLのバインド時はexpressionによる文字列の結合が禁止される
- expressionの解析処理が複雑化することによりミスが生じ、 XSSを許容するケースが生じる可能性があるため
- expressionの解析ミスによりJSが実行できてしまう問題が実際にあった <form ng-attr-action="{{'javascript:'}}alert(1)"><button>XSS</button>
※攻撃方法および攻撃可能バージョンの詳細は以下のサイトを参照 http://code.google.com/p/mustache-security/wiki/AngularJS
結合すると javascript:alert(1) となるので本来は無害化されるべき
• AngularJSはCSSのエスケープをサポートしていない
- style要素やstyle属性に挿入したデータはそのままCSSの構文として出力される
- 最近のブラウザではCSSの構文でスクリプトを実行できないためと思われる
CSSに対するデータバインド
17
• IEのQuirksモードではAngularJSの実行が禁止される
- CSS expressionsによるJSの実行が可能であるため
<style>body {color:expression(alert(1));}</style>
QuirksモードではCSSの宣言で JSを実行することができる
※以前はCSS expressions以外にもOperaの-o-linkプロパティで JSを実行する方法がありましたが現在は使えません
http://html5sec.org/#9
• ホワイトリストに合致しないURLは無効化される(サニタイズ)
- aHrefSanitizationWhiteList
- <a href>に指定可能なURLのホワイトリスト
- http:, https:, ftp:, mailto:, tel:, file:で開始するURLのみを許可
- imgSrcSanitizationWhiteList
- <img src>に指定可能なURLのホワイトリスト
- http:, https:, file:, blob:, data:image/ で開始するURLのみを許可
- 上記のホワイトリストに合致しないURLは頭にunsafe:を付けて無効化される
- 例:javascript:alert(1) unsafe:javascript:alert(1)
URLに対するデータバインド
18
• ホワイトリストは変更可能
- $compileProviderがホワイトリストを参照/変更する関数を提供している
※Firefox OSアプリで使う場合は「app:」を、Chromeアプリで使う場合は「chrome-extension:」を ホワイトリストに追加する必要があります
• ホワイトリストとブラックリストに基づいてサニタイズされる
- ページを構成するリソースを取得する際に適用される
- img以外のsrc属性、<form action>、SVGのxlink:href、ng-include、 $httpや$resourceの取得先URL、ディレクティブのtemplateUrlプロパティ
- ホワイトリストとブラックリストを用いてサニタイズする
- デフォルトではページと同一オリジン('self')のみを許容
- URLコンテキストより厳しい制限がかかる
リソースURLに対するデータバインド
19
• ホワイトリストとブラックリストは変更可能
- $sceDelegateProviderが両リストを参照/変更する関数を提供している
• ホワイトリストにはワイルドカードを指定できる
- リストには「*」と「**」という2種類のワイルドカードが指定できる
- ** は全ての文字にマッチする
- ** はURLの末尾のみで使用する
- 安全な例 http://example.com/templates/**
- 危険な例 http://**.example.com/
リソースURLに対するデータバインド
20 ※両リストに指定可能なパターンの詳細は以下のURLを参照
https://docs.angularjs.org/api/ng/service/$sce
http://evil.com/q=.example.com も許可されてしまう
• JSの動的生成は禁止される
- <script>の要素内容や、イベントハンドラ属性に対するexpressionの展開は禁止される
- onで始まる属性、もしくはformaction属性へのデータバインドが禁止される
- 代わりにng-clickなどのイベントハンドラディレクティブが使用できる
- scope外の関数にはアクセスできないのでXSSの被害を軽減できる
JSに対するデータバインド
21
• 任意のコードを実行可能なオブジェクトはexpressionで参照できない
- constructor, window, element, Objectなどはexpressionから参照できない
- Functionコンストラクタを用いて任意のJSが実行する方法が以前指摘されたため {{constructor.constructor('alert(1)')()}}
※最新版では__proto__の参照や、call, apply, bind関数も実行が禁止されています
以前はFunctionコンストラクタを 参照することでscope外の関数が実行できた
• XSS対策を自分で行う必要がある
- SCEによる保護は期待できない
- SCEはビルトインのディレクティブに最適化されている
- カスタムディレクティブが出力するHTML要素や属性には コンテキストの自動判別も適用されない
- 例:<a href>のコードを流用して<video poster>ディレクティブを作成する場合
カスタムディレクティブを作る際の注意点
22
var htmlAnchorDirective = valueFn({
compile: function(element, attr) {
if (!attr.href && !attr.name) {
attr.$set('href', '');
} $setはposter属性をURLとして判別 しないので自分でURLをケアする
• コンテキスト毎にデータの取扱い方針を決めておく
カスタムディレクティブを作る際の注意点
23
コンテキスト データの取扱い方針(例)
HTML
• 原則として要素内容はjQLite.text()で出力 • HTMLとして出力する際は$sce.getTrustedHtml()でサニタイズ • 属性値を出力する際はjQLite.attr()かjQLite.prop() を使用 • イベントハンドラと<iframe srcdoc>に対するデータバインドを禁止
CSS • style要素やstyle属性はテンプレートに対するデータバインドで出力
(styleタグを組み立ててHTMLとして出力しない)
URL • $$sanitizeUriで無害化したURLを出力
リソースURL • $sce.getTrustedResourceUri()でサニタイズ
JS • 信頼できるデータのみを出力
• HTMLとURLを出力する場合
カスタムディレクティブの実装例
24
.directive(‘myDirective’,[‘$sce’,‘$$sanitizeUri’,
function($sce, $$sanitizeUri) {
return function (scope, element, attrs) {
//HTML Content
element.html($sce.parseAsHtml(attrs.inputExpression)(scope));
//URL Attribute
element.attr('src', $$sanitizeUri(attrs.inputUri));
}
}]);
expressionを安全な HTMLにしてからDOMへ出力
URLをサニタイズして から属性値として出力
• ディレクティブにSCEが適用されていることを確認する
- 例:angular-translateはデフォルトでHTMLをエスケープせずそのまま出力する
- $translateProvider.useSanitizeValueStrategyでエスケープを有効にする必要がある
カスタムディレクティブを使う際の注意点
25
デフォルトでは HTMLエスケープ無効
• SCEの無効化
- $sceProvider.enabled(false)でSCEを無効化できる
- 無効化するとHTMLとリソースURLがサニタイズされなくなるので注意
SCEに関するその他のこと
26
• Content Security Policy(CSP)の利用
- CSPとは、XSSをはじめとする一般的な脆弱性のリスクを軽減するブラウザの保護機能
- CSPを適用する場合、AngularJSのHTMLテンプレートに以下の変更が必要
- ng-appの指定箇所にng-cspを併記する
- https://code.angularjs.org/snapshot/angular-csp.css をロードする
- CSPを有効にすると実行速度が30%低下するそうなので速度が求められる場合は注意
Cross-Site Request Forgery (CSRF)
CSRF発生のメカニズム
28
• 退会APIを例とする。このAPIは退会画面で利用されることを想定している
退会する
本当に退会するの?
退会はこちら
退会処理中です 退会しました
退会APIを実行 CookieでセッションIDが通知される
退会する
本当に退会するの?
退会はこちら
CSRF発生のメカニズム
29
• 退会APIを例とする。このAPIは退会画面で利用されることを想定している
• 罠サイトがAPIを実行した場合、ユーザを意図せず退会させることができる
秘密の画像
♡罠サイト♡ 退会APIを実行
CookieでセッションIDが通知される
退会処理中です 退会しました
被害者
AngularJSのCSRF対策
30
• 正規のページからのリクエストであることを示すトークンをXHRに付加する
退会する
本当に退会するの?
退会はこちら
退会処理中です 退会しました
サーバはXSRF-TOKENという トークン(乱数値)をCookieにセット
AngularJSはトークンをX-XSRF-TOKENという リクエストヘッダに付けて退会API(XHR)を実行
サーバは受信したトークンが 正しい場合のみ退会処理を許可
AngularJSのCSRF対策
31
• 正規のページからのリクエストであることを示すトークンをXHRに付加する
• 有効なトークンが付いていなければ、サーバ側でリクエストを拒否する
不正なリクエスト を検出しました
乱数値が不正なので 退会リクエストを拒否
退会する
本当に退会するの?
退会はこちら
秘密の画像
♡罠サイト♡ 罠サイトはリクエストに正しい 乱数値を付けることができない
被害者
AngularJSでCSRF対策する際の注意点
32
• クロスオリジンのXHRにはX-XSRF-TOKENヘッダが付かない
- $httpや$resourceのリクエストを送信する際に自分でヘッダを付ける
- CORSに基づいてヘッダを受信するための実装がサーバ側にも必要
$scope.method = 'POST';
$scope.url = 'https://api.example.com/resign.php';
$scope.headers = {'X-XSRF-TOKEN': $scope.token };
$http({method: $scope.method, url: $scope.url,
headers:$scope.headers});
リクエストヘッダに有効な トークン値をセットする
※恐らくカスタムヘッダ付きのCORSリクエストはプリフライトを発行してしまうためだと思われます https://github.com/angular/angular.js/issues/1004
謝辞
本資料は竹迫良範様(サイボウズ・ラボ)、佐藤鉄平様(サイボウズ)、
国分裕様(MBSD)に技術レビューをして頂きました。
心より感謝いたします。
お問い合わせはこちら
33