الفرق الحمراء — الهجومخبير80mL71

أمن Angular — Sanitizer و Template Injection

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

#Angular#Sanitizer#Template#Trusted Types#SSR

Angular مختلف بنيوياً

Angular يفرض contextual sanitization: كل قيمة تدخل DOM يحدّد لها Angular السياق (HTML, URL, Style, Script, Resource URL) و يعقّمها وفقاً له. هذا أقوى من React بشكل افتراضي. لكن Angular يعطيك أيضاً أبواباً للتجاوز: bypassSecurityTrust*، [innerHTML]، [srcdoc]، JIT eval، Server-Side Rendering مع Angular Universal.

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

الـ 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 تماماً
}
القاعدة الذهبية
كل bypassSecurityTrust* هو code smell. كل instance يحتاج: justification + sanitization منفصلة + code review.

Template Injection — Angular-specific

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 — الفخ الأعلى خطورة

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());
}

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.
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 يُحظر بالـ browser.

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) يعمل
ميزة Angular
عكس React، Angular مُصمَّم حول Trusted Types — التفعيل سهل و الفائدة فورية. يجب أن يكون default في كل deploy إنتاجي جديد.

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

  • 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

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;
ملاحظة CSP
إذا استخدمت DomSanitizer.bypassSecurityTrustStyle، قد تحتاج 'unsafe-inline' في style-src. حاول استخدام classes مسبقة بدلاً من inline style.

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 محلولة.
أدوات
@angular-eslint, eslint-plugin-security, Snyk, Semgrep p/angular, Sonarqube مع Angular ruleset.