پرش به محتوای اصلی
آسیب‌پذیری‌های سایت

Prototype Pollution چیست؟ بررسی آسیب‌پذیری Prototype در JavaScript و Node.js

Prototype Pollution نوعی آسیب‌پذیری در JavaScript است که باعث می‌شود Propertyهای غیرمجاز وارد Prototype Chain شوند و رفتار Objectهای دیگر را تغییر دهند. این ضعف در Browser و Node.js می‌تواند با Gadgetهای مختلف به مشکلاتی مانند دور زدن کنترل دسترسی، DOM XSS، تغییر Configuration یا اختلال در سرویس منجر شود. Schema Validation، محدود کردن Dynamic Propertyها، استفاده از Object.hasOwn()، Map یا Null-Prototype Object و به‌روزرسانی Dependencyها از مهم‌ترین راهکارهای جلوگیری از Prototype Pollution هستند.

تیم امنیت رخنه‌کاو ۳۹ دقیقه مطالعه انتشار:

Prototype Pollution یا «آلودگی Prototype» نوعی آسیب‌پذیری امنیتی در JavaScript است که به مهاجم اجازه می‌دهد Propertyهایی را به Prototype یک Object، به‌خصوص Object.prototype، اضافه یا مقادیر موجود را تغییر دهد. از آنجا که تعداد زیادی از Objectهای JavaScript از این Prototype ارث‌بری می‌کنند، یک تغییر ناخواسته می‌تواند رفتار بخش‌های مختلف برنامه را تحت تأثیر قرار دهد و در شرایط خاص به دور زدن Access Control، تغییر Configuration، DOM XSS، اختلال در سرویس و در برخی سناریوهای Server-Side حتی پیامدهای شدیدتری منجر شود.

مهم‌ترین روش جلوگیری از Prototype Pollution این است که ساختار داده ورودی را با Schema دقیق اعتبارسنجی کنیم، Propertyهای ناشناخته را نپذیریم، Keyهای حساس مانند __proto__، constructor و prototype را در مسیرهای Dynamic Property Assignment کنترل کنیم، برای Dictionaryهای داخلی از Map یا Objectهای بدون Prototype استفاده کنیم و هنگام خواندن Propertyهای امنیتی فقط Own Propertyهای Object را قابل‌اعتماد بدانیم. MDN و OWASP هر دو این اصول را در توصیه‌های دفاعی خود مطرح می‌کنند.

مقدمه؛ چرا Prototype Pollution مسئله مهمی در JavaScript است؟

JavaScript یکی از مهم‌ترین زبان‌های توسعه نرم‌افزار در وب است.

این زبان ابتدا بیشتر در Browser استفاده می‌شد، اما با ظهور Node.js به یکی از فناوری‌های اصلی Backend، API، Microservice، ابزارهای Build، Serverless Function و Applicationهای Real-Time تبدیل شد.

همین گسترش باعث شده است یک ضعف خاص JavaScript دیگر فقط یک مشکل Frontend نباشد.

Prototype Pollution نمونه مهمی از این وضعیت است.

در یک Application مدرن ممکن است داده User از مسیرهای مختلف وارد JavaScript شود:

Query String،

JSON Request،

Form Data،

API Response،

Configuration،

Message Queue،

WebSocket Message،

Database Record،

یا Package ثالث.

سپس این داده ممکن است با Objectهای داخلی Merge شود یا Propertyهای آن به‌صورت Dynamic پردازش شوند.

اگر Developer یا Library مرز میان Property عادی و Prototype Chain را به‌درستی کنترل نکند، داده‌ای که قرار بوده فقط داخل یک Object معمولی ذخیره شود ممکن است روی رفتار Objectهای دیگری در Application اثر بگذارد.

MITRE این کلاس ضعف را با شناسه CWE-1321 و عنوان Improperly Controlled Modification of Object Prototype Attributes ثبت کرده است.

Prototype Pollution معمولاً یک Vulnerability دومرحله‌ای است.

ابتدا باید Prototype آلوده شود.

سپس Application باید Property آلوده‌شده را در یک Context حساس مصرف کند.

MDN این دو مرحله را به‌صورت Pollution و Exploitation توضیح می‌دهد.

به همین دلیل صرف پیدا کردن یک Source بالقوه همیشه به معنی Exploit کامل نیست.

برای ارزیابی واقعی Risk باید مشخص شود آیا یک Gadget یا Sink مناسب نیز وجود دارد یا خیر. Prototype در JavaScript چیست؟

Prototype در JavaScript چیست؟

برای درک Prototype Pollution ابتدا باید Prototype را بفهمیم.

JavaScript از مدلی به نام Prototypal Inheritance استفاده می‌کند.

هر Object می‌تواند به Object دیگری به‌عنوان Prototype متصل باشد.

وقتی برنامه Propertyای را روی Object می‌خواند، JavaScript ابتدا بررسی می‌کند آیا Property مستقیماً روی همان Object وجود دارد یا خیر.

اگر وجود نداشته باشد، Runtime به Prototype آن مراجعه می‌کند.

اگر آنجا هم پیدا نشود، Prototype بعدی بررسی می‌شود.

این فرایند تا رسیدن به null ادامه پیدا می‌کند.

این زنجیره را Prototype Chain می‌نامند.

MDN توضیح می‌دهد که برای Objectهای معمولی، زنجیره در نهایت به Object.prototype و سپس null می‌رسد.

برای مثال فرض کنید Object زیر را داریم:

const user = {
  name: "Ali"
};

Property name مستقیماً روی user قرار دارد.

اما متدی مانند:

user.toString()

معمولاً مستقیماً روی user تعریف نشده است.

JavaScript آن را از Prototype Chain پیدا می‌کند.

مدل ساده:

user
  ↓
Object.prototype
  ↓
null

این Mechanism یکی از قابلیت‌های اصلی زبان است و به‌خودی‌خود هیچ آسیب‌پذیری‌ای محسوب نمی‌شود.

مشکل زمانی ایجاد می‌شود که ورودی غیرقابل‌اعتماد بتواند این Chain را تغییر دهد.

Own Property و Inherited Property چه تفاوتی دارند؟

یک Property می‌تواند به دو شکل در دسترس Object باشد.

اول Own Property؛ یعنی Property واقعاً روی همان Object تعریف شده است.

دوم Inherited Property؛ یعنی مقدار از Prototype Chain آمده است.

فرض کنید:

const user = {
  username: "reza"
};

اگر username را بخوانیم، Own Property است.

اما بسیاری از Methodهای عمومی Object از Prototype به ارث رسیده‌اند.

این تفاوت از نظر امنیتی بسیار مهم است.

فرض کنید Logic برنامه بگوید:

if (user.isAdmin) {
  // allow admin action
}

Developer ممکن است فرض کند اگر isAdmin روی Object وجود ندارد مقدار آن undefined خواهد بود.

اما اگر Prototype آلوده شده باشد و isAdmin از Prototype Chain مقدار بگیرد، این فرض دیگر درست نیست.

MDN دقیقاً چنین سناریویی را به‌عنوان یکی از نمونه‌های خطر Prototype Pollution مطرح می‌کند و توصیه می‌کند برای Propertyهای حساس از Object.hasOwn() استفاده شود.

مدل دفاعی بهتر:

if (Object.hasOwn(user, "isAdmin") && user.isAdmin === true) {
  // admin action
}

در این حالت Property ارث‌رسیده به‌تنهایی نمی‌تواند تصمیم Authorization را تغییر دهد.

Prototype Pollution دقیقاً چیست؟

Prototype Pollution زمانی رخ می‌دهد که داده تحت کنترل مهاجم باعث اضافه یا تغییر Property روی Prototype یک Object شود.

اگر Target، Object.prototype باشد، دامنه اثر بسیار گسترده می‌شود، زیرا تعداد زیادی از Objectهای معمولی JavaScript از آن ارث‌بری می‌کنند.

فرض کنید Application در جایی Prototype را ناخواسته با Property زیر آلوده کرده باشد:

someFlag = true

بعداً Object جدیدی ساخته می‌شود:

const settings = {};

حتی اگر settings خودش Property مربوط را نداشته باشد، ممکن است مقدار را از Prototype دریافت کند.

در نتیجه کدی که تصور می‌کند Object خالی است دیگر واقعاً رفتار یک Object خالی را ندارد.

PortSwigger نیز Prototype Pollution را قابلیتی برای اضافه کردن Propertyهای Arbitrary به Global Object Prototype تعریف می‌کند که سپس Objectهای User-Defined می‌توانند آنها را به ارث ببرند.

دو مرحله اصلی Prototype Pollution

برای اینکه Prototype Pollution به Vulnerability قابل بهره‌برداری تبدیل شود، معمولاً دو جزء نیاز داریم.

مرحله اول Source یا Pollution Source است.

Source نقطه‌ای است که ورودی User می‌تواند باعث تغییر Prototype شود.

مرحله دوم Gadget یا Sink است.

Gadget بخشی از Code است که Property آلوده‌شده را به شکلی حساس مصرف می‌کند.

مدل:

Untrusted Input
      ↓
Pollution Source
      ↓
Prototype Modified
      ↓
Application reads inherited property
      ↓
Gadget / Sensitive Sink
      ↓
Security Impact

ممکن است Source وجود داشته باشد ولی Gadget مناسبی وجود نداشته باشد.

در چنین شرایطی Vulnerability بالقوه وجود دارد اما Impact قابل توجه هنوز اثبات نشده است.

در مقابل اگر Property آلوده‌شده وارد Configuration حساس، DOM API، Authorization Decision یا Server Function شود، Severity می‌تواند بسیار بیشتر شود.

PortSwigger نیز Source و Gadget را دو جزء کلیدی Exploitation در Prototype Pollution معرفی می‌کند. Prototype Pollution چگونه ایجاد می‌شود؟

Prototype Pollution چگونه ایجاد می‌شود؟

یکی از رایج‌ترین Root Causeها Dynamic Property Assignment است.

یعنی Application Property Name را از داده خارجی دریافت کند و سپس چیزی شبیه این انجام دهد:

object[key] = value;

این Pattern همیشه آسیب‌پذیر نیست.

اگر key فقط از یک Allowlist مشخص انتخاب شود، مشکل خاصی وجود ندارد.

اما اگر User بتواند Keyهای دلخواه یا ساختار Nested ایجاد کند، خطر بیشتر می‌شود.

MDN اشاره می‌کند Patternهایی مانند Dynamic Property Modification می‌توانند به مسیرهای Prototype دسترسی پیدا کنند و توصیه می‌کند Keyهایی مانند:

__proto__
constructor
prototype

در Input Validation کنترل شوند.

مشکل Recursive Merge

یکی از رایج‌ترین محل‌های تاریخی Prototype Pollution، Functionهای Deep Merge بوده‌اند.

فرض کنید Application چند Object را به‌صورت Recursive با یکدیگر ترکیب کند:

Default Settings
      +
User Settings
      ↓
Final Settings

اگر Merge Function هر Key موجود در Input را بدون بررسی کپی کند، ممکن است Propertyهای خاص نیز وارد مسیر پردازش شوند.

کتابخانه‌هایی که برای Generic بودن مجبورند Structureهای Arbitrary را Merge کنند نسبت به Application Code محدودشده Attack Surface بیشتری دارند.

PortSwigger Research نیز Server-Side Prototype Pollution را در بسیاری از موارد به Recursive Merge بدون Sanitize کردن Keyها مرتبط می‌داند.

برای همین Packageهایی که Functionهایی مانند:

Deep Merge،

Deep Clone،

Extend،

Defaults،

Object Path Setter

ارائه می‌کنند باید از نظر Security Advisory و نسخه مورد استفاده بررسی شوند.

آیا JSON.parse به‌تنهایی باعث Prototype Pollution می‌شود؟

یکی از سوءتفاهم‌های رایج این است که وجود نام Property خاص در JSON به‌تنهایی Prototype را آلوده می‌کند.

این موضوع همیشه درست نیست.

MDN توضیح می‌دهد Property مانند __proto__ در JSON Parseشده در ابتدا صرفاً می‌تواند یک Property عادی داده باشد.

خطر زمانی ایجاد می‌شود که همان Object بعداً با روش‌هایی که Assignment Setter را فعال می‌کنند وارد Object دیگری Merge شود.

MDN به‌طور مشخص توضیح می‌دهد Object.assign() یا بعضی Loopهای Assignment می‌توانند در چنین شرایطی رفتار متفاوتی نسبت به Parse ساده داشته باشند، در حالی که Object Spread Setter مربوط را Trigger نمی‌کند.

پس Root Cause را نباید فقط به JSON نسبت داد.

Data Flow مهم است:

JSON Input
   ↓
Parse
   ↓
Merge / Assignment
   ↓
Possible prototype modification

Object.assign و Object Spread چه تفاوتی دارند؟

Object.assign() برای کپی Propertyها میان Objectها کاربرد زیادی دارد.

اما هنگام Assignment ممکن است Setterهای موجود روی Target Object یا Prototype درگیر شوند.

MDN اشاره می‌کند بعضی سناریوهای Prototype Manipulation می‌توانند هنگام Merge با Object.assign() فعال شوند.

در مقابل Object Spread:

const merged = {
  ...defaults,
  ...input
};

در این نوع مشخص از Assignment، Setter مربوط به __proto__ به همان شکل Trigger نمی‌شود.

اما این نکته نباید اشتباه تفسیر شود.

Object Spread به‌تنهایی تمام انواع Prototype Pollution را حل نمی‌کند.

اگر Source دیگری Prototype را قبلاً آلوده کرده باشد، Application همچنان ممکن است Propertyهای Inherited را در مراحل بعد مصرف کند.

همچنین Validation همچنان ضروری است.

Query String Parserها چرا حساس هستند؟

Query String ساده معمولاً Flat است:

?name=ali&age=25

اما بعضی Libraryها از Nested Structure پشتیبانی می‌کنند.

یعنی Parameterهای URL را به Object چندلایه تبدیل می‌کنند.

هرچه Parser انعطاف‌پذیرتر باشد، Developer باید بیشتر بررسی کند کدام Keyها اجازه ورود دارند.

MDN اشاره می‌کند Query String Parserهای سفارشی که امکان ساخت Object عمیق را فراهم می‌کنند، در صورت استفاده از Dynamic Property Assignment می‌توانند Attack Surface مهمی برای Prototype Pollution باشند.

راهکار اصلی حذف Query String نیست.

راهکار:

استفاده از Parser به‌روز،

Schema Validation پس از Parse،

عدم پذیرش Property ناشناخته،

و عدم Merge کورکورانه داده است.

Prototype Pollution در Client-Side JavaScript

Client-Side Prototype Pollution در Browser اتفاق می‌افتد.

Sourceهای معمول می‌توانند داده‌هایی باشند که از:

URL Query،

URL Fragment،

JSON،

Storage،

Message،

یا داده Remote

وارد JavaScript می‌شوند.

سپس Application ممکن است این داده را Parse و داخل یک Object Merge کند.

اگر Prototype تغییر کند، Gadget بعدی می‌تواند رفتار Browser Application را تغییر دهد.

PortSwigger Client-Side Prototype Pollution Source را ورودی User-Controlled می‌داند که به Object تبدیل شده و سپس با Object دیگری Merge می‌شود؛ در صورت وجود Gadget مناسب، این وضعیت ممکن است به DOM XSS یا سایر اثرات منجر شود.

ارتباط Prototype Pollution با DOM XSS

یکی از مهم‌ترین Impactهای Client-Side Prototype Pollution، DOM XSS است.

اما این دو Vulnerability یکی نیستند.

Prototype Pollution می‌تواند Propertyای ایجاد کند.

Gadget سپس ممکن است همان Property را در یک Sink ناامن استفاده کند.

اگر Sink اجازه ورود Data به HTML یا Script Context خطرناک بدهد، DOM XSS ممکن است ایجاد شود.

مدل:

Prototype Pollution Source
        ↓
Polluted Property
        ↓
DOM Gadget
        ↓
Unsafe HTML / Script Sink
        ↓
DOM XSS

بنابراین Finding حرفه‌ای باید این Chain را مشخص کند.

صرف اینکه Object.prototype قابل تغییر است، خودکار به معنی JavaScript Execution نیست.

PortSwigger نیز تأکید می‌کند Client-Side Prototype Pollution به‌تنهایی الزاماً Vulnerability با Impact نهایی نیست و وجود Gadget برای Exploitation اهمیت دارد.

Configuration Objectها؛ یکی از مهم‌ترین Gadgetها

Applicationها مرتباً Configuration Object می‌سازند.

برای مثال:

const options = {
  mode: "safe"
};

سپس Library یا API از Propertyهای آن استفاده می‌کند.

اگر Developer بعضی Propertyها را تعریف نکند، Runtime ممکن است آنها را از Prototype پیدا کند.

این مسئله مخصوصاً برای Configهایی حساس است که رفتار:

Network Request،

Sanitizer،

DOM Element،

Template Engine،

HTTP Client،

Process،

یا Security Feature

را تعیین می‌کنند.

MDN Configuration Objectها را از Targetهای حساس Prototype Pollution معرفی می‌کند و مثال می‌زند که Propertyهای ارث‌رسیده می‌توانند رفتار API را تغییر دهند.

راهکار مناسب فقط Sanitize کردن Output نیست.

Object Config باید به شکلی ساخته شود که مقدارهای امنیتی Explicit باشند.

Default Value چرا اهمیت دارد؟

فرض کنید:

function process(options = {}) {
  if (options.safeMode) {
    // ...
  }
}

اگر safeMode روی Object وجود نداشته باشد، JavaScript می‌تواند Prototype Chain را بررسی کند.

در Logic امنیتی بهتر است Default مورد انتظار صریح باشد.

مثلاً:

function process(
  options = {
    safeMode: true
  }
) {
  // ...
}

MDN توصیه می‌کند Propertyهای مهم مقدار Default صریح داشته باشند تا Application به Prototype Lookup وابسته نشود. Prototype Pollution در Node.js

Prototype Pollution در Node.js

Node.js باعث شده JavaScript مستقیماً در Server اجرا شود.

در این محیط Prototype Pollution می‌تواند دامنه اثر متفاوتی داشته باشد.

Browser Pollution معمولاً فقط Context همان Page را تحت تأثیر قرار می‌دهد.

اما در Node.js، Application Process ممکن است Requestهای تعداد زیادی User را پردازش کند.

اگر Global Prototype در Process تغییر کند، اثر ممکن است تا زمانی که Process زنده است باقی بماند.

PortSwigger توضیح می‌دهد که Server-Side Prototype Pollution نسبت به Client-Side سخت‌تر شناسایی می‌شود و تغییرات Prototype ممکن است در تمام Lifetime Process باقی بمانند.

همین موضوع Testing را نیز حساس‌تر می‌کند.

یک تست تهاجمی اشتباه ممکن است Application را برای تمام کاربران خراب کند.

چرا Prototype Pollution در Node.js خطرناک‌تر می‌تواند باشد؟

Backend معمولاً به منابع حساس‌تری دسترسی دارد.

برای مثال:

Database،

File System،

Internal API،

Environment Variables،

Service Credential،

Child Process،

Queue،

Cloud Metadata.

Prototype Pollution لزوماً دسترسی مستقیم به این منابع نمی‌دهد.

اما اگر Property آلوده‌شده وارد Gadgetی شود که یکی از این Componentها را Configure می‌کند، Impact ممکن است شدید شود.

PortSwigger گزارش کرده است که در برخی Server-Side Gadgetها Prototype Pollution می‌تواند حتی به Remote Code Execution منجر شود، اما این نتیجه نیازمند Gadget مناسب است و نباید برای هر Prototype Pollution به‌صورت پیش‌فرض ادعا شود.

پس ارزیابی Severity باید بر اساس Code واقعی انجام شود.

Prototype Pollution و دور زدن Access Control

یکی از سناریوهای مهم زمانی است که Security Decision بر Property Optional متکی باشد.

مثلاً Developer انتظار دارد User عادی:

isAdmin = undefined

داشته باشد.

و Admin:

isAdmin = true

داشته باشد.

Logic:

if (user.isAdmin) {
  allowAdmin();
}

اینجا نبودن Property با false بودن آن یکسان فرض شده است.

اگر Prototype Property مشابهی داشته باشد، User عادی ممکن است مقدار Inherited بگیرد.

MDN این مثال را به‌عنوان یک نمونه واضح از خطر Prototype Lookup در Authorization مطرح می‌کند.

طراحی بهتر:

if (
  Object.hasOwn(user, "isAdmin") &&
  user.isAdmin === true
) {
  allowAdmin();
}

و بهتر از آن اینکه Schema User تضمین کند isAdmin همیشه Boolean صریح باشد.

Prototype Pollution و Privilege Escalation

Privilege Escalation فقط محدود به isAdmin نیست.

Propertyهایی مانند:

role
permissions
authenticated
verified
canDelete
canWrite
bypassCheck

اگر Optional باشند و Application آنها را از Prototype Chain بپذیرد، ممکن است Risk مشابه داشته باشند.

Rule مهم:

Security Attribute باید روی Object واقعی وجود داشته باشد.

عدم وجود Property نباید به Runtime اجازه دهد مقدار را از یک Prototype عمومی دریافت کند. Gadget چیست و چه نقشی در Prototype Pollution دارد؟

Gadget چیست؟

Gadget بخشی از Code است که Property آلوده را به رفتار قابل سوءاستفاده تبدیل می‌کند.

ممکن است Prototype Pollution Source کاملاً دور از Gadget باشد.

برای مثال یک Parser در ابتدای Request Prototype را تغییر می‌دهد.

صدها خط بعد یک Library:

if (options.featureEnabled) {
  // sensitive behavior
}

را اجرا می‌کند.

اگر featureEnabled از Prototype خوانده شود، Gadget شکل گرفته است.

به همین دلیل Prototype Pollution اغلب در Code Review سطحی دیده نمی‌شود.

نیاز به Data Flow Analysis داریم.

Sink چیست؟

Sink مقصد حساس Data است.

در زمینه Prototype Pollution ممکن است Sink:

DOM API،

HTML Renderer،

HTTP Request Config،

Authorization Check،

File Operation،

Template Configuration،

Process Configuration،

یا Function Execution

باشد.

هر Gadget لزوماً Sink بسیار خطرناکی ندارد.

مثلاً تغییر Color Theme ممکن است فقط Bug نمایشی ایجاد کند.

اما Property دیگری ممکن است Authorization را تغییر دهد.

Severity را Sink تعیین می‌کند، نه فقط Source.

Prototype Pollution و Denial of Service

در Server-Side Environment تغییر Propertyهای عمومی می‌تواند باعث Exception، Loop غیرعادی، Type Confusion منطقی یا Failure در Library شود.

در نتیجه Denial of Service ممکن است یکی از Impactها باشد.

PortSwigger Research تأکید می‌کند تست Server-Side Prototype Pollution روی سیستم واقعی خطرناک است، زیرا Pollution می‌تواند Process را برای مدت باقی‌مانده عمر آن خراب کند یا Behavior کلی Application را تغییر دهد.

این نکته برای Pentest بسیار مهم است.

Proof of Concept مخرب روی Production مناسب نیست.

Prototype Pollution با Object Injection چه تفاوتی دارد؟

Prototype Pollution زیرمجموعه‌ای از مشکلات مربوط به کنترل نامناسب Propertyهاست، اما با Object Injection عمومی یکسان نیست.

در Object Injection ممکن است User فقط Property Object مشخص خودش را تغییر دهد.

مثلاً:

settings.theme

در Prototype Pollution تغییر به Prototype منتقل می‌شود و Objectهای دیگری نیز ممکن است آن را به ارث ببرند.

یعنی Scope مشکل گسترده‌تر است.

مدل:

Normal Object Modification
→ one object

در مقابل:

Prototype Pollution
→ shared inheritance behavior

Prototype Pollution با Mass Assignment چه تفاوتی دارد؟

Mass Assignment زمانی اتفاق می‌افتد که Application مجموعه Propertyهای ورودی User را بدون Allowlist روی Model یا Object حساس اعمال کند.

مثلاً User می‌تواند Propertyای را Set کند که نباید قابل تغییر باشد.

در Prototype Pollution هدف Prototype Chain است.

این دو مشکل می‌توانند Pattern مشابهی مثل Assignment کنترل‌نشده داشته باشند، اما Root Cause و Impact متفاوت است.

در Mass Assignment User ممکن است role همان Account خودش را عوض کند.

در Prototype Pollution Property ممکن است توسط Objectهای بیشتری Inherit شود.

Prototype Pollution با Insecure Deserialization فرق دارد؟

بله.

Insecure Deserialization بیشتر درباره پردازش ناامن Representation سریال‌شده و تبدیل آن به Object یا Object Graph است.

Prototype Pollution درباره تغییر Prototype Chain است.

ممکن است Parsing داده بخشی از Source باشد، اما وجود JSON یا Serialization به‌تنهایی Prototype Pollution محسوب نمی‌شود.

این تفاوت هنگام گزارش Vulnerability اهمیت دارد.

چرا Dependencyها اهمیت دارند؟

Application Node.js معمولاً ده‌ها یا صدها Dependency مستقیم و غیرمستقیم دارد.

Functionهایی مانند:

Merge،

Clone،

Path Setter،

Object Utility،

Query Parser

اغلب توسط Packageهای ثالث تأمین می‌شوند.

حتی اگر Application Code مستقیماً Dynamic Object Merge انجام ندهد، Dependency ممکن است این کار را انجام دهد.

بنابراین Security Review فقط Source Code پروژه نیست.

Dependency Tree نیز باید بررسی شود.

Packageهای قدیمی که Security Fix دریافت کرده‌اند باید Upgrade شوند.

Lockfile باید Version واقعی Production را نشان دهد.

SCA یا Software Composition Analysis می‌تواند Advisoryهای شناخته‌شده را پیدا کند.

آیا فیلتر کردن فقط proto کافی است؟

خیر.

این یکی از مهم‌ترین اشتباهات دفاعی است.

__proto__ مسیر شناخته‌شده‌ای برای Prototype Access است.

اما MDN توضیح می‌دهد Prototype می‌تواند از طریق constructor.prototype نیز قابل دسترس باشد. به همین دلیل Disable کردن __proto__ Attack Surface را کاهش می‌دهد اما Protection کامل نیست.

پس Defense باید چندلایه باشد:

Input Schema،

Property Allowlist،

Safe Object Construction،

Own Property Check،

Runtime Hardening.

Schema Validation؛ مهم‌ترین خط دفاع

ورودی User نباید Structure آزاد داشته باشد مگر واقعاً Business Requirement باشد.

فرض کنید API انتظار دارد:

{
  "name": "Ali",
  "email": "[email protected]"
}

Schema باید دقیقاً همین Propertyها را قبول کند.

Property سوم ناشناخته باید Reject شود.

MDN توصیه می‌کند از Validatorهایی مانند AJV یا Zod استفاده شود و در Schemaها Propertyهای ناشناخته پذیرفته نشوند؛ برای JSON Schema مفهوم additionalProperties: false نمونه‌ای از این سیاست است.

این Approach بسیار بهتر از تلاش برای Block کردن هزاران Input بد است.

اصل:

Accept expected structure

بهتر از:

Accept everything except known bad values

است.

Allowlist Keyها بهتر از Denylist است

اگر Dynamic Property واقعاً لازم است، مجموعه Keyهای مجاز باید مشخص باشد.

مثلاً:

const allowedKeys = new Set([
  "name",
  "email",
  "language"
]);

قبل از Assignment بررسی شود Key در این مجموعه است.

این مدل نسبت به Denylist بسیار قابل اعتمادتر است.

چون Denylist باید تمام روش‌های دسترسی ناخواسته را از قبل بشناسد.

در حالی که Allowlist فقط Business Propertyهای واقعی را قبول می‌کند.

Object.create(null) چیست؟

Object معمولی JavaScript از Object.prototype ارث می‌برد.

اما می‌توان Objectی بدون Prototype ایجاد کرد:

const data = Object.create(null);

در این Object Prototype Chain معمول وجود ندارد.

OWASP استفاده از Object.create(null) را برای مواردی که واقعاً Object Dictionary لازم است توصیه می‌کند.

MDN نیز Null-Prototype Object را یکی از مهم‌ترین Defenseها می‌داند، زیرا Object نه Propertyهای معمول Prototype را به ارث می‌برد و نه Lookup به همان Prototype Chain انجام می‌دهد.

این روش برای:

Dictionary،

Lookup Table،

Config Intermediate Object،

و Data Container

می‌تواند مناسب باشد.

Map بهتر است یا Object؟

گاهی Developer از Object فقط به‌عنوان Key-Value Store استفاده می‌کند:

const permissions = {};

در چنین مواردی Map می‌تواند انتخاب مناسب‌تری باشد.

مثلاً:

const permissions = new Map();

permissions.set("edit", true);

Map.get() فقط Entry واقعی خود Map را برمی‌گرداند و از Object.prototype Lookup نمی‌کند.

OWASP استفاده از Map و Set را به‌جای Object Literal در موارد مناسب توصیه می‌کند.

MDN نیز همین راهکار را در چک‌لیست دفاعی Prototype Pollution پیشنهاد می‌کند.

Object.hasOwn چرا مهم است؟

روش زیر:

if (object.property) {

مشخص نمی‌کند Property متعلق به خود Object است یا Prototype.

برای Security-Sensitive Logic بهتر است از:

Object.hasOwn(object, "property")

استفاده شود.

MDN این روش را برای جلوگیری از اعتماد ناخواسته به Propertyهای Inherited توصیه می‌کند.

این موضوع برای Authorization بسیار مهم‌تر است.

مثلاً:

if (
  Object.hasOwn(user, "role") &&
  user.role === "admin"
) {
  // ...
}

چرا for...in می‌تواند مشکل‌ساز باشد؟

for...in فقط Own Propertyها را پیمایش نمی‌کند.

Propertyهای Enumerable از Prototype Chain نیز می‌توانند وارد Loop شوند.

MDN توصیه می‌کند در مواقعی که فقط Own Keyها لازم است از:

Object.keys()

یا Iteration مناسب روی همان Keys استفاده شود.

مثلاً:

for (const key of Object.keys(input)) {
  process(input[key]);
}

از نظر Scope واضح‌تر از:

for (const key in input) {
  process(input[key]);
}

است.

Object.freeze چه کمکی می‌کند؟

Object.freeze() می‌تواند از اضافه شدن Property جدید و تغییر Propertyهای موجود روی Object جلوگیری کند.

برای محیط‌های حساس می‌توان بعضی Prototypeها را Freeze کرد.

OWASP Object.freeze() و Object.seal() را به‌عنوان مکانیزم‌های Hardening مطرح می‌کند.

اما این روش Caveat دارد.

بعضی Libraryها یا Polyfillها ممکن است عمداً Built-in Prototype را تغییر دهند.

Freezing زودهنگام می‌تواند Compatibility را خراب کند.

همچنین Object.freeze() به‌صورت پیش‌فرض Deep Freeze نیست.

MDN توصیه می‌کند در محیط‌های بسیار حساس از رویکردهای Lockdown کامل‌تر استفاده شود و محدودیت Freeze ساده در نظر گرفته شود.

فلگ --disable-proto در Node.js

Node.js گزینه Runtime زیر را ارائه می‌کند:

--disable-proto=MODE

MODE می‌تواند delete یا throw باشد.

در حالت delete، Property مربوط حذف می‌شود.

در حالت throw، دسترسی به آن Exception ایجاد می‌کند.

این قابلیت در مستندات Node.js فعلی نیز وجود دارد.

برای مثال Hardening Runtime می‌تواند با Policy مناسب اجرا شود:

node --disable-proto=throw app.js

اما این Defense کامل نیست.

OWASP و MDN هر دو تأکید می‌کنند مسیرهای دیگری مانند constructor.prototype همچنان می‌توانند مطرح باشند.

بنابراین این Flag باید Defense in Depth باشد.

آیا حذف proto کافی است؟

خیر.

حتی اگر:

Object.prototype.__proto__

غیرفعال شود، Prototype Chain و Constructor Mechanism همچنان وجود دارند.

Application باید Input Validation و Safe Object Handling داشته باشد.

Runtime Flag فقط یک Entry Point را محدود می‌کند.

Security Architecture نباید به یک Switch وابسته باشد.

چگونه Prototype Pollution را در Code Review پیدا کنیم؟

Code Review بهتر است روی Data Flow تمرکز کند.

ابتدا Sourceها شناسایی شوند.

برای مثال:

req.body
req.query
req.params
URLSearchParams
JSON.parse(...)
postMessage
localStorage
third-party API response

سپس Functionهایی پیدا شوند که Object می‌سازند یا Property Dynamic می‌نویسند.

نمونه Patternهای مهم:

obj[key] = value
deepMerge(...)
extend(...)
setPath(...)
recursive copy
for...in assignment
Object.assign(...)

بعد بررسی شود:

آیا Key Validate شده؟

آیا Object Prototype دارد؟

آیا Propertyهای ناشناخته Reject می‌شوند؟

آیا Data بعداً وارد Gadget حساس می‌شود؟

SAST و Prototype Pollution

Static Application Security Testing می‌تواند Patternهایی مانند Dynamic Property Assignment یا استفاده از Library آسیب‌پذیر را شناسایی کند.

اما Prototype Pollution معمولاً Data-Flow Sensitive است.

ممکن است Line زیر:

target[key] = value;

کاملاً امن باشد چون key از Enum داخلی آمده است.

یا ممکن است خطرناک باشد چون key مستقیم از Request آمده است.

بنابراین نتیجه SAST باید Contextual Review شود.

False Positive و False Negative هر دو محتمل‌اند.

SCA برای Dependencyها

Software Composition Analysis برای این Vulnerability اهمیت زیادی دارد.

زیرا تعداد قابل توجهی از Prototype Pollutionهای تاریخی در Utility Packageها و Parser Libraryها پیدا شده‌اند.

فرایند مناسب باید:

Package Lock را اسکن کند،

Dependencyهای Transitive را ببیند،

Advisoryها را مانیتور کند،

و Version آسیب‌پذیر را Upgrade کند.

فقط بررسی package.json سطح اول کافی نیست.

تست Client-Side Prototype Pollution

در Application خودتان یا محیط دارای مجوز، Browser DevTools و ابزارهای تخصصی می‌توانند Data Flow را بررسی کنند.

هدف دفاعی باید یافتن Source و Gadget بدون اجرای Payload مخرب باشد.

بهتر است از Markerهای بی‌خطر و Propertyهایی استفاده شود که صرفاً تغییر قابل مشاهده و غیرمخرب ایجاد می‌کنند.

PortSwigger ابزار DOM Invader را برای بررسی Client-Side Prototype Pollution و Source/Gadget Analysis ارائه می‌کند.

اما نتیجه ابزار نیز باید دستی تأیید شود. تفاوت Client-Side و Server-Side Prototype Pollution

تست Server-Side Prototype Pollution

Server-Side Testing حساس‌تر است.

تغییر Prototype در Node Process می‌تواند Persist کند و روی درخواست‌های کاربران دیگر نیز اثر بگذارد.

PortSwigger هشدار می‌دهد که Probeهای مخرب می‌توانند Server را وارد وضعیت خراب کنند یا DoS ایجاد کنند و گاهی فقط Restart Process تغییرات را پاک می‌کند.

بنابراین بهترین محیط:

Development،

Staging،

Container موقت،

یا Test Process اختصاصی

است.

روی Production باید از تستی که Global State را تغییر می‌دهد پرهیز شود.

تست امن چه ویژگی‌ای دارد؟

تست دفاعی نباید:

Process را Crash کند،

Authorization User واقعی را تغییر دهد،

Command اجرا کند،

File بنویسد،

یا Global Configuration پایدار را خراب کند.

Proof کافی می‌تواند نشان دهد که Object جدید یک Property Marker بی‌خطر و غیرمنتظره به ارث گرفته است.

اگر White-Box Access دارید، Unit Test بسیار بهتر از Black-Box Experiment روی Production است.

Unit Test برای Prototype Pollution

برای Functionهای Merge یا Parse می‌توان Regression Test ساخت.

مثلاً Test باید بررسی کند:

ورودی دارای Keyهای ممنوع Reject می‌شود.

Object نتیجه Null Prototype دارد.

Property ناشناخته حذف می‌شود.

Prototype اصلی قبل و بعد Function تغییری نمی‌کند.

Property امنیتی فقط Own Property است.

این Testها باید بعد از Upgrade Dependency نیز اجرا شوند.

Fuzzing امن

برای Parser یا Merge Functionهای داخلی می‌توان Fuzzing محدود انجام داد.

اما Test Cases باید روی:

Keyهای غیرمنتظره،

Nested Structure،

Deep Object،

Typeهای متفاوت

تمرکز کنند.

هدف Crash یا Exploit نیست.

هدف یافتن اختلاف میان Structure مورد انتظار و Structure واقعی است.

Prototype Pollution و Express.js

Express.js به‌خودی‌خود به معنی Prototype Pollution نیست.

اما Applicationهای Express معمولاً:

req.query،

req.body،

Middleware Parserها،

Configuration Objectها

را زیاد استفاده می‌کنند.

Risk زمانی ایجاد می‌شود که داده Request مستقیماً وارد Utility Merge یا Dynamic Assignment شود.

پس Audit باید Middleware Chain را نیز بررسی کند.

Parser Version و Configuration مهم است.

Prototype Pollution در APIهای Node.js

API Endpointهایی که PATCH یا Update Generic ارائه می‌کنند حساس‌اند.

برای مثال API ممکن است اجازه دهد Client Mapی از Fieldها را ارسال کند.

اگر Business Fieldها مشخص‌اند، بهتر است Endpoint صریح باشد:

name
email
language

نه اینکه Arbitrary Object گرفته شود و تمام Keyها Merge شوند.

هرچه API عمومی‌تر باشد، Validation باید قوی‌تر باشد.

Prototype Pollution و GraphQL

GraphQL Schema ذاتاً Structure ورودی را بهتر از JSON Arbitrary محدود می‌کند.

اما Resolverها ممکن است Input را داخل Config یا Domain Object Merge کنند.

اگر Scalar سفارشی JSON یا Object آزاد پذیرفته شود، Risk دوباره مطرح می‌شود.

Schema داشتن فقط وقتی کمک می‌کند که واقعاً Propertyهای قابل قبول محدود باشند.

Prototype Pollution و WebSocket

پیام WebSocket نیز User Input است.

اگر JSON Message از Socket Parse و با State داخلی Merge شود، همان Risk HTTP API وجود دارد.

Transport فرقی ایجاد نمی‌کند.

قاعده:

Untrusted structured input
→ validate before object mutation

در همه Interfaceها اعمال می‌شود.

Prototype Pollution در Frontend Frameworkها

React، Vue یا Angular به‌خودی‌خود Prototype Pollution محسوب نمی‌شوند.

اما Application و Dependencyهای اطراف آنها ممکن است:

Query String Parse کنند،

Configuration Merge کنند،

State Restore کنند،

یا Object Utility استفاده کنند.

Source می‌تواند قبل از رسیدن Data به Framework باشد.

همچنین Gadget می‌تواند داخل Library یا Browser API وجود داشته باشد.

بنابراین Audit فقط Component Code کافی نیست.

Prototype Pollution و WordPress

WordPress Core عمدتاً PHP است، اما Dashboard، Block Editor، Pluginها و Frontendهای مدرن مقدار زیادی JavaScript دارند.

Plugin یا Theme ممکن است Bundleهای JavaScript بزرگ، React Component، npm Dependency یا REST Data Parser داشته باشد.

Prototype Pollution در WordPress معمولاً مشکل مستقیم PHP نیست.

Attack Surface در JavaScript Asset یا Node.js Build/Service مرتبط قرار می‌گیرد.

برای Plugin Development باید:

Dependencyهای npm به‌روز باشند،

Bundleهای قدیمی بررسی شوند،

Data REST قبل از Merge Validate شود،

و Utility Libraryهای Object Management نسخه امن داشته باشند.

آیا Prototype Pollution می‌تواند Supply Chain Risk باشد؟

بله، از دو جهت.

اول اینکه یک Dependency Third-Party ممکن است Prototype Pollution Vulnerability داشته باشد.

دوم اینکه تعداد زیادی Application همان Dependency را مصرف کنند.

در نتیجه یک ضعف کوچک در Utility Package می‌تواند تعداد زیادی پروژه را درگیر کند.

این موضوع نشان می‌دهد Dependency Hygiene بخشی از AppSec است، نه صرفاً Maintenance.

چگونه Dependency امن انتخاب کنیم؟

نام Package یا تعداد Download به‌تنهایی معیار امنیت نیست.

بهتر است موارد زیر بررسی شوند:

Maintenance فعال،

نسخه‌های جدید،

Security Advisory،

Dependencyهای فرعی،

Test Coverage،

و نیاز واقعی به Package.

اگر برای یک Function ساده Package سنگینی با Dependency Tree بزرگ اضافه می‌شود، Attack Surface نیز افزایش می‌یابد.

Propertyهای Security-Sensitive را Explicit تعریف کنید

اگر Object قرار است Security Decision داشته باشد، Propertyهای اصلی را همیشه Set کنید.

بد:

const user = {
  username: "ali"
};

// isAdmin missing

بهتر:

const user = {
  username: "ali",
  isAdmin: false
};

در این حالت Logic کمتر به Prototype Lookup متکی است.

Schema Database و API نیز بهتر است این Property را Mandatory کنند.

Null Prototype Object چه محدودیت‌هایی دارد؟

Object.create(null) عالی است، اما Object حاصل Methodهای معمول Object.prototype را ندارد.

مثلاً بعضی Codeها انتظار دارند:

obj.toString()

یا سایر Methodهای Object در دسترس باشند.

پس جایگزینی کورکورانه تمام Objectها با Null-Prototype مناسب نیست.

این روش بیشتر برای Dictionary و Internal Maps مناسب است.

Design باید آگاهانه باشد.

Map و Set چه زمانی انتخاب بهتری هستند؟

اگر نیاز شما:

Key-Value Store،

Membership Check،

Unique Values

است، Map و Set Semantics واضح‌تری دارند.

به‌جای استفاده از Object برای مجموعه Permission:

const permissions = {};

می‌توان:

const permissions = new Set();

ساخت.

این تصمیم هم Code را خواناتر می‌کند و هم Prototype Lookup Object عادی را حذف می‌کند.

Object.freeze را کجا استفاده کنیم؟

Freeze برای Configهایی که بعد از Initialization نباید تغییر کنند مفید است.

مثلاً Security Policy:

const policy = Object.freeze({
  allowGuest: false,
  requireMFA: true
});

اما Freeze فقط همان Object را Freeze می‌کند و Objectهای Nested ممکن است نیاز به Strategy جدا داشته باشند.

همچنین Library Compatibility باید تست شود.

Least Privilege در Node.js

فرض کنیم Prototype Pollution در نهایت Gadget خطرناکی پیدا کند.

اگر Node Process دسترسی Root، File System گسترده، Credentialهای اضافی و Network Access نامحدود داشته باشد، Impact بزرگ‌تر می‌شود.

Defense in Depth باید شامل:

User سیستم محدود،

Container Isolation،

Read-Only File System در صورت امکان،

Secretهای حداقلی،

Egress Control،

و محدود کردن Permissionهای Cloud

باشد.

Prototype Pollution Fix اصلی همچنان Code است، اما Least Privilege Blast Radius را کاهش می‌دهد.

Monitoring برای Prototype Pollution

Detection Production آسان نیست.

اما Signalهای مختلف می‌توانند کمک کنند:

Propertyهای غیرمنتظره در Config،

تغییر Behavior چند Request نامرتبط،

Errorهای غیرمعمول Type،

Authorization Anomaly،

Crash بعد از Input خاص،

تغییر HTTP Method یا Option غیرمنتظره،

و Dependency Security Alert.

Instrumentation داخلی می‌تواند Object Configurationهای Critical را Validate کند.

اما Logging نباید کل Request Body حساس را ذخیره کند.

Incident Response در Prototype Pollution

اگر احتمال Server-Side Prototype Pollution وجود دارد، باید Global Process State را غیرقابل اعتماد در نظر گرفت.

اقدامات معمول شامل:

جلوگیری از ورودی مخرب،

Restart یا Replace کردن Process آلوده،

Patch کردن Root Cause،

Upgrade Dependency،

Invalidate کردن Session یا Credential در صورت وجود شواهد Impact،

و Review Logها

است.

صرف حذف Request مخرب کافی نیست؛ Prototype ممکن است تا پایان Process Lifetime تغییر کرده باشد. PortSwigger Persistence این نوع Pollution را یکی از تفاوت‌های مهم Server-Side می‌داند.

چگونه Severity را تعیین کنیم؟

Prototype Pollution Severity ثابت نیست.

Source بدون Gadget ممکن است Impact محدودی داشته باشد.

اما همان Source در Application دیگر ممکن است Gadgetی برای:

Authorization Bypass،

DOM XSS،

Server Behavior Manipulation،

DoS،

یا Code Execution

داشته باشد.

بنابراین Severity باید بر اساس:

Reachability Source،

Scope Prototype،

Persistence،

Gadget،

Sink،

Authentication Requirement،

و Blast Radius

تعیین شود.

نباید هر Prototype Pollution را خودکار Critical اعلام کرد.

گزارش امنیتی حرفه‌ای Prototype Pollution

گزارش خوب باید Root Cause و Data Flow را نشان دهد.

مثلاً:

Source:
User-controlled JSON object.

Unsafe operation:
Nested object properties are recursively merged
without a strict property allowlist.

Pollution effect:
A property becomes available through the prototype chain.

Gadget:
Authorization configuration reads the property
without checking ownership.

Impact:
Security behavior may be altered.

Remediation:
Validate schema, reject unknown/sensitive keys,
use own-property checks and safe object structures.

این گزارش برای Developer قابل‌اقدام‌تر از جمله ساده:

Prototype Pollution found

است.

چک‌لیست دفاعی جلوگیری از Prototype Pollution

کنترل امنیتیوضعیت پیشنهادی
Input SchemaStructure ورودی کاملاً مشخص و Validate شود
Unknown PropertiesPropertyهای ناشناخته Reject شوند
Dynamic Keysفقط Keyهای Allowlist‌شده پذیرفته شوند
__proto__در Input Dynamic مجاز نباشد
constructorدر مسیرهای Dynamic کنترل شود
prototypeدر مسیرهای Dynamic کنترل شود
Merge FunctionRecursive Mergeهای سفارشی Audit شوند
DependencyPackageهای Object/Query Utility به‌روز باشند
Dictionaryدر صورت امکان Map استفاده شود
Object Dictionaryاز Object.create(null) استفاده شود
Property Readبرای داده حساس Object.hasOwn() بررسی شود
Iterationترجیحاً Object.keys() به‌جای for...in
Default ValuesPropertyهای امنیتی Explicit تعریف شوند
Authorizationبه Property Optional ارث‌رسیده اعتماد نشود
Node.js--disable-proto به‌عنوان Defense in Depth بررسی شود
Freezeبرای Configهای ثابت و حساس در صورت سازگاری استفاده شود
SASTDynamic Property Assignment بررسی شود
SCADependencyهای آسیب‌پذیر شناسایی و Upgrade شوند
TestingServer-Side تست در محیط ایزوله انجام شود
Monitoringرفتار و Configهای غیرعادی پایش شوند
Incident ResponseProcess آلوده پس از Fix Restart/Replace شود
Least PrivilegeNode Process با Permission حداقلی اجرا شود

اشتباهات رایج در جلوگیری از Prototype Pollution

فقط فیلتر کردن __proto__

این روش کافی نیست، زیرا مسیرهای دیگری برای رسیدن به Prototype وجود دارند. MDN مشخصاً constructor.prototype را نیز مطرح می‌کند.

اعتماد به JSON.parse

Parse ساده لزوماً Pollution ایجاد نمی‌کند، اما Object تولیدشده ممکن است بعداً طی Merge خطرناک شود.

استفاده از Denylist طولانی

بهتر است Propertyهای مجاز Business مشخص باشند و سایر Propertyها Reject شوند.

استفاده از Object برای هر نوع Map

برای Dictionary عمومی، Map یا Null-Prototype Object گاهی انتخاب امن‌تر و واضح‌تری هستند.

استفاده از for...in روی داده User

این Loop می‌تواند Propertyهای Prototype را نیز مشاهده کند. برای Own Propertyها Object.keys() گزینه روشن‌تری است.

اعتماد به Property Optional

Property حساس مانند isAdmin نباید فقط در صورت True بودن تعریف شود و در سایر موارد Missing بماند.

تصور اینکه Dependency جدید همیشه امن است

Prototype Pollution سابقه طولانی در Utility Packageهای Object Manipulation دارد. Version و Advisory باید بررسی شوند.

تست مخرب روی Production

Server-Side Pollution ممکن است تا Restart Process باقی بماند و همه کاربران را تحت تأثیر قرار دهد.

استفاده از Node Flag به‌عنوان تنها دفاع

--disable-proto فقط بخشی از Attack Surface را محدود می‌کند و جای Validation را نمی‌گیرد. مهم‌ترین روش جلوگیری از Prototype Pollution

مهم‌ترین روش جلوگیری از Prototype Pollution چیست؟

اگر بخواهیم تمام توصیه‌های امنیتی را در یک اصل خلاصه کنیم:

«Object Structure کنترل‌شده توسط کاربر را مستقیماً و بدون Schema یا Property Allowlist وارد Objectهای داخلی برنامه نکنید.»

Application باید دقیقاً بداند چه Propertyهایی انتظار دارد.

ورودی اضافه باید Reject شود.

Objectهایی که نقش Dictionary دارند بهتر است از Map یا Null Prototype استفاده کنند.

Property امنیتی باید Own Property باشد و Default صریح داشته باشد.

Dependencyهایی که Deep Merge انجام می‌دهند باید Patch باشند.

OWASP مجموعه همین اقدامات را به‌عنوان روش‌های اصلی Prevention پیشنهاد می‌کند.

معماری Defense in Depth

مدل امن می‌تواند چنین باشد:

Untrusted Input
      ↓
Schema Validation
      ↓
Unknown Key Rejection
      ↓
Safe Object Construction
      ↓
Own-Property Access
      ↓
Business Logic

و در Node.js:

Secure Coding
      +
Updated Dependencies
      +
--disable-proto
      +
Least Privilege
      +
Monitoring

این مدل بسیار مقاوم‌تر از یک Regex یا Blacklist منفرد است.

سؤالات متداول درباره Prototype Pollution

Prototype Pollution چیست؟

Prototype Pollution آسیب‌پذیری‌ای در JavaScript است که در آن مهاجم می‌تواند Propertyهای Prototype یک Object را تغییر دهد و باعث شود Objectهای دیگر مقادیر غیرمنتظره را از Prototype Chain به ارث ببرند. MITRE آن را با CWE-1321 دسته‌بندی می‌کند.

Prototype در JavaScript چیست؟

Prototype Objectی است که Object دیگر می‌تواند Property و Methodهای آن را به ارث ببرد. JavaScript هنگام پیدا نکردن Property روی Object، Prototype Chain را جست‌وجو می‌کند.

Object.prototype چیست؟

یکی از Prototypeهای بنیادی JavaScript است که بسیاری از Objectهای معمولی در نهایت از آن ارث می‌برند. تغییر غیرمجاز آن می‌تواند تعداد زیادی Object را تحت تأثیر قرار دهد.

CWE مربوط به Prototype Pollution چیست؟

شناسه رسمی MITRE برای این ضعف CWE-1321 است: Improperly Controlled Modification of Object Prototype Attributes.

آیا Prototype Pollution فقط در Browser اتفاق می‌افتد؟

خیر. هم Client-Side JavaScript و هم Applicationهای Server-Side مبتنی بر Node.js می‌توانند در معرض آن باشند.

Client-Side Prototype Pollution چه خطری دارد؟

در صورت وجود Gadget مناسب می‌تواند رفتار Frontend را تغییر دهد و یکی از Impactهای مهم آن DOM XSS است.

Server-Side Prototype Pollution چه خطری دارد؟

بسته به Gadget می‌تواند Logic برنامه، Authorization یا Configuration سرور را تغییر دهد، DoS ایجاد کند و در شرایط خاص پیامدهای شدیدتری داشته باشد. اثر Pollution ممکن است برای Lifetime یک Node Process باقی بماند.

Gadget در Prototype Pollution چیست؟

Gadget کدی است که Property آلوده‌شده از Prototype را می‌خواند و آن را در یک Context قابل سوءاستفاده مصرف می‌کند.

آیا هر Prototype Pollution منجر به RCE می‌شود؟

خیر. RCE فقط در صورت وجود Gadget و Sink مناسب ممکن است. Severity باید براساس Application واقعی تعیین شود.

آیا JSON.parse آسیب‌پذیر است؟

JSON Parse ساده لزوماً Prototype را تغییر نمی‌دهد. خطر می‌تواند زمانی ایجاد شود که Object Parseشده بعداً توسط Assignment یا Merge ناامن پردازش شود.

آیا Object.assign خطرناک است؟

خود Object.assign() همیشه Vulnerability نیست، اما MDN نشان می‌دهد در بعضی Data Flowها Assignment Propertyهای خاص می‌تواند Prototype Target را تغییر دهد. بنابراین ورودی باید قبل از Merge Validate شود.

آیا Object Spread امن است؟

Object Spread Setter مربوط به __proto__ را به همان شکل Object.assign() Trigger نمی‌کند، اما جای Schema Validation و Safe Design را نمی‌گیرد.

چرا Object.hasOwn() مهم است؟

چون مشخص می‌کند Property واقعاً روی خود Object وجود دارد و صرفاً از Prototype Chain به ارث نرسیده است. این موضوع برای Authorization و Security Configuration اهمیت زیادی دارد.

Object.create(null) چه کمکی می‌کند؟

Object بدون Prototype می‌سازد، بنابراین Propertyهای Object.prototype را به ارث نمی‌برد. OWASP و MDN آن را برای Dictionaryها و Objectهای Dynamic پیشنهاد می‌کنند.

Map از Object امن‌تر است؟

در Contextهایی که فقط Key-Value Store لازم است، Map می‌تواند Attack Surface Prototype Lookup را حذف کند و انتخاب مناسب‌تری باشد.

آیا فیلتر کردن proto کافی است؟

خیر. مسیرهایی مانند constructor.prototype نیز باید در Threat Model باشند.

Node.js چگونه در برابر Prototype Pollution Hardening می‌شود؟

Node.js گزینه --disable-proto=delete یا --disable-proto=throw دارد که دسترسی به Object.prototype.__proto__ را محدود می‌کند. این کنترل Defense in Depth است و جای Validation را نمی‌گیرد.

آیا Object.freeze مشکل را کامل حل می‌کند؟

خیر. Freeze می‌تواند تغییر Objectها را محدود کند، اما Compatibility و Deep Objectها باید در نظر گرفته شوند و ممکن است Application یا Polyfill به تغییر Prototype نیاز داشته باشد.

چرا for...in توصیه نمی‌شود؟

چون علاوه بر Own Propertyها می‌تواند Propertyهای Enumerable موجود در Prototype Chain را نیز پیمایش کند. MDN برای مواردی که فقط Own Key لازم است Object.keys() را توصیه می‌کند.

بهترین روش جلوگیری از Prototype Pollution چیست؟

Schema Validation سخت‌گیرانه، Reject کردن Propertyهای ناشناخته، محدودسازی Dynamic Keyها، استفاده از Null-Prototype Object یا Map در Context مناسب، بررسی Own Property و به‌روزرسانی Dependencyها مهم‌ترین روش‌ها هستند.

آیا Prototype Pollution در WordPress ممکن است؟

در JavaScript Pluginها، Themeها، Block Editor Extensionها یا سرویس‌های Node.js مرتبط ممکن است رخ دهد. WordPress Core عمدتاً PHP است، بنابراین مسئله معمولاً در JavaScript Ecosystem یا Dependencyهای افزونه قرار دارد.

آیا WAF جلوی Prototype Pollution را می‌گیرد؟

WAF ممکن است بعضی Inputهای مشکوک را Block کند، اما Root Cause داخل Object Processing Application است. WAF جای Schema Validation و Secure Coding را نمی‌گیرد.

آیا TypeScript از Prototype Pollution جلوگیری می‌کند؟

خیر. TypeScript Typeها عمدتاً Compile-Time هستند. داده Runtime همچنان باید Validate شود. Interface TypeScript جای Runtime Schema Validation نیست.

آیا HTTPS از Prototype Pollution جلوگیری می‌کند؟

خیر. HTTPS فقط ارتباط را در Transit محافظت می‌کند. اگر Application داده معتبر TLS را به شکل ناامن در Object Merge کند، Prototype Pollution همچنان ممکن است.

آیا CSP جلوی Prototype Pollution را می‌گیرد؟

CSP ممکن است Impact بعضی DOM XSS Chainها را کاهش دهد، اما Root Prototype Pollution را برطرف نمی‌کند.

جمع‌بندی

Prototype Pollution یکی از آسیب‌پذیری‌های خاص و مهم اکوسیستم JavaScript است که ریشه آن در Prototypal Inheritance و پردازش ناامن Propertyهای Dynamic قرار دارد.

JavaScript وقتی Propertyای روی Object پیدا نمی‌کند، Prototype Chain را جست‌وجو می‌کند. همین رفتار طبیعی زبان وقتی خطرناک می‌شود که User بتواند Prototype مشترک Objectها را تغییر دهد.

در این شرایط Application ممکن است Propertyهایی را ببیند که Developer هیچ‌گاه روی Object موردنظر تعریف نکرده است.

Prototype Pollution معمولاً دو مرحله دارد:

اول Pollution Source که Prototype را تغییر می‌دهد.

دوم Gadget که Property آلوده‌شده را در Context حساس مصرف می‌کند.

به همین دلیل وجود Source به‌تنهایی همیشه به معنی Impact شدید نیست.

در Client-Side، Gadget می‌تواند به DOM XSS منجر شود.

در Server-Side Node.js، Gadget می‌تواند Logic، Configuration، Authorization یا رفتار Server را تغییر دهد و در برخی Applicationها Impact شدیدتری ایجاد کند. PortSwigger تأکید می‌کند Pollution Server-Side همچنین ممکن است تا پایان Lifetime Process باقی بماند و به همین دلیل تست آن روی Production پرریسک است.

یکی از مهم‌ترین Root Causeها Dynamic Property Assignment و Recursive Merge است.

Application یا Library داده‌ای را از Query String، JSON یا API دریافت می‌کند و Propertyهای آن را بدون Schema دقیق وارد Object داخلی می‌کند.

نام‌هایی مانند __proto__، constructor و prototype در چنین Data Flowهایی باید به‌عنوان Security-Sensitive در نظر گرفته شوند. MDN صریحاً توصیه می‌کند Dynamic Keyها Validate شوند و Propertyهای غیرضروری Reject شوند.

اما Prevention نباید به Blacklist سه کلمه محدود شود.

بهترین دفاع این است که Application فقط Structure مورد انتظار را بپذیرد.

Schema Validation باید Propertyهای مجاز و Type آنها را تعیین کند.

Objectهای Dictionary در صورت امکان با Map یا Object.create(null) ساخته شوند.

Propertyهای Security-Sensitive فقط با Own-Property Check خوانده شوند.

for...in برای داده غیرقابل‌اعتماد باید با احتیاط استفاده شود و Default Valueها باید صریح باشند.

OWASP و MDN هر دو همین Defenseهای چندلایه را توصیه می‌کنند.

در Node.js نیز --disable-proto می‌تواند Attack Surface را کاهش دهد، اما جای Fix واقعی را نمی‌گیرد، زیرا Prototype از مسیرهای دیگری نیز قابل دسترسی است.

Dependency Management نیز بخش مهمی از دفاع است.

Applicationهایی که Packageهای قدیمی برای Deep Merge، Query Parsing یا Object Utility استفاده می‌کنند باید Dependency Tree را مرتباً اسکن و Versionهای آسیب‌پذیر را Upgrade کنند.

در نهایت مهم‌ترین سؤال هنگام بررسی Prototype Pollution این است:

«آیا داده‌ای که User کنترل می‌کند می‌تواند نام یا مسیر Propertyهای JavaScript را تعیین کند، و آیا Application بعداً به Propertyهای ارث‌رسیده از Prototype در یک تصمیم حساس اعتماد می‌کند؟»

اگر پاسخ مثبت باشد، Data Flow باید با دقت بررسی و Object Handling بازطراحی شود.

Prototype Pollution نمونه‌ای روشن از این اصل امنیتی است که در JavaScript، «وجود نداشتن یک Property روی Object» لزوماً به این معنی نیست که آن Property هنگام دسترسی وجود نخواهد داشت. امنیت زمانی قابل اتکاست که Application Structure داده را صریح تعریف کند، ورودی را محدود کند و هیچ تصمیم حساس را به مقدار ناخواسته‌ای از Prototype Chain واگذار نکند.

مطالب مرتبط