DEV Community

Cover image for Common Security Vulnerabilities in Angular Applications and How to Fix Them
bytebantz
bytebantz

Posted on

1

Common Security Vulnerabilities in Angular Applications and How to Fix Them

In this article, we’ll explore common security issues in Angular applications, how attackers exploit them, and how to secure your code.

1. Cross-Site Scripting (XSS)

XSS occurs when an application allows malicious scripts to execute in the browser.

Angular sanitization is a built-in mechanisms that checks untrusted content and ensures it’s safe for the DOM.

Angular cleans HTML by removing dangerous tags like <script>, blocks unsafe URLs (javascript: schemes), and prevents harmful CSS.

Angular automatically sanitizes values bound [innerHTML], <a [href]>, <img [src]>, and [style] to protect your app.

However, you can still introduce vulnerabilities if you intentionally bypass security mechanisms or manipulate the DOM directly.

Here are some examples of vulnerable code:

Vulnerability 1: Using bypassSecurityTrustHtml Unsafely

The bypassSecurityTrustHtml method forces Angular to trust the provided HTML, including unsafe content like <script> tags.

Angular’s DomSanitizer can be misused to allow malicious scripts.

Vulnerable Code:

import { Component } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

@Component({
  selector: 'app-inner-html-binding',
  template: `<div [innerHTML]="trustedHtmlSnippet"></div>`,
})
export class InnerHtmlBindingComponent {
  trustedHtmlSnippet: SafeHtml;

  constructor(private sanitizer: DomSanitizer) {
    const potentiallyUnsafe = 'Template <script>alert("0wned")</script>';
    this.trustedHtmlSnippet = this.sanitizer.bypassSecurityTrustHtml(potentiallyUnsafe);
  }
}
Enter fullscreen mode Exit fullscreen mode

Safer Approach:

Instead of bypassing security, let Angular sanitize the HTML automatically:

import { Component } from '@angular/core';

@Component({
  selector: 'app-inner-html-binding',
  template: `<div [innerHTML]="safeHtmlSnippet"></div>`,
})
export class InnerHtmlBindingComponent {
  safeHtmlSnippet = 'Template <script>alert("0wned")</script>'; // Angular will sanitize this automatically
}
Enter fullscreen mode Exit fullscreen mode

Vulnerability 2: Directly Manipulating the DOM (ElementRef)

Angular’s built-in XSS protection only works in templates, but direct DOM manipulation bypasses it.

Using ElementRef and innerHTML allows unsafe content insertion.

Vulnerable Code:

import { Component, ElementRef, ViewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-root',
  imports: [FormsModule],
  template: `
    <input [(ngModel)]="comment" placeholder="Enter your comment">
    <button (click)="postComment()">Post Comment</button>
    <div #commentBox></div>
  `,
})
export class AppComponent {
  @ViewChild('commentBox', { static: true }) commentBox!: ElementRef;
  comment: string = '';

  postComment() {
    this.commentBox.nativeElement.innerHTML = this.comment; // ⚠ Vulnerable to XSS!
  }
}

Enter fullscreen mode Exit fullscreen mode

By default, modern browsers block scripts inserted via innerHTML (they won't execute <script> tags).

However, attackers can still exploit XSS without using <script> tags by injecting malicious event handlers like onerror, onclick, or onmouseover.

<img src="x" onerror="alert('Hacked!')">

Enter fullscreen mode Exit fullscreen mode

Since the app is directly inserting user input into the DOM, if a user submits the comment above, the <img> tag loads, fails (because src="x" is invalid), and triggers the onerror JavaScript, showing an alert box. This is a Cross-Site Scripting (XSS) attack.

Fix: Use Angular’s Sanitization

To avoid XSS, use [innerHTML] instead of ElementRef.innerHTML:

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-root',
  imports: [FormsModule],
  template: `
    <input [(ngModel)]="comment" placeholder="Enter your comment">
    <button (click)="postComment()">Post Comment</button>
    <div [innerHTML]="safeComment"></div>
  `,
})
export class AppComponent {
  comment: string = '';
  safeComment: string = '';

  postComment() {
    this.safeComment = this.comment; // Angular sanitizes input automatically
  }
}

Enter fullscreen mode Exit fullscreen mode

2. Clickjacking

Clickjacking is when someone tricks you into clicking on something different from what you think you're clicking on.

For example, they might overlay a malicious button on top of a legitimate one, so when you click, you're actually interacting with something you didn't intend to.

Attackers can embed your Angular app inside an <iframe> and overlay a fake UI to steal user inputs.

To check if your website can be embedded in an <iframe>, create an HTML file (e.g., clickjack-test.html) and add this code:

<iframe src="https://your-angular-app.com" width="800" height="600"></iframe>

Enter fullscreen mode Exit fullscreen mode

Example of Clickjacking in Action:

1. The Fake Job Ad

  • A scammer creates a webpage claiming: "Earn $500 per day! Click below to apply!"
  • This fake ad targets people searching for jobs.

2. The Clickjacking Trick

  • The fake page loads a real job posting inside an invisible iframe.
  • A hidden "Like" or "Follow" button is placed over the real "Apply Now" button.
  • When the user clicks "Apply Now," they actually like or follow a scam page instead.

3. The Redirection to Avoid Suspicion

  • After the hidden click happens, the user is redirected to the real job listing.
  • The victim sees a legitimate job post, making them think everything is normal.
  • They never realize they just followed a scam page!

Why Scammers Use Clickjacking:

  • They collect thousands of likes or follows for credibility.
  • They post fake job offers to lure victims.
  • Users click on scam links, thinking they are real opportunities.
  • Some are tricked into paying fake "registration fees."

Secure Fix

Angular does not have a specific built-in mechanism solely dedicated to preventing clickjacking out of the box.

Set X-Frame-Options and Content-Security-Policy headers in the backend:

X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none';
Enter fullscreen mode Exit fullscreen mode

This prevents clickjacking by blocking the site from being embedded in iframes.

3. Cross-Site Request Forgery (CSRF)

CSRF is an attack where a malicious website tricks a user's browser into sending unintended requests to a different website where the user is authenticated.

CSRF attacks are possible because web browsers automatically include cookies, including session cookies that contain authentication information, in every request

If the server does not validate the request properly (e.g., checking for an anti-CSRF token or validating the Origin header), an attacker can still trick a logged-in user into making unintended changes.

Imagine you have an account on FastPizza (https://fastpizza.com), where you order food regularly.

How It Works Normally:

You log in and go to Settings → Change Email, where you enter a new email.

The website sends this request:

POST https://fastpizza.com/update-email
Content-Type: application/x-www-form-urlencoded

email=mynewemail@example.com
Enter fullscreen mode Exit fullscreen mode

The Attack (CSRF Exploit)

You are already logged into FastPizza in one tab.

You then visit a malicious website, maybe a recipe blog or a free giveaway site.

The hacker creates this hidden form into the page:

<form action="https://fastpizza.com/update-email" method="POST">
    <input type="hidden" name="email" value="hacker@example.com">
</form>
<script>
    document.forms[0].submit(); // Automatically submits the form when the victim visits
</script>

Enter fullscreen mode Exit fullscreen mode
  • If the victim is logged in, their session cookie is sent automatically.
  • The server processes the request and updates the email without any additional verification.
  • Now, the attacker's email is linked to the victim’s account.

Secure Fix

Use CSRF tokens in HTTP requests:

Angular's HttpClient helps prevent these attacks using a token system:

  1. The backend server creates a special security token (usually named XSRF-TOKEN) and stores it in a cookie when the user loads the page.
  2. Angular automatically reads this token and includes it in the request headers under X-XSRF-TOKEN.

If it’s not working automatically, create an Interceptor to manually extract the token.

3.The backend checks if the request contains a valid token before allowing it to change data.

If your backend uses different token names, you can configure Angular to use them:

provideHttpClient(
  withXsrfConfiguration({
    cookieName: 'CUSTOM_XSRF_TOKEN',
    headerName: 'X-Custom-Xsrf-Header',
  }),
)
Enter fullscreen mode Exit fullscreen mode

4. DOM Clobbering

DOM Clobbering is an attack where an attacker manipulates the DOM properties to override JavaScript variables or elements, causing unintended behavior.

Imagine a user is trying to update their password on a website. If the frontend is vulnerable to DOM Clobbering, an attacker can inject their own password, causing the victim’s password to be changed to one controlled by the attacker.

Vulnerable Code:

<form id="updatePasswordForm">
  <input type="password" name="newPassword" placeholder="Enter new password" />
  <input type="password" name="confirmPassword" placeholder="Confirm new password" />
  <button type="submit">Update Password</button>
</form>

<script>
  const newPasswordInput = document.querySelector('input[name="newPassword"]');

  document.getElementById('updatePasswordForm').addEventListener('submit', function(event) {
    event.preventDefault();
    console.log('Updating password to:', newPasswordInput.value);
  });
</script>
Enter fullscreen mode Exit fullscreen mode

Malicious Injection

An attacker injects the following malicious hidden input field into the page:

Since JavaScript automatically associates elements with global variables, document.getElementById('newPassword') now points to the hidden field instead of the real input.

Instead of their chosen password, "hackedPassword123" is sent to the server.

The attacker can now log in using the victim’s account.

Secure Fix Using Angular Reactive Forms

To prevent this attack, avoid querySelector() and use Angular’s Form Controls to securely reference inputs.

import { Component } from '@angular/core';
import { ReactiveFormsModule, FormControl, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="passwordForm" (ngSubmit)="onSubmit()">
      <input type="password" formControlName="newPassword" placeholder="Enter new password">
      <input type="password" formControlName="confirmPassword" placeholder="Confirm new password">
      <button type="submit">Update Password</button>
    </form>
  `,
})
export class AppComponent {
  passwordForm = new FormGroup({
    newPassword: new FormControl('', [Validators.required]),
    confirmPassword: new FormControl('', [Validators.required])
  });

  onSubmit() {
    console.log('Updating password to:', this.passwordForm.get('newPassword')?.value);
  }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

By understanding these vulnerabilities and implementing Angular’s built-in security mechanisms and recommended practices, developers can significantly enhance the security posture of their applications.

Check out my deep dives on → Gumroad

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

If this post resonated with you, feel free to hit ❤️ or leave a quick comment to share your thoughts!

Okay