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);
}
}
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
}
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!
}
}
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!')">
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
}
}
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>
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';
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
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>
- 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:
- The backend server creates a special security token (usually named XSRF-TOKEN) and stores it in a cookie when the user loads the page.
- 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',
}),
)
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>
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);
}
}
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
Top comments (0)