Angular Cross Site Scripting

ID

javascript.angular_cross_site_scripting

Severity

high

Resource

Injection

Language

JavaScript

Tags

Angular, AngularJS, CWE:79, NIST.SP.800-53, OWASP:2021:A3, PCI-DSS:6.5.7

Description

Angular provides protection against Cross-Site Scripting (XSS) attacks, but that protection could be circumvented by developers for various use-cases that require more direct control on the generated content.

This detector reports where Angular XSS protection is bypassed, or direct modification of the native DOM element is performed.

Rationale

Angular has built-in security protections. It treats all values as suspicious and untrusted by default, which is incredibly helpful because the framework automatically guards us against unintentionally creating vulnerabilities in our applications.

When you add values through interpolation in templates using the {{…​}} syntax, Angular automatically escapes the data. Angular considers tags such as form, textarea, button, embed, link, style or template as suspicious and may altogether remove the tag or remove specific attributes/child elements. Angular automatically removes any script tags, for example.

What if you need to bind trusted values that Angular thinks are unsafe? You can mark values as trusted and bypass the security checks, or even directly modify the DOM. Bypassing XSS controls is risky and should be carefully considered.

You can use the DomSanitizer class in @angular/platform-browser, to mark values as safe. The DomSanitizer class offers a sanitization method for four types of contexts: HTML, Style, Url, and ResourceUrl. And to mark the value as trusted and safe to use, use one of the bypassSecurity* methods appropriate for the security context to return a value marked as safe. But be aware of the warning:

calling this method with untrusted user data exposes your application to XSS security risks!

Listing 1. Escaping bypass
import { Component } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

@Component({
  selector: 'app-trustworthy-image',
  template: `
    <section [innerHTML]="html"></section>
  `
})
export class TrustworthyImageComponent {
  // Pretend this comes from an untrusted remote service
  public html = `<img src=1 onerror="alert('Doh!')"/>`;
  public safeHtml: SafeHtml;

  constructor(sanitizer: DomSanitizer) {
    // Vulnerable to XSS
    this.safeHtml = sanitizer.bypassSecurityTrustHtml(this.html);
  }
}
typescript

Alternatively, you can modify the DOM directly, the perfect way to cause security vulnerabilities:

Listing 2. Direct DOM manipulation
import { AfterViewInit, Component, ElementRef, Renderer2, ViewChild } from '@angular/core';

@Component({
  selector: 'app-yikes',
  template: `
    <div #whydothis></div>
  `
})
export class YikesComponent implements AfterViewInit {

  @ViewChild('whydothis') public el!: ElementRef<HTMLElement>;

  // pretend this comes from an untrusted external source
  public attack = '<img src=1 onerror="alert(\'YIKES!\')"';

  constructor(private renderer: Renderer2) { }

  public ngAfterViewInit(): void {
    // VULNERABLE - direct DOM manipulation
    this.el.nativeElement.innerHTML = this.attack;
    // VULNERABLE - same, coded another way
    this.renderer.setProperty(this.el.nativeElement, 'innerHTML', this.attack);
  }
}
typescript

Remediation

Angular is safe against XSS attacks by default, but sometimes one need to bypass the framework’s automatic escaping. General techniques for preventing HTML or JavaScript injection in Angular:

  • Use interpolation with {{ …​ }} to automatically apply escaping by the framework.

  • Use safe property binding such as [href], [src], [style]. Use binding to [innerHTML] to safely insert HTML content.

  • Do not use DomSanitizer.bypassSecurity*() methods unless you fully trust the input. If such bypass is necessary, a previous sanitization based on a whitelist is the best option.

    In case you are absolutely certain that input is trusted, mute vulnerabilities reported on DomSanitizer.bypassSecurity*() calls so they will be ignored in later scans.

  • Avoid modifying the DOM directly, and in particular the innerHTML property. When needed to build out the DOM in code using ElementRef or Renderer2, use DomSanitizer.sanitize() to sanitize user input.

    To fix the vulnerabilities shown before, the code should be modified as follows:

Listing 3. Sanitizing user input before modifying the DOM
import { Component, AfterViewInit, ViewChild, ElementRef, Renderer2 } from '@angular/core';
import { DomSanitizer, SecurityContext } from '@angular/platform-browser';

@Component({
  selector: 'app-way-better',
  template: `
    <div #waybetter></div>
  `
})
export class WayBetterComponent implements AfterViewInit {

  @ViewChild('waybetter') public el!: ElementRef<HTMLElement>;

  // pretend this is from an external source
  public attack = '<img src=1 onerror="alert(\'YIKES!\')"';

  constructor(private renderer: Renderer2, private sanitizer: DomSanitizer) { }

  public ngAfterViewInit(): void {
    // FIXED, the input is sanitized before modyfying the DOM
    const cleaned = this.sanitizer.sanitize(SecurityContext.HTML, this.attack);
    this.renderer.setProperty(this.el.nativeElement, 'innerHTML', cleaned);
  }
}
typescript

Configuration

This detector has no specific configurable parameters.

References