أمن Angular — Sanitizer و Template Injection
Trusted Types، DomSanitizer bypasses، AOT vs JIT، SSR leaks
Angular مختلف بنيوياً
Angular يفرض contextual sanitization: كل قيمة تدخل DOM يحدّد لها Angular السياق (HTML, URL, Style, Script, Resource URL) و يعقّمها وفقاً له. هذا أقوى من React بشكل افتراضي. لكن Angular يعطيك أيضاً أبواباً للتجاوز: bypassSecurityTrust*، [innerHTML]، [srcdoc]، JIT eval، Server-Side Rendering مع Angular Universal.
الـ DomSanitizer — كيف يعمل و كيف يُتجاوز
Angular يصنّف القيم في 5 سياقات:
// خطر — bypass كامل
import { DomSanitizer } from '@angular/platform-browser';
@Component({
template: `<div [innerHTML]="content"></div>`
})
export class CmpComponent {
content: SafeHtml;
constructor(private sanitizer: DomSanitizer) {}
load(userInput: string) {
this.content = this.sanitizer.bypassSecurityTrustHtml(userInput);
// !! لا توجد حماية إطلاقاً !!
}
}
// PoC: userInput = "<img src=x onerror=alert(document.cookie)>"
// الإصلاح
load(userInput: string) {
// إما تستخدم interpolation {{ userInput }} (يهرّب تلقائياً)
// أو DOMPurify مع SafeHtml
this.content = this.sanitizer.bypassSecurityTrustHtml(
DOMPurify.sanitize(userInput)
);
// أو تتجنب innerHTML تماماً
}Template Injection — Angular-specific
Angular template = expression language. لو تستخرج template من user input ثم compile، فأنت تنفّذ كود.
// خطر — Server-Side Template Injection
// تطبيق يولّد components من DB (CMS-like)
@Component({
template: `${userInputFromCMS}`, // !! template أحياناً يحوي JS expressions
})
// PoC في Angular template:
{{constructor.constructor('alert(1)')()}}
// أو
{{$any(1).constructor.constructor('return process')()}} // SSR escape
// JIT compilation runtime (التطبيق غير AOT)
@Component({
selector: 'dynamic',
template: this.userTemplate, // ← compile + run user code
})
// دفاع — استخدم AOT compilation فقط (default في prod من Angular 9+)
// ng build --aot
// JIT compilation معطّل في prodbypassSecurityTrustResourceUrl — الفخ الأعلى خطورة
// خطر — iframe يأخذ URL من user
@Component({
template: `<iframe [src]="vidUrl"></iframe>`
})
export class VideoCmp {
vidUrl: SafeResourceUrl;
constructor(private s: DomSanitizer) {}
setVideo(url: string) {
this.vidUrl = this.s.bypassSecurityTrustResourceUrl(url);
}
}
// PoC: setVideo("javascript:alert(document.cookie)")
// أو data:text/html,<script>...
// الإصلاح
setVideo(url: string) {
// allowlist صارمة
const u = new URL(url);
if (u.hostname !== 'youtube.com' || !/^\/embed\//.test(u.pathname)) {
return;
}
this.vidUrl = this.s.bypassSecurityTrustResourceUrl(u.toString());
}Server-Side Rendering — Angular Universal
Angular Universal يُشغّل الكود على Node.js للـ SSR. كل ثغرة Node تنطبق + ثغرات خاصة بالـ rendering:
- Hydration mismatch — كـ React، يكشف server-side state.
- SSR XSS — لو يستخدم document.write أو manipulate DOM في server context، النتيجة قد تشمل user input بدون escape.
- Resource leaks — كل request ينشئ Angular module جديد. استدعاء CPU-heavy على demand قد يُسقط الخادم.
- Secrets exposure — initial state يُطبع في HTML (TransferState). أي شيء fetch قبل client يصل = visible في source.
// خطر — TransferState يحوي بيانات حسّاسة
constructor(
private state: TransferState,
private http: HttpClient
) {
const KEY = makeStateKey<User>('user-data');
this.user = this.state.get(KEY, null);
if (!this.user) {
this.http.get<User>('/api/me').subscribe(u => {
this.state.set(KEY, u); // !! يُطبع في HTML
});
}
}
// User يحوي email, internalRoles, ...
// كل visitor يقرأ HTML source يرى البيانات
// آمن — DTO فقط للـ TransferState، لا entities كاملة
const safeUser = { id: u.id, name: u.name };
this.state.set(KEY, safeUser);Trusted Types — تفعيل قوي في Angular
Angular 16+ يدعم Trusted Types بسهولة. مع CSP require-trusted-types-for 'script'، أي bypass يُحظر بالـ browser.
// app.module.ts أو main.ts
import { TrustedTypesModule } from '@angular/platform-browser';
// CSP header
// Content-Security-Policy: require-trusted-types-for 'script'; trusted-types angular angular#unsafe-bypass;
// لو حاول كود في bundle أن يضع innerHTML بـ string عادي → throw
// فقط Angular sanitizer (الذي ينتج TrustedHTML) يعملRouting — Open Redirect و Route Guards
// خطر — redirect بعد login
loginCmp() {
this.auth.login(...).subscribe(() => {
const next = this.route.snapshot.queryParams['returnUrl'];
this.router.navigateByUrl(next); // attacker controls
});
}
// PoC: /login?returnUrl=//evil.com
// آمن
loginCmp() {
this.auth.login(...).subscribe(() => {
let next = this.route.snapshot.queryParams['returnUrl'] || '/';
if (!next.startsWith('/') || next.startsWith('//')) next = '/';
this.router.navigateByUrl(next);
});
}
// Route Guards — احرص على CanActivate و CanActivateChild
const routes: Routes = [
{
path: 'admin',
canActivate: [AdminGuard],
canActivateChild: [AdminGuard], // ← لا تنسَ children!
children: [...]
}
];
// AdminGuard يعتمد على service موثوق، ليس localStorage مباشرة
@Injectable({ providedIn: 'root' })
export class AdminGuard implements CanActivate {
constructor(private auth: AuthService, private router: Router) {}
canActivate(): boolean {
if (!this.auth.isAdmin()) {
this.router.navigate(['/forbidden']);
return false;
}
return true;
}
}HttpClient — XSRF & Interceptors
Angular يحوي حماية CSRF مدمجة عبر HttpClientXsrfModule. تقرأ cookie XSRF-TOKEN و تضيفها في header X-XSRF-TOKEN.
@NgModule({
imports: [
HttpClientModule,
HttpClientXsrfModule.withOptions({
cookieName: 'XSRF-TOKEN',
headerName: 'X-XSRF-TOKEN',
})
]
})
// الخادم يضبط cookie XSRF-TOKEN عند login
// يرفض كل non-GET بدون header X-XSRF-TOKEN
// خطر شائع — Interceptor يضيف Authorization من localStorage
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler) {
const token = localStorage.getItem('token'); // XSS = سرقة
return next.handle(req.clone({
setHeaders: { Authorization: `Bearer ${token}` }
}));
}
}
// أفضل: cookie httpOnly + إعادة الـ XSRF token في كل non-GETForms — Validation وReactive Forms
- Validation سيرفر-جانب إجباري — Angular validators تجميلي فقط، يمكن تجاوزه ببساطة في DevTools.
- FormControl values — أي one-way binding من user input إلى model، احفظ نسخة DTO مفلترة.
- Custom validators async — لا تستخدم eval أو ديناميكي يأتي من API.
- File upload — كما في React/Express، فحص MIME + حجم + extension + scan.
JIT vs AOT — لماذا AOT حماية
- JIT — templates تُجمع وقت runtime في المتصفح. أبطأ + يعرّض لكثير من ثغرات template injection.
- AOT — templates تُجمع وقت البناء. أسرع + يكتشف أخطاء template في build، و templates ديناميكية تصبح مستحيلة.
- منذ Angular 9 (Ivy)، AOT افتراضي. تأكد أن ng build --configuration=production يستخدم AOT (هو كذلك).
- تجنّب @Component({ jit: true }) أو JitCompilerFactory في prod.
Supply chain — Angular ecosystem
- npm + @angular/* mirror — حدّث Angular بانتظام. إصدار جديد كل 6 أشهر، LTS سنة.
- schematics من شركة ثالثة — ng add ينفّذ كود. تحقّق من المصدر.
- Material / PrimeNG / NG-Zorro — مكوّنات UI كبيرة، تاريخ CVEs (XSS في tooltip, popover). حدّث بانتظام.
- SystemJS / esm.sh في dev — لا تستخدمها في prod.
CSP صارم لـ Angular
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{rand}'; ← Angular لا يحتاج unsafe-inline في AOT
style-src 'self' 'unsafe-inline'; ← ngStyle يستخدم inline
img-src 'self' data: https:;
font-src 'self' data:;
connect-src 'self' https://api.target.gov;
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
require-trusted-types-for 'script';
trusted-types angular;checklist مراجعة Angular app
- كل bypassSecurityTrust* له justification + DOMPurify.
- كل [innerHTML] يأتي من مصدر موثوق أو يمر عبر sanitization.
- AOT في prod (افحص angular.json).
- HttpClientXsrfModule مفعّل + الخادم يحقّق X-XSRF-TOKEN.
- كل route admin/protected له CanActivate و CanActivateChild.
- Trusted Types CSP مفعّل.
- لا tokens في localStorage — httpOnly cookies.
- TransferState لا يحوي PII أو internal flags.
- SSR (Universal): timeouts على HTTP، لا synchronous heavy work.
- Open redirect filter في كل navigateByUrl من user input.
- Forms: server-side validation كاملة، لا تثق في Angular validators.
- Material/PrimeNG محدّث، CVEs محلولة.