Angular Security — Sanitizer مش حصن
Trusted Types و DomSanitizer bypasses و AOT vs JIT و SSR leaks
Angular مختلف من جذره
Angular مش زي React في الموضوع ده.
Angular بيفرض contextual sanitization by default — كل قيمة بتدخل الـ DOM، Angular بيعرف هي رايحة فين (HTML, URL, Style, Script, Resource URL) وبيعقمها على الأساس ده.
طب فين الخرّامة؟
في الأبواب الجانبية اللي Angular نفسه فاتحها: bypassSecurityTrust*.
- طب يا حضرتك، Angular أمن من React.. إيه اللي يخليني أقلق؟
يا مستجد. Angular فعلاً بيدّيك حماية افتراضية أحسن. بس بيدّيك في نفس الوقت باب جانبي اسمه واضح: bypassSecurityTrustHtml. والمبرمجين بيستخدموه عشان "الـ warning يسكت". اليوم اللي حد كتب السطر ده من غير ما يفهمه — ده اليوم اللي الحماية فيه راحت.
الـ 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
الـ 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 — أخطر فخ في Angular
// خطر — 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());
}SSR — Angular Universal
Angular Universal بيشغل الكود على Node.js عشان الـ SSR. كل ثغرات Node بتنطبق هنا، وبتيجي معاها ثغرات خاصة بالـ rendering:
- Hydration mismatch — زي React، بيكشف الـ server-side state.
- SSR XSS — لو الكود بيستخدم document.write أو بيلعب في الـ DOM في context السيرفر، الـ user input ممكن يدخل من غير escape.
- Resource leaks — كل request بينشئ Angular module جديد. شغل CPU ثقيل على demand = السيرفر بيقع.
- Secrets exposure — الـ initial state بينطبع جوه الـ HTML (TransferState). أي حاجة fetched قبل ما الـ client يستلم = ظاهرة في الـ 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 بيتلغي على مستوى المتصفح نفسه.
// 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
- Server-side validation إجباري — Angular validators ديكور بس، أي حد بيتخطاهم بـ DevTools في ثواني.
- FormControl values — أي one-way binding من user input للـ model، احتفظ بنسخة DTO مفلترة.
- Custom async validators — متستخدمش eval ولا كود ديناميكي جاي من API.
- File upload — زي React/Express: افحص MIME + الحجم + الـ extension + اعمل scan.
JIT vs AOT — ليه الـ AOT حماية؟
- JIT — الـ templates بتتجمع runtime في المتصفح. أبطأ + بتفتح باب لـ template injection.
- AOT — الـ templates بتتجمع وقت الـ build. أسرع + بتمسك أخطاء الـ template بدري، والـ templates الديناميكية بتبقى مستحيلة.
- من Angular 9 (Ivy)، الـ AOT default. اتأكد إن ng build --configuration=production بيستخدم AOT (وهو فعلاً بيستخدمه).
- ابعد عن @Component({ jit: true }) وJitCompilerFactory في prod.
Supply chain — منظومة Angular
- npm + @angular/* — حدّث Angular بانتظام. إصدار major كل 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 متحلولة.
غلطات الـ junior في Angular
- bypassSecurityTrustHtml لكل markdown — من غير DOMPurify. الـ XSS بيدخل بالباب اللي إنت فاتحه.
- JIT في production — أبطأ، أكبر، وفيه class من template injection ما بتطلعش في AOT. اشتغل AOT دايماً.
- HttpClient من غير CSRF token — Angular بيدّيك HttpClientXsrfModule مجاناً. استخدمه.
- Tokens في localStorage — Angular مش بيفرق عن React هنا. HttpOnly cookies + SameSite.
- Route guards بس على parent route — الـ child routes مفتوحة. CanActivateChild ضرورة.
- TransferState بـ PII — الـ SSR بيـ embed البيانات في HTML للـ hydration. أي حد بيـ view source يلاقيها.
- navigateByUrl بـ user input — open redirect مفتوح. لازم whitelist.
الخلاصة الناشفة
Angular بيدّيك أمان افتراضي أحسن من React. وفي نفس الوقت بيدّيك أبواب جانبية اسمها واضح: bypassSecurityTrust*.
الفرق بين Angular آمن وApp مكشوف = إنت فاهم ليه استخدمت كل bypass في الكود، ولا حطّيتها عشان TypeScript يسكت.
اقرا الـ bypassSecurityTrust* في codebase بتاعك. لو فيه واحد ما عندوش سبب مكتوب جنبه، يبقى دي ثغرتك القادمة.
اكتبها على ظهر إيدك:
كل bypassSecurityTrust* من غير تعليق بيشرح ليه = thread review قادم. اوعى تنسى.