There's Safety in Angular

A scary tale

A scary tale

A scary tale

😱

Web vulnerabilities can cause risks to your assets: your application, data, end users, and reputation.
Not to mention the bottom line.

It all adds up to liability

Alisa Duncan

  • Senior Developer Advocate Okta
  • Angular GDE
  • Core Team ngGirls
  • Fan of K-Dramas

Alisa Duncan

  • A regular dev
  • Senior Developer Advocate Okta
  • Angular GDE
  • Core Team ngGirls
  • Fan of K-Dramas

Open Web Application Security Project (OWASP)

Open Web Application Security Project (OWASP)

  1. Broken Access Control
  2. Cryptographic Failures
  3. Injection
  4. Insecure Design
  5. Security Misconfiguration
  6. Vulnerable and Outdated Components
  7. Identification and Authentication Failures
  8. Software and Data Integrity Failures
  9. Security Logging and Monitoring Failures
  10. Server-Side Request Forgery (SSRF)
2021 OWASP Top 10 https://owasp.org/Top10/

Open Web Application Security Project (OWASP)

  1. Broken Access Control
  2. Cryptographic Failures
  3. Injection
  4. Insecure Design
  5. Security Misconfiguration
  6. Vulnerable and Outdated Components
  7. Identification and Authentication Failures
  8. Software and Data Integrity Failures
  9. Security Logging and Monitoring Failures
  10. Server-Side Request Forgery (SSRF)
2021 OWASP Top 10 https://owasp.org/Top10/

Cross-site Scripting (XSS)

Occurs when there's not good data and code boundaries in values, thus allowing the attacker to perform unauthorized actions

Example XSS attack

Example XSS attack

Example XSS attack

<a href="javascript:alert('Yikes!')"> Free prizes here! </a>

Once an attacker can successfully run their script in the app, they can

  • Impersonate you
  • Perform unauthorized actions
  • Read & capture sensitive data, including login credentials

Consider what happens when vulnerabilities exist in critical applications

Consider what happens when the attacked user has elevated access within the application

😰

Watch out for XSS when

  • Poor data hygiene
  • Adding untrusted data via injection sinks

Angular treats all values as untrusted

Security without effort

Escape values

Angular automatically escapes values when using interpolation

                            
                                

{{comment}}

<img src=1 onerror="alert('Boo!')" />

<img src=1 onerror="alert('Boo!')" />

Sanitize values

Angular automatically sanitizes values when property binding to sinks

Injection Sinks

Web API functions that allow us to create dynamic content such as

  • Methods that append to the DOM - innerHTML
  • Approaches to load external resources - src, href, url
  • Event handlers

Sanitize values

<img src=1 onerror="alert('Boo!')" />

Unveiling the Secrets of Sanitization

A peek inside packages/core/src/sanitization

Angular maintains lists of safe elements and attributes


                        private startElement(element: Element): boolean {
                            const tagName = element.nodeName.toLowerCase();
                            if (!VALID_ELEMENTS.hasOwnProperty(tagName)) {
                              this.sanitizedSomething = true;
                              return !SKIP_TRAVERSING_CONTENT_IF_INVALID_ELEMENTS.hasOwnProperty(tagName);
                            }
                            this.buf.push('<');
                            this.buf.push(tagName);
                            const elAttrs = element.attributes;
                            for (let i = 0; i < elAttrs.length; i++) {
                              const elAttr = elAttrs.item(i);
                              const attrName = elAttr!.name;
                              const lower = attrName.toLowerCase();
                              if (!VALID_ATTRS.hasOwnProperty(lower)) {
                                this.sanitizedSomething = true;
                                continue;
                              }
                              let value = elAttr!.value;
                              if (URI_ATTRS[lower]) value = _sanitizeUrl(value);
                              this.buf.push(' ', attrName, '="', encodeEntities(value), '"');
                            }
                            this.buf.push('>');
                            return true;
                          }
                    

Source from packages/core/src/sanitization/html_sanitizer.ts


                        private startElement(element: Element): boolean {
                          
                          if (!VALID_ELEMENTS.hasOwnProperty(tagName)) {
                             // yikes!
                          }

                          // tedious work here

                          foreach attribute {
                            if (!VALID_ATTRS.hasOwnProperty(lower)) {
                              // make note, move on
                            }
                            if (URI_ATTRS[lower]) value = _sanitizeUrl(value);
                          }

                            // finalize tedious work
                        }
                    

Original source from packages/core/src/sanitization/html_sanitizer.ts. My interpretation of what the code does.

<img src=1 onerror="alert('Boo!')" />


                            // Safe Void Elements - HTML5
                            // https://html.spec.whatwg.org/#void-elements
                            const VOID_ELEMENTS = tagSet('area,br,col,hr,img,wbr');
                        

                            // Attributes that have href and hence need to be sanitized
                            export const URI_ATTRS = tagSet(
                                'background,cite,href,itemtype,longdesc,poster,src,xlink:href'
                            );
                        

The onerror attribute is not a safe attribute and is removed.

Source from packages/core/src/sanitization/html_sanitizer.ts

Let's change our exploit to a previous example

<a href="javascript:alert('Yikes!')"> Free prizes here! </a>

                        const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:\/?#]*(?:[\/?#]|$))/i;

                        export function _sanitizeUrl(url: string): string {
                          url = String(url);
                          if (url.match(SAFE_URL_PATTERN)) return url;

                          if (typeof ngDevMode === 'undefined' || ngDevMode) {
                            console.warn(`WARNING: sanitizing unsafe URL value ${url} (see https://g.co/ng/security#xss)`);
                          }

                          return 'unsafe:' + url;
                        }
                    

Source from packages/core/src/sanitization/url_sanitizer.ts

Allow safe markup

<strong>It's a wonderful drama! The best!</strong>

strong is a valid inline element


                        // Inline Elements - HTML5
                        const INLINE_ELEMENTS = merge(
                          OPTIONAL_END_TAG_INLINE_ELEMENTS,
                          tagSet(
                            'a,abbr,acronym,audio,b,' +
                            'bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,picture,q,ruby,rp,rt,s,' +
                            'samp,small,source,span,strike,strong,sub,sup,time,track,tt,u,var,video'
                          )
                        );
                    

Source from packages/core/src/sanitization/html_sanitizer.ts

Angular protects us against mutation XSS (mXSS) 🔍

                        
                            let mXSSAttempts = 5;
                            let parsedHtml = unsafeHtml;
                            
                            do {
                              if (mXSSAttempts === 0) {
                                throw new Error('Failed to sanitize html because the input is unstable');
                              }
                              mXSSAttempts--;
                          
                              unsafeHtml = parsedHtml;
                              parsedHtml = inertBodyElement!.innerHTML;
                              inertBodyElement = inertBodyHelper.getInertBodyElement(unsafeHtml);
                            } while (unsafeHtml !== parsedHtml);
                          
                        
                    

Source is packages/core/src/sanitization/html_sanitizer.ts

Security without effort

  • Using Angular constructs means security without effort
  • Angular does the hard work escaping and sanitizing

Use Angular constructs

Bypassing security

                        
                    
                        
                    
                        
                    
                        
                    

Trust by security context


                        export enum SecurityContext {
                            NONE = 0,
                            HTML = 1,
                            STYLE = 2,
                            SCRIPT = 3,
                            URL = 4,
                            RESOURCE_URL = 5,
                          }
                    

Source from packages/core/src/sanitization/security.ts


                      export abstract class DomSanitizer implements Sanitizer {
                        abstract sanitize(context: SecurityContext, value: SafeValue|string|null): string|null;
                        abstract bypassSecurityTrustHtml(value: string): SafeHtml;
                        abstract bypassSecurityTrustStyle(value: string): SafeStyle;
                        abstract bypassSecurityTrustScript(value: string): SafeScript;
                        abstract bypassSecurityTrustUrl(value: string): SafeUrl;
                        abstract bypassSecurityTrustResourceUrl(value: string): SafeResourceUrl;
                      }
                    

Source from angular/packages/platform-browser/src/security/dom_sanitizer_service.ts

Revealing the magic behind automatic sanitization

Revealing the magic behind automatic sanitization

Angular tracks which security context to use based on the attribute you are binding


                        registerContext(SecurityContext.HTML, [
                          'iframe|srcdoc',
                          '*|innerHTML',
                          '*|outerHTML',
                        ]);

                    

Source from packages/compiler/src/schema/dom_security_schema.ts

With great power comes great responsibility

Broken Access Control - the #1 vulnerability

Cross-site Request Forgery (CSRF)

Occurs when the application shares session cookies to untrusted sources

Example CSRF attack

Example CSRF attack

yikesbank.com/transfer?amt=100&acctNum=12345

Mitigation strategies

  • Protect your cookies using built-in browser capabilities
  • Exchange a CSRF token

Angular helps mitigate CSRF

Use built-in CSRF protection to automatically send a CSRF token value in the API header that the backend can verify


                        

                    

From packages/common/http/src/module.ts

Handling accidental elevation of privilege

Angular gives you the building blocks to implement the access controls you need

Building blocks such as

  • Route guards
  • Structural directives
  • Interceptors

Angular is our soft cozy security blanket

Learn more

SPA Web Security posts
bit.ly/SPAWebSecurity

Code
github.com/alisaduncan/angular-security-code

Secure Coding in Angular
Pluralsight course

Okta Developer resources
Okta Developer blog

@AlisaDuncan

@AlisaDuncan #safeWithAngular