CVE-2025-55182 به زبان ساده: چطور یک باگ در React و Next.js اجازه اجرای کد روی سرور می‌دهد؟

نویسنده

پوریا باباعلی

15 آذر 1404
15 دقیقه مطالعه

توی این متن، با مثال‌های ساده و غیرتخصصی می‌بینیم باگ CVE-2025-55182 چطور از React Server Actions سوءاستفاده می‌کند و می‌تواند روی سرور Next.js کد دلخواه اجرا کند، و باید برای امن شدن چه کار کنیم.

تصویر مقاله

توضیح ساده‌ی باگ CVE-2025-55182 در React / Next.js

این متن برای کسی نوشته شده که شاید فقط کمی جاوااسکریپت بداند یا حتی فقط بداند «وب‌سایت چیه».
حرف‌های پیچیده را تا حد ممکن تبدیل می‌کنیم به مثال‌های واقعی و ساده.


۱. خلاصه‌ی ماجرا در یک پاراگراف

در بعضی نسخه‌های React و Next.js، وقتی کاربر از سمت مرورگر با «Server Actions / Server Functions» صحبت می‌کرد،
سمت سرور اطلاعات را از حالت رشته‌ای به آبجکت تبدیل می‌کرد (deserialize).

در این تبدیل، یک اشتباه امنیتی وجود داشت که باعث می‌شد یک هکر بتواند:

  1. به چیزهای داخلی جاوااسکریپت دسترسی بگیرد (مثل Function constructor)
  2. آن را طوری استفاده کند که روی سرور شما کد دلخواه اجرا شود (Remote Code Execution یا RCE)

یعنی اگر وب‌سایت شما در معرض اینترنت است و این باگ را داشته،
هکر می‌توانسته روی سرور شما دستورهایی مثل این اجرا کند:

require('child_process').exec('rm -rf /');

۲. اول از همه: Server Function یعنی چی؟

مثال دنیای واقعی

فرض کن:

  • تو توی خانه نشستی (مرورگر کاربر)
  • یک دوستت توی اداره پشت سرور نشسته (سرور)
  • تو به دوستت می‌گی: «لطفا برو از بانک استعلام بگیر»
    چون خودت به بانک دسترسی مستقیم نداری، ولی او دارد.

در Next.js / React:

  • تو در سمت سرور یک تابع داری مثلا: 'use server' export async function createUser(data) { // کار با دیتابیس }
  • از سمت کلاینت، این تابع را صدا می‌زنی،
    ولی در حقیقت یک درخواست HTTP به سرور می‌رود و داده‌ها با یک فرمت خاص فرستاده می‌شوند.

این فرمت خاص اسمش هست React Flight Protocol.
همان «زبانِ صحبت» بین مرورگر و سرور برای React Server Components / Actions.


۳. React Flight را خیلی ساده تصور کنیم

React داده‌ها را «تکه‌تکه» می‌فرستد؛ هر تکه را می‌توانی مثل یک برگه‌ی شماره‌دار داخل یک پوشه‌ی بایگانی تصور کنی.

مثال:

files = {
  "0": (None, '["$1"]'),
  "1": (None, '{"object":"fruit","name":"$2:fruitName"}'),
  "2": (None, '{"fruitName":"cherry"}'),
}

این یعنی:

  • برگه ۰ می‌گوید: برو برگه ۱ را نگاه کن ("$1")
  • برگه ۱ می‌گوید: من یک شیء هستم با:
    • object = "fruit"
    • name = مقدار فیلد fruitName در برگه ۲
  • برگه ۲ می‌گوید: fruitName = "cherry"

وقتی سرور همه‌ی این برگه‌ها را کنار هم می‌گذارد، نتیجه می‌شود:

{ object: "fruit", name: "cherry" }

پس ایده این است:

  • هر برگه می‌تواند به برگه‌های دیگر ارجاع بدهد.
  • سرور باید این ارجاعات را دنبال کند و در آخر یک آبجکت نهایی بسازد.

تا اینجای کار مشکلی نیست.


۴. مشکل از کجا شروع شد؟ (داستان prototype)

در جاوااسکریپت، هر شیء یک «پدر و مادر مخفی» دارد که به آن prototype می‌گویند.

مثال دنیای واقعی

فرض کن:

  • هر کارمند در یک شرکت، یک پوشه‌ی شخصی دارد (همان object)
  • اما یک پوشه‌ی عمومی هم وجود دارد (prototype) که در آن:
    • قوانین کلی شرکت
    • فرم‌های عمومی
    • دستورالعمل‌ها
      هستند.

وقتی می‌پرسی:

در پوشه‌ی «علی» فرم "مرخصی" هست؟

سیستم می‌گوید:

  • اگر در پوشه‌ی علی نبود، برو در پوشه‌ی عمومی (prototype) بگرد.

در جاوااسکریپت:

obj.__proto__

به همین پوشه‌ی عمومی وصل است.

حالا مشکل React این بود:

وقتی داشت از روی برگه‌ها (chunks) داده‌ها را می‌خواند،
چک نمی‌کرد که key واقعا روی خود آبجکت هست یا از prototype می‌آید.

یعنی اگر هکر می‌گفت:

"$1:__proto__:constructor:constructor"

React می‌رفت:

  1. برگه‌ی ۱
  2. روی __proto__
  3. روی constructor
  4. دوباره روی constructor

و در نهایت می‌رسید به تابع سراسری Function.

این دقیقا معادل این بود که به مهمان اجازه بدهی نه‌تنها به کشوی شخصی خودش،
بلکه به کمد کلیدهای اصلی ساختمان هم دسترسی داشته باشد.


۵. Function constructor چرا خطرناک است؟

در جاوااسکریپت:

const f = Function("console.log('سلام از سرور')");
f(); // این کد اجرا می‌شود

این یعنی:

  • یک رشته (string) کد می‌گیری
  • آن را تبدیل به یک تابع می‌کنی
  • بعد تابع را اجرا می‌کنی

اگر هکر بتواند:

  1. به خود Function برسد
  2. یک رشته‌ی دلخواه هم به آن بدهد
  3. و بعد حاصل تابع را اجرا کند

در واقع می‌تواند هر کدی روی سرور اجرا کند.


۶. چطوری هکرها از داخل Flight به اجرای تابع رسیدند؟

قدم ۱: رسیدن به Function در داده‌ها

در PoC این مثال آمده:

files = {
  "0": (None, '["$1:__proto__:constructor:constructor"]'),
  "1": (None, '{"x":1}'),
}

معنی‌اش:

  • از برگه ۰، مسیر "$1:__proto__:constructor:constructor" را دنبال کن
  • این در نهایت می‌شود Function

نتیجه این است که در فرایند تبدیل، سرور در یک متغیر داخلی خودش، به Function دسترسی پیدا می‌کند.

قدم ۲: وادار کردن سیستم به «صدا زدن» این تابع

در Next.js، جایی وجود دارد که این کار را می‌کند:

const result = await decodeReplyFromBusboy(...);

اگر result یک آبجکت باشد که then دارد،
جاوااسکریپت به صورت خودکار آن را به عنوان شبیه Promise می‌بیند و این کار را می‌کند:

result.then(resolve, reject);

پس اگر هکر بتواند then را برابر با Function(...) قرار دهد،
ماجرا این طوری می‌شود:

  1. then می‌شود همان تابعی که از Function("کد") آمده
  2. await باعث اجرای آن می‌شود
  3. کد داخلش روی سرور اجرا می‌شود

یک payload ساده برای تست این بود:

files = {
  "0": (None, '{"then":"$1:__proto__:constructor:constructor"}'),
  "1": (None, '{"x":1}'),
}

اینجا:

  • then برابر Function می‌شود
  • بعد وقتی await روی آن صدا می‌شود، چیزهای عجیبی رخ می‌دهد و خطای SyntaxError دیده می‌شود

این مرحله بیشتر برای اثبات این است که:

ما توانستیم then را دستکاری کنیم تا به Function اشاره کند و سیستم تلاش کرد آن را اجرا کند.


۷. ترفند اصلی PoC: ساختن «chunk جعلی» مثل یک فرم پر از دروغ

PoC یک حرکت هوشمندانه انجام می‌دهد:

  1. یک chunk می‌سازد که ظاهرش شبیه chunk داخلی React است
  2. خودش را چند بار به خودش ارجاع می‌دهد
  3. کاری می‌کند که داخل کد React، این chunk جعلی به عنوان داده‌ی معتبر دیده شود

مثال ساده‌ی دنیای واقعی

فرض کن:

  • شرکتت برای همه‌ی درخواست‌ها فرم دارد
  • کارمندها فرم‌ها را بررسی و اجرا می‌کنند
  • یک نفر یک فرم قلابی و خیلی شبیه فرم واقعی می‌سازد
  • این فرم را طوری پر می‌کند که:
    • بخش «نوع درخواست» بگوید: «دوباره این فرم را بخوان»
    • بخش «مقصد» بگوید: «از کمد کلید یک کپی از کلید اصلی بردار»
    • بخش «دستورالعمل» بگوید: «با این کلید برو گاوصندوق را باز کن و پول‌ها را بفرست فلان‌جا»

اگر سیستم هیچ چکی روی این‌که «این فرم واقعا از خود شرکت است یا از بیرون» نکند،
کارمند ساده‌دل دقیقا همان کار را انجام می‌دهد.

در PoC:

  • از یک قابلیت خاص با پیشوند "$@" استفاده می‌شود که می‌گوید:
    «به من خود chunk خام را بده نه مقدار نهایی‌اش»
  • بعد با چند دور بازی با then و status و value کاری می‌کنند که:
    • تابع داخلی initializeModelChunk روی این داده‌ی جعلی صدا زده شود
    • داخل آن، JSON parse شود
    • و در نهایت Function("کد هکر") ساخته و اجرا شود

۸. یک تکه‌ی مهم از زنجیره‌ی RCE

در یک بخش از کد React، چیزی شبیه این وجود داشت:

case "B":
  return (
    (obj = parseInt(value.slice(2, 16))),
    response._formData.get(response._prefix + obj)
  );

PoC از این بخش این‌طور سوءاستفاده می‌کند:

  • response._formData.get را برابر Function می‌گذارد
  • response._prefix را می‌گذارد برابر رشته‌ی کد دلخواه

در نهایت، چیزی که اجرا می‌شود این است:

Function("process.mainModule.require('child_process').execSync('calc');" + "0");

این Function(...) می‌شود همان then و چون در زنجیره‌ی await قرار دارد، اجرا می‌شود.

این یعنی هکر با استفاده از چند ارجاع تو در تو، سیستم را مجبور می‌کند کدی که خودش نوشته
به عنوان تابع در سرور ساخته و سپس اجرا شود.


۹. چرا این باگ خیلی خطرناک است؟

چند نکته‌ی مهم:

  1. قبل از چک کردن مجوزها رخ می‌دهد یعنی حتی قبل از اینکه سرور بررسی کند «این کاربر اجازه‌ی این اکشن را دارد یا نه»،
    کد Flight parse می‌شود و exploit می‌تواند اجرا شود.
  2. فقط نیاز به دسترسی به endpoint دارد هر کسی که بتواند یک درخواست شبیه فرم ارسال کند به endpoint Server Actions،
    می‌تواند این payload را بفرستد.
  3. بدون نیاز به کدنویسی عجیب در پروژه‌ی شما حتی اگر خود شما Server Action خاصی ننوشته باشید،
    ممکن است فقط به خاطر استفاده از App Router / RSC در Next.js، این مسیر باز باشد.

در دنیای واقعی، این شبیه این است که:

هر کسی بتواند فقط با فرستادن یک فرم ظاهرا عادی،
نگهبان ساختمان را قانع کند درِ اتاق سرور را باز کند و دستورهایی روی سرور اجرا کند.


۱۰. پچ چطوری درستش کرد؟

در بخشی از کد که ماژول‌ها را بر اساس اسم‌شان برمی‌گرداند، قبلا این‌طوری بود:

export function requireModule<T>(metadata: ClientReference<T>): T {
  const moduleExports = parcelRequire(metadata[ID]);
  return moduleExports[metadata[NAME]];
}

پچ آن را به این تبدیل کرده:

export function requireModule<T>(metadata: ClientReference<T>): T {
  const moduleExports = parcelRequire(metadata[ID]);
  if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
    return moduleExports[metadata[NAME]];
  }
  return (undefined: any);
}

اینجا مهم‌ترین نکته این است:

  • قبلا هر چیزی در زنجیره‌ی prototype (حتی از prototype) قابل دسترسی بود
  • الان فقط اگر خود آبجکت آن property را داشته باشد اجازه می‌دهد

یعنی دیگر نمی‌توانی با __proto__ و constructor به چیزهای خطرناک داخلی برسی.


۱۱. اگر برنامه‌نویس Next.js / React هستم، چه کار باید بکنم؟

به شکل خیلی ساده:

  1. نسخه‌ها را آپدیت کن
  • پکیج‌های React server (مثل react-server-dom-webpack و مشابه)
  • Next.js را به نسخه‌ای که تیم رسمی گفته امن است، ارتقا بده
  1. اگر الان نمی‌توانی آپدیت کنی (فقط موقت)
  • endpointهایی که Server Actions را هندل می‌کنند را پشت:
    • فایروال
    • IP allowlist
    • یا هر راهی که فقط به کاربران مورد اعتماد دسترسی بدهد
      قرار بده
  • این فقط یک مسکن موقت است، نه درمان اصلی
  1. بعد از آپدیت، حتما deployment کامل انجام بده فقط تغییر در package.json کافی نیست.
    باید build جدید بگیری و سرور را با نسخه‌ی جدید بالا بیاوری.

۱۲. جمع‌بندی خیلی کوتاه

  • React / Next.js برای صحبت بین مرورگر و سرور از یک فرمت تکه‌تکه (Flight) استفاده می‌کند.
  • در نسخه‌های مشکل‌دار، سیستم موقع سر هم کردن این تکه‌ها:
    • اجازه می‌داد به prototype objectها دسترسی بگیری
    • از آن‌جا به Function برسی
  • با چند حقه، هکر می‌توانست:
    • Function("کد خودم") بسازد
    • و آن را در زنجیره‌ی await طوری جا بدهد که روی سرور اجرا شود
  • این یعنی Remote Code Execution بدون نیاز به لاگین.
  • پچ با محدود کردن دسترسی به propertyهای خود آبجکت (و نه prototype) این راه را بست.
  • راه‌حل عملی برای ما: آپدیت سریع dependencyها و deployment نسخه‌ی جدید.

نظرات

کدهالیک

کدهالیک پلتفرمی برای یادگیری زبان‌های برنامه‌نویسی است. ما با ارائه دوره‌های کاربردی و پروژه‌محور، شما را در مسیر تبدیل شدن به یک برنامه‌نویس حرفه‌ای همراهی می‌کنیم. از مبتدی تا پیشرفته، با کدهالیک آینده‌ی شغلی خود را بسازید.

لینک‌های سریع

ارتباط با ما

mail@codehalic.ir

چیتگر جوزانی غربی خیابان مظفر خیابان زنبق پلاک صفر برج همت یاس

02146021206 - 09100455680

© 1405 کدهالیک™ - تمامی حقوق محفوظ است

CVE-2025-55182 به زبان ساده: چطور یک باگ در React و Next.js اجازه اجرای کد روی سرور می‌دهد؟ - وبلاگ | کد هالیک