اعرف اللي قصادك. احمي أرضك.
الفرق الحمراء — الهجوم

Angular Security — Sanitizer مش حصن

Trusted Types و DomSanitizer bypasses و AOT vs JIT و SSR leaks

المستوى
خبير
المدة
80m
الترتيب
الدرس 32 من 85
AngularSanitizerTemplateTrusted TypesSSR

Angular مختلف من جذره

Angular مش زي React في الموضوع ده.

Angular بيفرض contextual sanitization by default — كل قيمة بتدخل الـ DOM، Angular بيعرف هي رايحة فين (HTML, URL, Style, Script, Resource URL) وبيعقمها على الأساس ده.

طب فين الخرّامة؟

في الأبواب الجانبية اللي Angular نفسه فاتحها: bypassSecurityTrust*.

تشبيه يقرّب الفكرة
قفل ذكي بيميّز المفاتيح المسجّلة عنده، وبيرفض أي مفتاح تاني. بس صاحب البيت بإيده يقدر يحط علامة "trust" على أي مفتاح. اليوم اللي يحط فيه العلامة على مفتاح غريب، الحماية انتهت.

- طب يا حضرتك، Angular أمن من React.. إيه اللي يخليني أقلق؟

يا مستجد. Angular فعلاً بيدّيك حماية افتراضية أحسن. بس بيدّيك في نفس الوقت باب جانبي اسمه واضح: bypassSecurityTrustHtml. والمبرمجين بيستخدموه عشان "الـ warning يسكت". اليوم اللي حد كتب السطر ده من غير ما يفهمه — ده اليوم اللي الحماية فيه راحت.

الـ DomSanitizer — بيشتغل إزاي وبيتخطى إزاي

Angular بيصنف القيم لـ 5 سياقات:

HTML
المحتوى داخل element. مثال: [innerHTML].
STYLE
قيمة CSS. مثال: [style.background]. يحظر expression(), javascript:.
URL
الروابط. مثال: [href]. يحظر javascript:, data:.
RESOURCE_URL
Scripts, iframes. مثال: [src] لـ iframe. الأشد صرامة.
SCRIPT
script.text. يحظر بالكامل.
typescript
// خطر — 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، إنت كدة بتنفذ كود مباشرة.

typescript
// خطر — 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 معطّل في prod

bypassSecurityTrustResourceUrl — أخطر فخ في Angular

typescript
// خطر — 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.
typescript
// خطر — 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 بيتلغي على مستوى المتصفح نفسه.

typescript
// 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

typescript
// خطر — 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.

typescript
@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-GET

Forms — الـ 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

text
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

  1. كل bypassSecurityTrust* معاه justification + DOMPurify.
  2. كل [innerHTML] جاي من مصدر موثوق أو ماشي عبر sanitization.
  3. AOT في prod (افحص angular.json).
  4. HttpClientXsrfModule مفعل + السيرفر بيتحقق من X-XSRF-TOKEN.
  5. كل route admin/protected معاه CanActivate وCanActivateChild.
  6. Trusted Types CSP مفعل.
  7. مفيش tokens في localStorage — httpOnly cookies بس.
  8. TransferState مفيهوش PII ولا internal flags.
  9. SSR (Universal): timeouts على الـ HTTP، مفيش synchronous heavy work.
  10. Open redirect filter على كل navigateByUrl جاي من user input.
  11. Forms: server-side validation كاملة، متثقش في Angular validators أبداً.
  12. Material/PrimeNG محدّث، الـ CVEs متحلولة.

غلطات الـ junior في Angular

الخلاصة الناشفة

Angular بيدّيك أمان افتراضي أحسن من React. وفي نفس الوقت بيدّيك أبواب جانبية اسمها واضح: bypassSecurityTrust*.

الفرق بين Angular آمن وApp مكشوف = إنت فاهم ليه استخدمت كل bypass في الكود، ولا حطّيتها عشان TypeScript يسكت.

اقرا الـ bypassSecurityTrust* في codebase بتاعك. لو فيه واحد ما عندوش سبب مكتوب جنبه، يبقى دي ثغرتك القادمة.

اكتبها على ظهر إيدك:

كل bypassSecurityTrust* من غير تعليق بيشرح ليه = thread review قادم. اوعى تنسى.

خلّصت الدرس؟

علّمه علشان تفضل عارف انت فين على السكة.