29 KiB
Angular(アンギュラー)
チェックリスト
以下を確認してください:
- Angularはクライアントサイドのフレームワークと見なされ、サーバーサイドの保護を提供することは期待されていません
- プロジェクトの設定でスクリプトのソースマップが無効になっていることを確認する
- 信頼できないユーザー入力は、テンプレートで使用される前に常に補間またはサニタイズされていることを確認する
- ユーザーはサーバーサイドまたはクライアントサイドのテンプレートを制御できないことを確認する
- 信頼できないユーザー入力は、アプリケーションに信頼される前に適切なセキュリティコンテキストを使用してサニタイズされていることを確認する
BypassSecurity*
メソッドは信頼できない入力と一緒に使用されていないことを確認する- 信頼できないユーザー入力は、
ElementRef
、Renderer2
、Document
などのAngularクラスや他のJQuery/DOMのシンクに渡されていないことを確認する
Angularとは
Angularは、ダイナミックなWebアプリケーションを構築するために広く使用されている強力なフロントエンドフレームワークです。これはオープンソースであり、Googleによってメンテナンスされています。Angularの主な特徴の1つは、TypeScriptの使用です。TypeScriptはJavaScriptの型付けされた上位セットであり、コードの読みやすさ、保守性、デバッグが容易になります。
Angularのセキュリティメカニズムは、クロスサイトスクリプティング(XSS)やオープンリダイレクトなどの一般的なクライアントサイドの脆弱性を防ぐように設計されています。ただし、Angularはサーバーサイドでも使用され、静的ページを生成するために使用されることもあります。したがって、Angularのセキュリティは両方の側面から考慮する必要があります。
フレームワークのアーキテクチャ
Angularの基本をよりよく理解するために、重要なコンセプトを見ていきましょう。
一般的なAngularプロジェクトは通常、以下のような構造をしています:
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のソースマップの設定
Angularフレームワークは、tsconfig.json
のオプションに従ってTypeScriptファイルをJavaScriptコードに変換し、angular.json
の設定でプロジェクトをビルドします。angular.json
ファイルを見ると、ソースマップを有効または無効にするオプションがあることがわかりました。Angularのドキュメントによると、デフォルトの設定ではスクリプト用のソースマップファイルが有効になっており、デフォルトでは非表示になっていません。
"sourceMap": {
"scripts": true,
"styles": true,
"vendor": false,
"hidden": false
}
一般的に、ソースマップファイルはデバッグ目的で使用され、生成されたファイルを元のファイルにマッピングします。そのため、本番環境では使用しないことが推奨されています。ソースマップが有効になっている場合、Angularプロジェクトの元の状態を再現することで、可読性が向上し、ファイルの分析が容易になります。ただし、無効になっている場合、セキュリティに関するパターンを手動で検索することで、コンパイルされたJavaScriptファイルをレビューすることができます。
さらに、AngularプロジェクトのコンパイルされたJavaScriptファイルは、ブラウザの開発者ツール→ソース(またはデバッガとソース)→[id].main.jsにあります。有効なオプションに応じて、このファイルの末尾に次の行が含まれている場合があります //# sourceMappingURL=[id].main.js.map
または、hidden オプションが true に設定されている場合は含まれていない場合もあります。ただし、scripts のソースマップが無効になっている場合、テストはより複雑になり、ファイルを取得することはできません。さらに、ng build --source-map
のようにプロジェクトビルド中にソースマップを有効にすることもできます。
データバインディング
バインディングとは、コンポーネントとそれに対応するビュー間の通信プロセスを指します。これは、データをAngularフレームワークに送受信するために使用されます。データは、イベント、補間、プロパティ、または双方向バインディングメカニズムを介してさまざまな手段で渡すことができます。さらに、データは関連するコンポーネント(親子関係)やService機能を使用して、2つの関連のないコンポーネント間で共有することもできます。
データフローによってバインディングを分類することができます:
- データソースからビューターゲットへ(補間、プロパティ、属性、クラス、_スタイル_を含む);テンプレート内で
[]
または{{}}
を使用して適用できます。 - ビューターゲットからデータソースへ(_イベント_を含む);テンプレート内で
()
を使用して適用できます。 - 双方向;テンプレート内で
[()]
を使用して適用できます。
バインディングは、プロパティ、イベント、属性、およびソースディレクティブの任意の公開メンバーに対して呼び出すことができます:
タイプ | ターゲット | 例 |
---|---|---|
プロパティ | 要素のプロパティ、コンポーネントのプロパティ、ディレクティブのプロパティ | <img [alt]="hero.name" [src]="heroImageUrl"> |
イベント | 要素のイベント、コンポーネントのイベント、ディレクティブのイベント | <button type="button" (click)="onSave()">Save |
双方向 | イベントとプロパティ | <input [(ngModel)]="name"> |
属性 | 属性(例外) | <button type="button" [attr.aria-label]="help">help |
クラス | クラスのプロパティ | <div [class.special]="isSpecial">Special |
スタイル | スタイルのプロパティ | <button type="button" [style.color]="isSpecial ? 'red' : 'green'"> |
Angularのセキュリティモデル
Angularの設計には、デフォルトですべてのデータのエンコードまたはサニタイズが含まれており、AngularプロジェクトでXSSの脆弱性を発見して悪用することがますます困難になっています。データの処理には2つの異なるシナリオがあります:
- 補間または
{{user_input}}
- コンテキストに応じたエンコードを実行し、ユーザーの入力をテキストとして解釈します。
//app.component.ts
test = "<script>alert(1)</script><h1>test</h1>";
//app.component.html
{{test}}
結果:<script>alert(1)</script><h1>test</h1>
2. プロパティ、属性、クラス、スタイルにバインディングする場合、 [attribute]="user_input"
- 提供されたセキュリティコンテキストに基づいてサニタイズが実行されます。
//app.component.ts
test = "<script>alert(1)</script><h1>test</h1>";
//app.component.html
<div [innerHtml]="test"></div>
結果:<div><h1>test</h1></div>
SecurityContext
には6つのタイプがあります:
None
;HTML
- 値をHTMLとして解釈する場合に使用されます;STYLE
-style
プロパティにCSSをバインディングする場合に使用されます;URL
-<a href>
などのURLプロパティに使用されます;SCRIPT
- JavaScriptコードに使用されます;RESOURCE_URL
-<script src>
などでコードとしてロードおよび実行されるURLとして使用されます。
脆弱性
Bypass Security Trustメソッド
Angularでは、デフォルトのサニタイズプロセスをバイパスし、特定のコンテキストで値を安全に使用できることを示すためのメソッドのリストが導入されています。以下の5つの例に示すように:
bypassSecurityTrustUrl
は、指定された値が安全なスタイルURLであることを示すために使用されます:
//app.component.ts
this.trustedUrl = this.sanitizer.bypassSecurityTrustUrl('javascript:alert()');
//app.component.html
<a class="e2e-trusted-url" [href]="trustedUrl">Click me</a>
//result
<a _ngcontent-pqg-c12="" class="e2e-trusted-url" href="javascript:alert()">Click me</a>
bypassSecurityTrustResourceUrl
は、指定された値が安全なリソースURLであることを示すために使用されます:
//app.component.ts
this.trustedResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl("https://www.google.com/images/branding/googlelogo/1x/googlelogo_light_color_272x92dp.png");
//app.component.html
<iframe [src]="trustedResourceUrl"></iframe>
//result
<img _ngcontent-nre-c12="" src="https://www.google.com/images/branding/googlelogo/1x/googlelogo_light_color_272x92dp.png">
bypassSecurityTrustHtml
は、指定された値が安全なHTMLであることを示すために使用されます。この方法でDOMツリーにscript
要素を挿入しても、これらの要素がDOMツリーに追加される方法のため、囲まれたJavaScriptコードは実行されません。
//app.component.ts
this.trustedHtml = this.sanitizer.bypassSecurityTrustHtml("<h1>html tag</h1><svg onclick=\"alert('bypassSecurityTrustHtml')\" style=display:block>blah</svg>");
//app.component.html
<p style="border:solid" [innerHtml]="trustedHtml"></p>
//result
<h1>html tag</h1>
<svg onclick="alert('bypassSecurity
### HTMLインジェクション
この脆弱性は、ユーザーの入力が`innerHTML`、`outerHTML`、または`iframe`の`srcdoc`のいずれかのプロパティにバインドされた場合に発生します。これらの属性にバインドすると、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 = "<script>alert(1)</script><h1>test</h1>";
}
//app.component.html
<div [innerHTML]="test"></div>
結果は <div><h1>test</h1></div>
です。
テンプレートインジェクション
クライアントサイドレンダリング(CSR)
Angularはテンプレートを使用してページを動的に構築します。このアプローチでは、Angularが評価するために二重の中括弧({{}}
)で囲まれたテンプレート式を使用します。このようにすることで、フレームワークは追加の機能を提供します。たとえば、{{1+1}}
というテンプレートは2と表示されます。
通常、Angularはテンプレート式と混同される可能性のあるユーザーの入力をエスケープします(たとえば、`< > ' " ``などの文字)。これは、ブラックリストに登録された文字を使用しないために、JavaScriptの文字列オブジェクトを生成する関数を利用するなど、この制限を回避するための追加の手順が必要であることを意味します。ただし、これを実現するには、Angularのコンテキスト、プロパティ、および変数を考慮する必要があります。したがって、テンプレートインジェクション攻撃は次のように表示される場合があります:
//app.component.ts
const _userInput = '{{constructor.constructor(\'alert(1)\'()}}'
@Component({
selector: 'app-root',
template: '<h1>title</h1>' + _userInput
})
上記のように、constructor
はオブジェクトのconstructor
プロパティのスコープを指します。これにより、Stringコンストラクタを呼び出して任意のコードを実行することができます。
サーバーサイドレンダリング(SSR)
CSRとは異なり、Angular UniversalはテンプレートファイルのSSRを担当し、これらのファイルはユーザーに配信されます。ただし、この違いにもかかわらず、Angular UniversalはCSRで使用される同じサニタイズメカニズムを適用してSSRのセキュリティを強化します。SSRにおけるテンプレートインジェクションの脆弱性は、CSRと同じように検出することができます。なぜなら、使用されるテンプレート言語が同じだからです。
もちろん、PugやHandlebarsなどのサードパーティのテンプレートエンジンを使用する場合、新たなテンプレートインジェクションの脆弱性が発生する可能性もあります。
XSS
DOMインターフェース
前述のように、Document
インターフェースを使用して直接DOMにアクセスすることができます。ユーザーの入力が事前に検証されていない場合、クロスサイトスクリプティング(XSS)の脆弱性が発生する可能性があります。
以下の例では、document.write()
とdocument.createElement()
メソッドを使用しました:
//app.component.ts 1
import { Component} from '@angular/core';
@Component({
selector: 'app-root',
template: ''
})
export class AppComponent{
constructor () {
document.open();
document.write("<script>alert(document.domain)</script>");
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つのクラスの詳細な説明は、オープンリダイレクトセクションで説明されています。最初の2つの違いは、Renderer2
APIがDOM要素とコンポーネントコードの間に抽象化レイヤーを提供するのに対し、ElementRef
は要素への参照を保持するだけです。したがって、Angularのドキュメントによれば、ElementRef
APIはDOMへの直接アクセスが必要な場合にのみ使用するべきです。
ElementRef
には、DOM要素を操作するために使用できるnativeElement
というプロパティが含まれています。ただし、nativeElement
の不適切な使用はXSSインジェクションの脆弱性を引き起こす可能性があります。以下に示すように:
//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防止メカニズムがありません。
//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
<img #img>
<button (click)="setAttribute()">Click me!</button>
- DOM要素のプロパティを設定するには、
Renderer2.setProperty()
メソッドを使用してXSS攻撃をトリガーすることができます。
//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', '<img src=1 onerror=alert(1)>');
}
}
//app.component.html
<a #a></a>
<button (click)="setProperty()">Click me!</button>
私たちの調査中、XSSおよびCSSインジェクションに関連する他のRenderer2
メソッド(setStyle()
、createComment()
、setValue()
など)の動作も調べました。しかし、これらのメソッドに対して有効な攻撃ベクトルを見つけることはできませんでした。
jQuery
jQueryは、AngularプロジェクトでHTML DOMオブジェクトの操作を補助するために使用できる高速で小さくて機能豊富なJavaScriptライブラリです。ただし、このライブラリのメソッドはXSSの脆弱性を引き起こす可能性があることが知られています。このサブセクションでは、いくつかの脆弱なjQueryメソッドがAngularプロジェクトでどのように悪用されるかについて説明します。
html()
メソッドは、一致した要素セットの最初の要素のHTMLコンテンツを取得するか、すべての一致した要素のHTMLコンテンツを設定します。ただし、設計上、HTML文字列を受け入れる任意のjQueryコンストラクタまたはメソッドは、コードを実行する可能性があります。これは、<script>
タグのインジェクションやコードを実行するHTML属性の使用によって発生する可能性があります。
//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()
{
$("p").html("<script>alert(1)</script>");
});
}
}
//app.component.html
<button>Click me</button>
<p>some text here</p>
jQuery.parseHTML()
メソッドは、文字列をDOMノードのセットに変換するためにネイティブメソッドを使用し、それをドキュメントに挿入することができます。
jQuery.parseHTML(data [, context ] [, keepScripts ])
前述のように、HTML文字列を受け入れるほとんどのjQuery APIは、HTMLに含まれるスクリプトを実行します。ただし、jQuery.parseHTML()
メソッドは、keepScripts
が明示的にtrue
でない限り、解析されたHTML内のスクリプトを実行しません。ただし、ほとんどの環境では、<img onerror>
属性を介してスクリプトを間接的に実行することができます。
//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 = "<img src=1 onerror=alert(1)>",
html = $.parseHTML(str),
nodeNames = [];
$palias.append(html);
});
}
}
//app.component.html
<button>Click me</button>
<p id="palias">some text</p>
オープンリダイレクト
DOMインターフェース
W3Cのドキュメントによると、window.location
とdocument.location
オブジェクトは、モダンなブラウザではエイリアスとして扱われます。そのため、いくつかのメソッドとプロパティの実装が類似しているため、javascript://
スキーマ攻撃によるオープンリダイレクトとDOM XSSが発生する可能性があります。
window.location.href
(およびdocument.location.href
)
現在のDOMの場所オブジェクトを取得するための正規の方法は、window.location
を使用することです。また、ブラウザを新しいページにリダイレクトするためにも使用できます。そのため、このオブジェクトを制御できると、オープンリダイレクトの脆弱性を悪用することができます。
//app.component.ts
...
export class AppComponent {
goToUrl(): void {
window.location.href = "https://google.com/about"
}
}
//app.component.html
<button type="button" (click)="goToUrl()">Click me!</button>
以下のシナリオに対して、悪用プロセスは同じです。
window.location.assign()
(およびdocument.location.assign()
)
このメソッドは、ウィンドウを指定されたURLのドキュメントをロードして表示します。このメソッドを制御できる場合、オープンリダイレクト攻撃のシンクになる可能性があります。
//app.component.ts
...
export class AppComponent {
goToUrl(): void {
window.location.assign("https://google.com/about")
}
}
window.location.replace()
(およびdocument.location.replace()
)
このメソッドは
Angular クラス
- Angularのドキュメントによると、Angularの
Document
はDOMのdocumentと同じであり、Angularでクライアントサイドの脆弱性を悪用するために一般的なベクターをDOMのdocumentに使用することが可能です。Document.location
のプロパティとメソッドは、以下の例に示すように、成功したオープンリダイレクト攻撃のためのシンクスとなる可能性があります。
//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
<button type="button" (click)="goToUrl()">Click me!</button>
- 調査フェーズでは、Angularの
Location
クラスもオープンリダイレクトの脆弱性について調査しましたが、有効なベクターは見つかりませんでした。Location
は、アプリケーションがブラウザの現在のURLとやり取りするために使用できるAngularのサービスです。このサービスには、与えられたURLを操作するためのいくつかのメソッドがあります -go()
、replaceState()
、prepareExternalUrl()
。しかし、これらを外部ドメインへのリダイレクトに使用することはできません。例えば:
//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
クラスは、主に同じドメイン内でのナビゲーションに使用され、アプリケーションに追加の脆弱性を導入しません:
//app-routing.module.ts
const routes: Routes = [
{ path: '', redirectTo: 'https://google.com', pathMatch: 'full' }]
結果:http://localhost:4200/https:
以下のメソッドもドメインの範囲内でナビゲーションを行います:
const routes: Routes = [ { path: '', redirectTo: 'ROUTE', pathMatch: 'prefix' } ]
this.router.navigate(['PATH'])
this.router.navigateByUrl('URL')
参考文献
- Angular
- Angular Security: The Definitive Guide (Part 1)
- Angular Security: The Definitive Guide (Part 2)
- Angular Security: The Definitive Guide (Part 3)
- Angular Security: Checklist
- Workspace and project file structure
- Introduction to components and templates
- Source map configuration
- Binding syntax
- Angular Context: Easy Data-Binding for Nested Component Trees and the Router Outlet
- Sanitization and security contexts
- GitHub - angular/dom_security_schema.ts
- XSS in Angular and AngularJS
- Angular Universal
- DOM XSS
- Angular ElementRef
- Angular Renderer2
- Renderer2 Example: Manipulating DOM in Angular - TekTutorialsHub
- jQuery API Documentation
- How To Use jQuery With Angular (When You Absolutely Have To)
- Angular Document
- Angular Location
- Angular Router