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

Web Cache Poisoning چیست؟ بررسی مسموم‌سازی کش سایت و روش‌های جلوگیری

Web Cache Poisoning زمانی رخ می‌دهد که مهاجم بتواند Response دستکاری‌شده‌ای را زیر یک Cache Key عمومی ذخیره کند تا همان محتوا بعداً به کاربران دیگر تحویل داده شود. ریشه این ضعف معمولاً اختلاف میان Inputهایی است که Backend برای تولید Response پردازش می‌کند و Inputهایی که Cache در Cache Key لحاظ می‌کند. شناسایی Unkeyed Inputها، تنظیم صحیح Cache-Control و Vary، محدود کردن Cache صفحات حساس و هماهنگ‌سازی Cache Key با رفتار واقعی Application از مهم‌ترین روش‌های جلوگیری از Web Cache Poisoning هستند.

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

Web Cache Poisoning یا «مسموم‌سازی کش وب» زمانی رخ می‌دهد که مهاجم بتواند وب‌سایت یا زیرساخت Cache را وادار کند یک Response دستکاری‌شده را ذخیره کند و سپس همان Response به کاربران دیگری که Request آنها Cache Key مشابهی دارد نمایش داده شود. این ضعف معمولاً از اختلاف میان ورودی‌هایی که Backend پردازش می‌کند و ورودی‌هایی که Cache برای تفکیک Responseها در Cache Key در نظر می‌گیرد ایجاد می‌شود.

مهم‌ترین روش جلوگیری از Web Cache Poisoning این است که هیچ Input کنترل‌شده توسط Client نتواند محتوای Response عمومی را تغییر دهد مگر اینکه همان Input به‌درستی در Cache Key لحاظ شود. صفحات شخصی یا حساس نباید در Shared Cache ذخیره شوند، Headerهای غیرضروری باید حذف یا نادیده گرفته شوند و Cache Ruleها، Cache-Control، Vary و رفتار CDN باید با Data Flow واقعی Application هماهنگ باشند.

Cache وب چیست و چرا استفاده می‌شود؟

برای درک Web Cache Poisoning ابتدا باید خود مفهوم Cache را بشناسیم.

یک وب‌سایت پرترافیک ممکن است در هر دقیقه هزاران یا میلیون‌ها Request مشابه دریافت کند.

اگر Backend مجبور باشد برای هر Request:

Database Query اجرا کند،

Template را Render کند،

APIهای داخلی را فراخوانی کند،

و Response را از صفر بسازد،

مصرف CPU، Database و Network به‌شدت افزایش پیدا می‌کند.

Cache برای حل همین مسئله استفاده می‌شود.

RFC 9111 یک HTTP Cache را محل ذخیره Response Messageها و مجموعه مکانیزم‌هایی تعریف می‌کند که ذخیره، بازیابی و حذف آنها را کنترل می‌کنند. هدف اصلی Cache کاهش زمان پاسخ و مصرف پهنای باند از طریق استفاده مجدد از Response قبلی برای Requestهای معادل است. (rfc-editor.org)

مدل ساده:

User A
   ↓
GET /article
   ↓
Cache MISS
   ↓
Origin Server
   ↓
Response
   ↓
Cache stores response

بعداً:

User B
   ↓
GET /article
   ↓
Cache HIT
   ↓
Cached Response

در Request دوم ممکن است Origin Server اصلاً درگیر نشود.

همین ویژگی باعث افزایش Performance می‌شود.

اما از نظر امنیتی یک پیامد مهم دارد:

اگر Response اشتباهی در Cache ذخیره شود، اشتباه دیگر فقط روی یک Request اثر ندارد.

ممکن است همان Response برای تعداد زیادی User دیگر نیز استفاده شود.

Shared Cache و Private Cache چه تفاوتی دارند؟

RFC 9111 میان Shared Cache و Private Cache تفاوت قائل می‌شود.

Private Cache معمولاً برای یک User استفاده می‌شود؛ Cache مرورگر نمونه شناخته‌شده آن است.

Shared Cache برای چند User مشترک است.

مثال‌ها:

CDN،

Reverse Proxy Cache،

Corporate Proxy،

Edge Cache.

مدل:

             ┌── User A
             │
Shared Cache ├── User B
             │
             └── User C

در Web Cache Poisoning معمولاً Shared Cache اهمیت بیشتری دارد، چون یک Entry مسموم‌شده ممکن است به تعداد زیادی User تحویل داده شود.

RFC 9111 نیز در بخش Security Considerations توضیح می‌دهد که ذخیره محتوای مخرب در Cache می‌تواند دامنه اثر مهاجم را گسترش دهد و Shared Cache این مسئله را به‌طور خاص خطرناک می‌کند. (rfc-editor.org) Cache Key و Unkeyed Input چیست؟

Cache Key چیست؟

Cache نمی‌تواند تمام جزئیات هر Request را با Requestهای قبلی مقایسه کند.

به همین دلیل مجموعه‌ای از مشخصات Request را به‌عنوان Cache Key انتخاب می‌کند.

RFC 9111 می‌گوید Cache Key حداقل از Method و Target URI مربوط به Request تشکیل می‌شود، هرچند Cacheها می‌توانند اطلاعات بیشتری نیز وارد Key کنند. در عمل بسیاری از Cacheها عمدتاً GET Responseها را Cache می‌کنند. (rfc-editor.org)

به‌صورت مفهومی:

GET /products/10
Host: example.com

می‌تواند Keyی شبیه این داشته باشد:

example.com + /products/10

اگر Request دیگری همان Key را تولید کند، Cache ممکن است Response قبلی را برگرداند.

Keyed Input چیست؟

Keyed Input بخشی از Request است که Cache هنگام ساخت Cache Key به آن توجه می‌کند.

مثلاً اگر Query String بخشی از Cache Key باشد:

/page?lang=en

و:

/page?lang=fa

دو Entry متفاوت خواهند داشت.

مدل:

/page?lang=en
      ↓
Cache Key A

و:

/page?lang=fa
      ↓
Cache Key B

در این حالت Response انگلیسی و فارسی از یکدیگر جدا می‌مانند.

Unkeyed Input چیست؟

Unkeyed Input ورودی‌ای است که Backend آن را می‌خواند و ممکن است براساس آن Response را تغییر دهد، اما Cache آن ورودی را بخشی از Cache Key در نظر نمی‌گیرد.

این مفهوم قلب بسیاری از Web Cache Poisoningهای مدرن است.

PortSwigger توضیح می‌دهد وب‌سایت زمانی مستعد Web Cache Poisoning می‌شود که Backend یک Unkeyed Input را پردازش کند و Response حاصل نیز قابل Cache شدن باشد. در چنین شرایطی Cache ممکن است Response تولیدشده برای Request خاص مهاجم را برای Requestهای عادی کاربران نیز معتبر تصور کند. (portswigger.net)

مثلاً فرض کنید Cache Key فقط این باشد:

GET /home
Host: example.com

اما Backend Header زیر را نیز پردازش کند:

X-Forwarded-Host

اگر مقدار این Header URLهای موجود در HTML را تغییر دهد اما در Cache Key وجود نداشته باشد، دو Request:

GET /home

با Headerهای متفاوت ممکن است از دید Cache «یک Request» محسوب شوند.

این همان شکافی است که زمینه Cache Poisoning را ایجاد می‌کند. Web Cache Poisoning چگونه ایجاد می‌شود؟

Web Cache Poisoning چیست؟

Web Cache Poisoning یک تکنیک حمله است که در آن مهاجم شرایطی ایجاد می‌کند تا Cache یک Response نامطلوب یا مخرب را برای یک Cache Key عادی ذخیره کند.

سپس Userهای دیگر با Request معمولی همان Cached Response را دریافت می‌کنند.

PortSwigger این فرایند را به‌طور کلی در سه مرحله توضیح می‌دهد:

شناسایی Unkeyed Input،

وادار کردن Backend به تولید Response ناخواسته،

و Cache شدن آن Response. (portswigger.net)

مدل ساده:

Attacker Request
     ↓
Unkeyed Input changes response
     ↓
Origin produces altered response
     ↓
Shared Cache stores response
     ↓
Normal User
     ↓
Same Cache Key
     ↓
Poisoned Response

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

همین ویژگی Web Cache Poisoning را از بسیاری از Client-Side Attackها متمایز می‌کند.

چرا Web Cache Poisoning خطرناک است؟

در بسیاری از آسیب‌پذیری‌ها، Request مخرب فقط Response همان Attacker را تغییر می‌دهد.

اما Cache می‌تواند آن Response را «تکثیر» کند.

به همین دلیل Impact ممکن است از:

Attacker sees altered response

به:

Thousands of users receive altered response

تبدیل شود.

RFC 9111 نیز مشخصاً اشاره می‌کند که Cache Poisoning با قرار دادن Content مخرب در Cache می‌تواند اثر یک مهاجم را به چندین Client گسترش دهد. (rfc-editor.org)

پیامد بسته به نوع Gadget آسیب‌پذیر می‌تواند شامل موارد زیر باشد:

تغییر محتوای سایت،

Redirect کاربران،

لود Resource از مقصد اشتباه،

Persistent XSS از طریق Cache،

اختلال در سرویس،

نمایش Version نادرست صفحه،

یا ایجاد Security Policy اشتباه.

اما Web Cache Poisoning به‌تنهایی Severity ثابت ندارد.

نوع Data کنترل‌شده و محل استفاده آن تعیین‌کننده Impact هستند.

Cache Poisoning چگونه ایجاد می‌شود؟

برای ایجاد Vulnerability معمولاً سه شرط هم‌زمان وجود دارند.

شرط اول: یک Input قابل کنترل وجود دارد

این Input ممکن است:

Header،

Cookie،

Query Parameter،

Port،

Path Variation،

GET Body،

یا Metadata مربوط به Proxy

باشد.

شرط دوم: Backend از آن Input استفاده می‌کند

مثلاً Input:

داخل HTML بازتاب داده می‌شود،

برای ساخت Absolute URL استفاده می‌شود،

Resource URL را تغییر می‌دهد،

Redirect می‌سازد،

Language را تعیین می‌کند،

یا رفتار Application را عوض می‌کند.

شرط سوم: Cache همان Input را از Cache Key حذف کرده است

اگر Cache Input مذکور را در Key وارد کند، Response دستکاری‌شده فقط برای همان Variation باقی می‌ماند.

ولی اگر Input Unkeyed باشد، Request عادی User می‌تواند همان Key را Match کند.

فرمول مفهومی:

Untrusted Input
      +
Response Influence
      +
Missing From Cache Key
      +
Cacheable Response
      =
Web Cache Poisoning Risk

چرا اختلاف دید Cache و Backend خطرناک است؟

Cache و Backend الزاماً Request را به شکل یکسان نمی‌بینند.

Cache ممکن است فقط URL را معیار قرار دهد.

اما Backend موارد زیر را نیز پردازش کند:

X-Forwarded-Host
X-Original-URL
X-Rewrite-URL
Accept-Language
custom cookies
feature headers

این اختلاف باعث ایجاد دو تعریف از «Request معادل» می‌شود.

Cache می‌گوید:

Request A == Request B

اما Backend می‌گوید:

Request A produces Response X
Request B produces Response Y

این Interpretation Conflict یکی از ریشه‌های اصلی Web Cache Poisoning است.

PortSwigger در Knowledge Base خود نیز Web Cache Poisoning را با مشکلات Interpretation Conflict مرتبط می‌کند. (portswigger.net)

Cache Key ناقص چگونه مشکل ایجاد می‌کند؟

فرض کنید سایت براساس Language Header محتوا تولید کند:

Accept-Language: fa

اما Cache فقط URL را Key کند.

User فارسی اولین Request را می‌فرستد.

Cache نسخه فارسی را ذخیره می‌کند.

سپس User انگلیسی همان URL را درخواست می‌کند و همان Response فارسی را دریافت می‌کند.

این مثال لزوماً Attack امنیتی نیست، اما Mechanism اصلی مشکل را نشان می‌دهد.

اگر به‌جای Language، Input روی:

JavaScript URL،

Redirect Destination،

Security Header،

یا HTML قابل اجرا

اثر بگذارد، Impact امنیتی می‌شود.

نقش Vary در Cache Key

HTTP Header به نام Vary برای اعلام این موضوع طراحی شده است که چه Headerهایی روی Representation Response اثر دارند.

مثلاً:

Vary: Accept-Language

به Cache می‌گوید Responseهای دارای Language متفاوت باید جداگانه نگهداری شوند.

RFC 9111 الزام می‌کند Cache هنگام Reuse کردن Response دارای Vary، مقادیر Request Headerهای نام‌برده‌شده را با Request اصلی مقایسه کند. (rfc-editor.org)

MDN نیز توضیح می‌دهد Vary بخش‌هایی از Request را مشخص می‌کند که علاوه بر Method و URL در انتخاب Response Cache شده نقش دارند. (developer.mozilla.org)

بنابراین اگر Content واقعاً بر اساس:

Accept-Language

متفاوت است، می‌توان:

Vary: Accept-Language

فرستاد.

آیا Vary همیشه مشکل را حل می‌کند؟

خیر.

Vary باید با Cache/CDN مورد استفاده شما سازگار و درست Config شده باشد.

همچنین اضافه کردن Headerهای بی‌نهایت متنوع به Cache Key می‌تواند Cache Hit Ratio را نابود کند.

مثلاً:

Vary: User-Agent

ممکن است هزاران Variation ایجاد کند.

MDN نیز هشدار می‌دهد User-Agent Cardinality بسیار بالایی دارد و می‌تواند Reuse شدن Cache را به‌شدت کاهش دهد. (developer.mozilla.org)

راهکار Security همیشه این نیست که:

«هر Input را به Cache Key اضافه کنیم.»

گاهی بهتر است Input اصلاً روی Response اثر نگذارد.

گاهی باید آن صفحه Cache نشود.

و گاهی Request باید قبل از رسیدن به Application Normalize شود.

Cache-Control چیست؟

Cache-Control یکی از اصلی‌ترین Headerهای کنترل Cache است.

Directiveهای مهم آن شامل:

public
private
no-store
no-cache
max-age
s-maxage

هستند.

این Directiveها یک معنی ندارند و اشتباه گرفتن آنها می‌تواند مشکل امنیتی ایجاد کند.

تفاوت no-store و no-cache

یکی از اشتباهات بسیار رایج این است که تصور شود:

Cache-Control: no-cache

یعنی Response هرگز ذخیره نشود.

این تصور دقیق نیست.

no-cache یعنی Response قبل از Reuse باید با Origin Revalidate شود.

اگر هدف این است که Response اصلاً ذخیره نشود، Directive مهم‌تر:

Cache-Control: no-store

است.

RFC 9111 قواعد این Directiveها را تعریف می‌کند. (rfc-editor.org)

برای Data بسیار حساس:

Cache-Control: no-store

اغلب بخش مهمی از Policy است.

private چه معنایی دارد؟

Directive:

Cache-Control: private

به Shared Cache می‌گوید Response برای ذخیره و استفاده عمومی مناسب نیست.

Private Browser Cache ممکن است بسته به سایر Directiveها همچنان بتواند آن را نگهداری کند.

این گزینه برای Responseهای User-Specific اهمیت دارد.

برای مثال:

Dashboard شخصی،

Profile،

Account Settings،

Financial Information

نباید به شکل عمومی در CDN Cache شوند.

اشتباه Cache کردن Responseهای شخصی

فرض کنید:

/account

برای User A اطلاعات Account A را Render می‌کند.

اگر Shared Cache این صفحه را بدون جداسازی Identity Cache کند، User B ممکن است همان Response را دریافت کند.

این وضعیت بیشتر به Cache Deception، Cache Data Leakage یا Cache Key Design اشتباه نزدیک است تا Web Cache Poisoning کلاسیک، اما نشان می‌دهد Cache Rule نادرست می‌تواند مستقیماً Confidentiality را نقض کند.

قاعده پایه:

User-specific response
→ Do not publicly cache

مگر Architecture بسیار دقیق و Keying مناسب داشته باشد. Web Cache Poisoning با X-Forwarded-Host

Web Cache Poisoning با X-Forwarded-Host

یکی از شناخته‌شده‌ترین Unkeyed Inputها:

X-Forwarded-Host

است.

Reverse Proxyها ممکن است Host اصلی را از این Header به Backend منتقل کنند.

Application ممکن است از آن برای ساخت Absolute URL استفاده کند.

مثلاً:

https://<forwarded-host>/assets/app.js

اگر:

X-Forwarded-Host توسط Client کنترل شود،

Backend مقدار آن را Trust کند،

و Cache آن Header را در Key لحاظ نکند،

Response دستکاری‌شده ممکن است تحت Cache Key URL اصلی ذخیره شود.

OWASP Web Security Testing Guide نیز توضیح می‌دهد Manipulation هدر Host یا X-Forwarded-Host در بعضی Applicationها می‌تواند به Web Cache Poisoning منجر شود. (wstg.owasp.org)

چرا Forwarded Headerها حساس هستند؟

Headerهایی مانند:

X-Forwarded-Host
X-Forwarded-Proto
Forwarded

باید فقط زمانی Trusted باشند که توسط Reverse Proxy قابل‌اعتماد ساخته شوند.

Architecture امن:

Internet
   ↓
Trusted Reverse Proxy
   ↓
Remove client-supplied forwarded headers
   ↓
Set trusted values
   ↓
Application

نه:

Internet Client
   ↓
arbitrary X-Forwarded-Host
   ↓
Application trusts it

حتی بدون Cache Poisoning نیز Trust اشتباه این Headerها می‌تواند باعث Host Header Injection یا URL Generation نادرست شود.

Hidden Headerها و Web Cache Poisoning

Applicationهای پیچیده ممکن است Headerهایی را پشتیبانی کنند که Browser عادی ارسال نمی‌کند.

مثلاً:

X-Original-URL
X-Rewrite-URL
X-Forwarded-Scheme
X-Host

وجود یک Header به‌خودی‌خود آسیب‌پذیری نیست.

مشکل زمانی ایجاد می‌شود که:

Backend براساس آن Response را تغییر دهد،

Client بتواند مقدار را کنترل کند،

و Cache آن را نادیده بگیرد.

PortSwigger اشاره می‌کند تکنولوژی‌های مختلف گاهی Headerهای غیرمعمول را به‌صورت پیش‌فرض پردازش می‌کنند و همین Inputهای Unkeyed می‌توانند Attack Surface ایجاد کنند. (portswigger.net)

Cookie نیز می‌تواند مشکل‌ساز باشد.

بعضی Cacheها عمداً Cookie را در Cache Key وارد نمی‌کنند تا Cache Fragmentation کاهش پیدا کند.

اما اگر Backend مقدار Cookie خاصی را داخل Response عمومی Render کند، اختلاف ایجاد می‌شود.

مثلاً Cookie:

theme=dark

اگر فقط ظاهر را عوض کند، شاید Impact امنیتی کم باشد.

اما Cookieای که مقدار آن وارد JavaScript یا HTML حساس شود می‌تواند خطر بیشتری ایجاد کند.

قاعده:

هر Input که Response را materially تغییر می‌دهد باید یا:

در Cache Key لحاظ شود،

Normalize شود،

یا Response مربوط از Shared Cache خارج شود.

Query String و Cache Poisoning

معمولاً Query String بخشی از Cache Key است.

اما این همیشه درست نیست.

بعضی CDNها برای Performance ممکن است:

تمام Query String را Ignore کنند،

فقط بعضی Parameterها را وارد Key کنند،

یا Analytics Parameterهایی مانند UTM را حذف کنند.

PortSwigger نشان داده است Cache Implementationهایی که Query String یا Parameter خاصی را از Key حذف می‌کنند در صورت اثرگذاری همان Input روی Response می‌توانند Web Cache Poisoning ایجاد کنند. (portswigger.net)

مثلاً:

/page?campaign=A

و:

/page?campaign=B

ممکن است از دید Cache یکسان باشند.

اگر Backend آنها را متفاوت Render کند، Cache Integrity مشکل دارد.

Analytics Parameterها

Parameterهایی مانند:

utm_source
utm_campaign
utm_content

اغلب برای Analytics هستند و ممکن است از Cache Key حذف شوند.

این کار ذاتاً بد نیست.

ولی Backend نباید مقدار آنها را در بخش قابل‌اجرای HTML یا Security-Sensitive Response استفاده کند، مگر Cache Policy با آن هماهنگ باشد.

این مثال نشان می‌دهد یک Parameter کم‌اهمیت Marketing نیز می‌تواند به Security Boundary تبدیل شود.

GET Request Body

HTTP GET Request معمولاً Body عملیاتی ندارد، اما بعضی Stackها GET Body را قبول می‌کنند.

Cloudflare در راهنمای فعلی جلوگیری از Web Cache Poisoning هشدار می‌دهد GET Body می‌تواند توسط Origin پردازش شود در حالی که لزوماً در Cache Key قرار ندارد؛ بنابراین GET Body نباید محتوای Cacheable Response را به‌شکلی Security-Relevant تغییر دهد. (developers.cloudflare.com)

اگر Data عملیاتی دارید، Method مناسب‌تر مانند POST را در نظر بگیرید.

Web Cache Poisoning از طریق Resource URL

یکی از سناریوهای خطرناک زمانی است که Input کنترل‌شده در آدرس Resource وارد شود.

مثلاً Backend HTMLی تولید می‌کند که شامل:

<script src="..."></script>

یا:

<link rel="stylesheet" href="...">

است.

اگر Host یا مسیر این Resource از Unkeyed Input بیاید و Response Cache شود، Userهای بعدی ممکن است Resource اشتباه دریافت کنند.

این سناریو خطرناک‌تر از تغییر یک متن ساده است، چون Resource فعال Browser می‌تواند Context امنیتی گسترده‌تری داشته باشد.

راهکار اصلی:

URL Resourceهای اصلی را از Configuration مورد اعتماد بسازید.

Client Header نباید Host فایل JavaScript تولیدشده در HTML را تعیین کند.

Web Cache Poisoning و XSS

گاهی Input قابل‌کنترل وارد HTML یا JavaScript Response می‌شود.

اگر Sanitization ناقص باشد، Root Vulnerability ممکن است XSS باشد.

بدون Cache، Attacker معمولاً باید قربانی را وادار کند URL خاصی را باز کند.

اما اگر همان Response Cache شود، Cache می‌تواند Payload را بدون نیاز به URL خاص برای کاربران عادی توزیع کند.

PortSwigger Web Cache Poisoning را به‌عنوان Delivery Mechanism برای ضعف‌هایی مانند XSS توضیح می‌دهد. (portswigger.net)

بنابراین Fix نباید فقط Cache را تغییر دهد.

Root XSS نیز باید اصلاح شود.

Cache Poisoning و Redirect

اگر Application یک Redirect را براساس Input غیرقابل‌اعتماد تولید و Cache کند، Userهای دیگر ممکن است با URL عادی Redirect اشتباه دریافت کنند.

برای مثال Root Cause می‌تواند Host Header Injection یا Open Redirect باشد.

Cache فقط Impact را Amplify می‌کند.

به همین دلیل هنگام Incident باید هر دو بخش اصلاح شوند:

unsafe redirect generation
+
unsafe caching

Cache Poisoning و Denial of Service

همه Cache Poisoningها برای اجرای Script نیستند.

ممکن است Response مسموم‌شده:

خطای 404،

Redirect به Port نامعتبر،

صفحه Maintenance،

Response خالی،

یا Content خراب

باشد.

اگر این Response برای Route پرترافیک Cache شود، Availability سایت تحت تأثیر قرار می‌گیرد.

حتی Poisoning کوتاه‌مدت Homepage می‌تواند Business Impact زیادی ایجاد کند.

Negative Caching

Cacheها می‌توانند بعضی Error Responseها را نیز ذخیره کنند.

RFC 9111 اشاره می‌کند علاوه بر Responseهای موفق، بعضی نتایج منفی مانند 404 نیز می‌توانند قابل Cache باشند. (rfc-editor.org)

بنابراین Cache Policy باید برای:

404،

301،

302،

500،

503

نیز بررسی شود.

یک Misconfiguration ممکن است Failure موقت Origin را به Failure طولانی‌تر برای کاربران تبدیل کند.

Cache Poisoning و CDN

CDN یکی از رایج‌ترین Shared Cacheهای اینترنت است.

CDN Response را در Edge Location نزدیک User نگه می‌دارد.

این کار Latency را کاهش می‌دهد.

اما اگر Entry مخرب وارد CDN شود، Scope آن می‌تواند از یک Reverse Proxy محلی بزرگ‌تر باشد.

با این حال CDNهای مختلف:

Cache Key،

Default Cache Rules،

Query String Handling،

Cookie Handling،

Header Handling،

Purge Behavior

متفاوتی دارند.

هیچ Security Rule عمومی نباید بدون بررسی Documentation همان Provider اعمال شود.

Custom Cache Key

CDNها معمولاً اجازه Custom Cache Key می‌دهند.

مثلاً می‌توانید تعیین کنید:

Header،

Cookie،

Query Parameter،

Device Type،

Language

بخشی از Key باشند.

این قابلیت قدرتمند است اما خطرناک نیز هست.

اگر داده‌ای که Response را تغییر می‌دهد از Key حذف شود، خطر Mixing Response ایجاد می‌شود.

اگر داده‌ای با Cardinality بالا وارد Key شود، Cache Fragmentation شدید رخ می‌دهد.

هدف:

Different meaningful responses
→ Different cache keys

و:

Equivalent responses
→ Same cache key

است.

رفتار Cloudflare و Vary

Cloudflare در Documentation به‌روز خود توضیح می‌دهد که Vary در Cache Rules می‌تواند Headerهای مشخص‌شده توسط Origin را بخشی از Cache Key کند و برای Headerها حالت‌هایی مانند Normalize، Passthrough یا Bypass ارائه می‌کند. (developers.cloudflare.com)

این مثال مهمی است که نشان می‌دهد Behavior CDN ممکن است علاوه بر استاندارد HTTP، Configuration Provider-Specific نیز نیاز داشته باشد.

بنابراین صرف ارسال:

Vary: Some-Header

بدون بررسی رفتار واقعی CDN کافی نیست.

فقط Static Content را Cache کنیم؟

این توصیه برای شروع بسیار امن است، اما مطلق نیست.

Dynamic Content نیز می‌تواند Cache شود، اگر Variationهای آن دقیقاً تعریف شده باشند.

با این حال هرچه Response بیشتر به:

User،

Cookie،

Header،

Permission،

Location،

Language،

Feature Flag

وابسته باشد، طراحی Cache پیچیده‌تر می‌شود.

Cloudflare در راهنمای جلوگیری از Web Cache Poisoning توصیه می‌کند به‌طور خاص بررسی شود فقط Resourceهایی Cache شوند که واقعاً Static هستند و به Input کاربر وابستگی خطرناک ندارند. (developers.cloudflare.com)

برای صفحه حساس، Cache نکردن اغلب بهتر از Cache Key بسیار پیچیده است.

Web Cache Poisoning و WordPress

WordPress ذاتاً به معنی وجود Web Cache Poisoning نیست.

اما اکوسیستم WordPress معمولاً چندین Cache Layer دارد:

Browser Cache،

Plugin Cache،

Nginx FastCGI Cache،

Varnish،

Hosting Cache،

CDN.

همزمان Plugin و Theme می‌توانند Content داینامیک تولید کنند.

به همین دلیل Data Flow باید End-to-End بررسی شود.

مدل رایج:

Browser
  ↓
CDN
  ↓
Reverse Proxy Cache
  ↓
WordPress Cache Plugin
  ↓
PHP / WordPress
  ↓
Database

هر لایه ممکن است تعریف متفاوتی از Cache Key داشته باشد.

WordPress و صفحات کاربران لاگین‌شده

Dashboard، Admin، Account و سایر صفحات شخصی نباید مانند صفحه عمومی Blog Cache شوند.

WordPress تابع:

nocache_headers()

را برای ارسال Headerهایی ارائه می‌دهد که از Cache شدن Response جلوگیری کنند.

مستندات رسمی WordPress می‌گویند این Function Headerهای لازم برای جلوگیری از Cache شدن در Browserها و Intermediate Proxyها را تنظیم می‌کند. (developer.wordpress.org)

تابع wp_get_nocache_headers() در نسخه‌های فعلی WordPress برای Cache-Control مقادیری شامل:

no-cache
must-revalidate
max-age=0
no-store
private

تولید می‌کند. (developer.wordpress.org)

این رفتار برای Endpointهای Sensitive مهم است.

Cache Pluginهای WordPress

Plugin Cache ممکن است HTML کامل صفحات را ذخیره کند.

باید مشخص شود چه Routeهایی از Cache خارج می‌شوند.

معمولاً مواردی مانند:

/wp-admin/
/wp-login.php
cart
checkout
my-account
personal dashboards
custom authenticated pages

نباید Shared Full-Page Cache عادی داشته باشند.

اما Custom Plugin ممکن است Route جدیدی بسازد که Cache Plugin از حساس بودن آن خبر ندارد.

پس هر Feature جدید باید Cache Policy خودش را داشته باشد.

WooCommerce و Cache

در WooCommerce Contentهایی مانند:

Cart،

Checkout،

Account،

Session-dependent Pricing،

Personalized Offers

به Cookie و User State وابسته‌اند.

Cache کردن آنها بدون Variation صحیح می‌تواند باعث Data Leakage یا Logic Error شود.

مشکل ممکن است دقیقاً Web Cache Poisoning نباشد، اما همان اصل بنیادی برقرار است:

اگر Response بر اساس State کاربر فرق دارد، Shared Cache باید این تفاوت را بشناسد یا Response را Cache نکند.

REST API در WordPress

Custom REST Endpoint ممکن است GET باشد و CDN آن را Cache کند.

اگر Endpoint براساس:

Authentication،

Cookie،

Role،

Query Parameter،

Header

Response متفاوتی تولید کند، Developer باید Cache Behavior را صریح تعریف کند.

مثلاً Endpoint عمومی Catalog با Response ثابت ممکن است Cacheable باشد.

اما Endpoint:

/my-profile

نباید بدون Isolation مناسب وارد Shared Cache شود.

Host Header در WordPress

Custom Plugin ممکن است از:

$_SERVER['HTTP_HOST']

یا Forwarded Header برای ساخت URL داخل HTML استفاده کند.

اگر Frontend Page Cache نیز فعال باشد، Host Header Injection می‌تواند به Cache Poisoning متصل شود.

راه مناسب:

استفاده از URLهای Configured وردپرس،

Validation Host،

و عدم اعتماد مستقیم به Headerهای Client.

چگونه Web Cache Poisoning را به‌صورت امن شناسایی کنیم؟

تست Cache Poisoning باید با احتیاط زیادی انجام شود.

تغییر Cache Shared در Production ممکن است کاربران واقعی را تحت تأثیر قرار دهد.

PortSwigger نیز به‌صراحت هشدار می‌دهد هنگام تست Unkeyed Input روی سایت Live ممکن است Response تولیدشده ناخواسته برای کاربران دیگر Cache شود؛ بنابراین باید از Cache Key منحصر‌به‌فرد یا محیط کنترل‌شده استفاده شود. (portswigger.net)

برای رخنه‌کاو و تست دفاعی، روش ترجیحی:

Staging
یا
isolated cache namespace

است.

مرحله اول: Cache Layerها را Inventory کنید

قبل از ارسال Test Request باید بدانید Cache کجاست.

سؤال‌ها:

CDN داریم؟

Varnish داریم؟

Nginx FastCGI Cache فعال است؟

Plugin WordPress Cache دارد؟

Application Cache داخلی دارد؟

Browser Cache نیز مهم است؟

اگر Layer را نشناسید، Response Cache شده را ممکن است اشتباه تفسیر کنید.

مرحله دوم: Cache Policy را مستند کنید

برای هر Route مشخص کنید:

Cacheable است؟

TTL چقدر است؟

Cache Key چیست؟

Query String وارد Key می‌شود؟

کدام Cookieها وارد Key هستند؟

کدام Headerها وارد Key هستند؟

Responseهای Authentication شده Cache می‌شوند؟

Cache Purge چگونه انجام می‌شود؟

این Inventory در بسیاری از سازمان‌ها اصلاً وجود ندارد.

مرحله سوم: Hit و Miss را تشخیص دهید

برخی Cacheها Headerهایی برای Debug دارند، مثلاً مفاهیمی مانند:

HIT
MISS
BYPASS
STALE

اما نام دقیق Provider-Specific است.

می‌توان همچنین Timing، Age Header یا Request Logها را بررسی کرد.

هدف این است که بفهمیم Response از Cache آمده یا Origin.

مرحله چهارم: Variationهای Response را پیدا کنید

در محیط آزمایشی Inputهای بی‌خطر را تغییر دهید.

مثلاً یک Marker ساده:

cache-test-12345

نه Payload اجرایی.

بررسی کنید آیا Input:

در Response ظاهر می‌شود؟

Redirect را تغییر می‌دهد؟

Resource URL را تغییر می‌دهد؟

Language یا Template را تغییر می‌دهد؟

اگر Input روی Response اثر ندارد، احتمال Exploit آن مسیر کمتر است.

مرحله پنجم: بررسی کنید Input در Key هست یا خیر

این بخش باید در محیط ایزوله انجام شود.

دو Request فقط در Input موردنظر تفاوت دارند.

اگر Cache آنها را یک Entry در نظر می‌گیرد اما Origin Response متفاوت است، Cache-Key Mismatch وجود دارد.

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

یک Marker harmless برای اثبات کافی است.

Cache Buster چیست؟

Cache Buster مقداری است که باعث ایجاد Cache Key متفاوت می‌شود تا تست شما کاربران دیگر را تحت تأثیر قرار ندهد.

مثلاً اگر Query String قطعاً بخشی از Cache Key باشد:

?cache_test=unique-123456

اما نکته مهم:

نباید از قبل فرض کنیم Query String Keyed است.

یکی از کلاس‌های Vulnerability دقیقاً Unkeyed Query String است.

پس ایمن‌ترین روش همچنان Staging یا Cache Namespace مجزا است.

تست Production چرا خطرناک است؟

ممکن است Tester فقط بخواهد بفهمد Header در Response Reflect می‌شود.

اما همان Response:

در CDN ذخیره شود،

به Homepage مربوط باشد،

TTL ده دقیقه داشته باشد،

و هزاران User آن را دریافت کنند.

در چنین حالتی یک تست ساده به Incident واقعی تبدیل شده است.

بنابراین برای Cache Poisoning:

Safe Testing Environment

از خود Payload مهم‌تر است. تفاوت Web Cache Poisoning با Web Cache Deception

تفاوت Web Cache Poisoning با Web Cache Deception

این دو مفهوم اغلب اشتباه گرفته می‌شوند.

Web Cache Poisoning:

مهاجم Response مخرب یا دستکاری‌شده‌ای را وارد Cache می‌کند و Userهای دیگر آن را دریافت می‌کنند.

Web Cache Deception:

مهاجم Cache را فریب می‌دهد تا Content خصوصی قربانی را ذخیره کند، سپس مهاجم همان Content Cache شده را بازیابی می‌کند.

PortSwigger این تفاوت را به‌صورت روشن بیان می‌کند: در Poisoning محتوای مخرب به قربانیان تحویل داده می‌شود؛ در Deception محتوای حساس قربانی Cache می‌شود تا مهاجم آن را بخواند. (portswigger.net)

جدول مقایسه:

ویژگیWeb Cache PoisoningWeb Cache Deception
هدف اصلیدستکاری Response برای سایر کاربرانسرقت Response خصوصی
مهاجم چه چیزی وارد Cache می‌کند؟Response تغییرکرده یا مخربResponse حساس قربانی
چه کسی Response را می‌گیرد؟قربانیانمهاجم
مشکل اصلیCache Key / Unkeyed Input / Response Influenceاختلاف Cache و Origin در تشخیص Resource
اثر رایجIntegrity، XSS، Redirect، DoSConfidentiality

تفاوت Web Cache Poisoning با HTTP Request Smuggling

HTTP Request Smuggling زمانی ایجاد می‌شود که چند HTTP Component درباره مرز Request توافق نداشته باشند.

مثلاً Frontend و Backend Message Length را متفاوت Parse می‌کنند.

Cache Poisoning بیشتر درباره این است که چه Responseای زیر چه Cache Key ذخیره می‌شود.

با این حال Request Smuggling می‌تواند در بعضی Attack Chainها برای Poison کردن Cache استفاده شود.

RFC 9111 نیز یکی از Attack Vectorهای Cache Poisoning را اختلاف Parsing میان Proxy و User Agent یا سایر HTTP Componentها معرفی می‌کند. (rfc-editor.org)

پس:

Request Smuggling
→ Request Boundary / Parsing

و:

Web Cache Poisoning
→ Cache Integrity / Cache Key

است.

تفاوت Web Cache Poisoning با DNS Cache Poisoning

نام مشابه است، اما لایه کاملاً متفاوت است.

DNS Cache Poisoning:

DNS Resolver را وادار به ذخیره Mapping اشتباه Domain → IP می‌کند.

Web Cache Poisoning:

HTTP Cache را وادار به ذخیره Response نامناسب برای Request می‌کند.

بنابراین:

DNS Cache
≠
HTTP Web Cache

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

Response Splitting و Cache Poisoning

مدل‌های تاریخی Cache Poisoning گاهی با HTTP Response Splitting مرتبط بوده‌اند.

OWASP Cache Poisoning Page قدیمی‌تر به سناریوهایی اشاره می‌کند که Application اجازه Inject کردن Headerهای اضافی با CR/LF را می‌داد و Cache Response دستکاری‌شده را ذخیره می‌کرد. (community.owasp.org)

این سناریو هنوز از نظر مفهومی مهم است، اما Web Cache Poisoning مدرن فقط به Response Splitting محدود نیست.

امروزه Unkeyed Inputها، Cache Key Transformation و Proxy/Application اختلاف‌های مهمی هستند.

Cache Key Normalization

Cacheها ممکن است قبل از ساخت Key Request را Normalize کنند.

مثلاً:

ترتیب Query Parameterها را تغییر دهند،

Encoding را Normalize کنند،

Port را حذف کنند،

Parameterهای خاص را Ignore کنند،

Case را تغییر دهند.

اگر Origin همان Normalization را انجام ندهد، دو Request که از دید Cache برابرند ممکن است از دید Backend متفاوت باشند.

این اختلاف می‌تواند Vulnerability ایجاد کند.

بنابراین Cache Key باید با Canonicalization Application هماهنگ باشد.

Query Parameter Cloaking

گاهی Cache و Backend Query String را با Parserهای متفاوت می‌خوانند.

مثلاً Cache Parameter خاصی را Analytics تشخیص داده و حذف می‌کند، در حالی که Backend آن را بخشی از Business Response می‌داند.

اصل دفاعی:

هیچ Parameter حذف‌شده از Cache Key نباید Response امنیتی یا محتوایی مهم را تغییر دهد.

اگر تغییر می‌دهد، Rule Cache اشتباه است.

Headerهایی که نباید وجود داشته باشند

یکی از ساده‌ترین Hardeningها:

اگر Application به Header خاصی نیاز ندارد، آن را پردازش نکنید.

برای مثال اگر:

X-Original-URL

در Architecture شما استفاده نمی‌شود، دلیلی ندارد Framework یا Proxy آن را Trust کند.

PortSwigger نیز توصیه می‌کند Unkeyed Headerهای غیرضروری که تکنولوژی به‌صورت پیش‌فرض پشتیبانی می‌کند Disable شوند. (portswigger.net)

Attack Surface کمتر یعنی Cache Policy ساده‌تر. مهم‌ترین روش جلوگیری از Web Cache Poisoning

مهم‌ترین روش جلوگیری از Web Cache Poisoning

اصل اول:

Response-affecting input
must not be invisible to the cache.

اگر Input روی Response اثر می‌گذارد، یکی از سه تصمیم باید گرفته شود:

Input را حذف یا Ignore کنید.

آن را به Cache Key اضافه کنید.

یا Response را از Shared Cache خارج کنید.

این سه‌گانه پایه بسیاری از Fixهاست.

فقط Cache Key را بزرگ نکنید

اضافه کردن تمام Headerها، Cookieها و Parameterها به Cache Key راهکار خوبی نیست.

چرا؟

Cache Fragmentation ایجاد می‌شود.

Memory مصرف می‌شود.

Hit Rate پایین می‌آید.

Attack Surface DoS ممکن است افزایش پیدا کند.

عملکرد CDN ضعیف می‌شود.

راهکار بهتر:

Inputهای Response-Relevant را مشخص کنید.

بقیه را حذف یا Normalize کنید.

این همان اصل Data Minimization، این بار در Cache Design است.

Cache-Control درست تعریف کنید

Responseهای حساس:

Cache-Control: no-store

یا ترکیب مناسب دیگری براساس Use Case نیاز دارند.

Responseهای User-Specific برای Shared Cache معمولاً باید private باشند.

Content عمومی Static می‌تواند public و دارای TTL باشد.

Policy باید بر اساس نوع Resource باشد، نه یک Header واحد برای کل سایت.

استفاده درست از Vary

اگر Representation واقعاً بر اساس Header محدودی تغییر می‌کند:

Vary: Accept-Language

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

اما نباید Header غیرقابل‌کنترل یا User-Specific را کورکورانه وارد Vary کنید.

در بعضی موارد Cache Bypass برای چنین Responseای منطقی‌تر است.

RFC 9111 و MDN رفتار استاندارد Vary را تعریف کرده‌اند. (rfc-editor.org)

Host را Validate کنید

Host و Forwarded Host یکی از منابع شناخته‌شده Poisoning هستند.

Domainهای مجاز باید مشخص باشند.

Unknown Host بهتر است در Edge یا Reverse Proxy Reject شود.

Forwarded Header فقط از Proxy مورد اعتماد پذیرفته شود.

Absolute URLهای حساس از Base URL ثابت ساخته شوند.

این کنترل علاوه بر Cache Poisoning، Host Header Injection را نیز کاهش می‌دهد.

Static Assetها را از Dynamic HTML جدا کنید

Architecture ساده‌تر:

/static/*
→ aggressively cached

و:

/account/*
→ not shared cached

است.

هرچه Ruleها Explicitتر باشند، احتمال Cache کردن accidental Response کاهش پیدا می‌کند.

Authenticated Content را جدا کنید

یکی از Policyهای کاربردی:

Request دارای Authentication Session → Shared Cache Bypass

است.

پیاده‌سازی دقیق بسته به سیستم دارد.

مثلاً ممکن است Presence یک Session Cookie شناخته‌شده باعث Bypass شود.

اما این Rule باید دقیق باشد.

نباید هر Cookie تصادفی باعث نابودی Cache Hit Rate شود.

برای هر Cookie مشخص کنید:

آیا Response را تغییر می‌دهد؟

آیا User-Specific است؟

آیا Cache Key آن را می‌بیند؟

آیا Presence آن باید Cache را Bypass کند؟

این Inventory مخصوصاً برای WordPress مهم است، چون Core، WooCommerce و Pluginهای مختلف Cookieهای متفاوتی ایجاد می‌کنند.

Cache Rule باید بخشی از Code Review باشد

گاهی تیم Developer Code را تغییر می‌دهد ولی Cache Configuration ثابت می‌ماند.

Feature جدید Headerی معرفی می‌کند.

Backend براساس آن Response متفاوت می‌دهد.

CDN خبر ندارد.

Vulnerability ایجاد می‌شود.

بنابراین تغییر Response Semantics باید Review Cache Policy را Trigger کند.

Cache Configuration as Code

اگر امکان دارد Cache Rules را Version-Controlled کنید.

مزایا:

Review،

History،

Rollback،

Testing،

Peer Approval.

تغییر Cache Key در Dashboard بدون Audit می‌تواند Security Behavior کل سایت را تغییر دهد.

Purge امن و سریع

اگر Cache Poisoning رخ داد، Fix Code کافی نیست.

Poisoned Entry ممکن است تا پایان TTL باقی بماند.

Incident Response باید امکان Purge داشته باشد:

By URL،

By Host،

By Prefix،

By Tag،

یا در شرایط خاص Purge Everything.

نوع Purge به Provider بستگی دارد.

مثلاً Cloudflare توضیح می‌دهد Custom Cache Keyها ممکن است باعث شوند Purge دقیق URL نیاز به همان Header/Cookieهای Key داشته باشد و گزینه‌هایی مانند Purge by Host یا Prefix در بعضی موارد مناسب‌تر باشند. (developers.cloudflare.com)

TTL کوتاه جای Fix را نمی‌گیرد

گاهی گفته می‌شود:

«Cache فقط ۳۰ ثانیه است، پس خطر کم است.»

TTL کوتاه Impact را محدود می‌کند، اما Vulnerability را حذف نمی‌کند.

در سایت پرترافیک ۳۰ ثانیه ممکن است هزاران Request باشد.

همچنین مهاجم ممکن است Poisoning را تکرار کند.

Root Cause باید اصلاح شود.

Stale Content

بعضی Cacheها هنگام Origin Failure می‌توانند Stale Response را Serve کنند.

این قابلیت برای Availability مفید است.

اما اگر Poisoned Entry Stale باقی بماند، Incident ممکن است بیشتر ادامه پیدا کند.

Policyهایی مانند stale-if-error باید هنگام Incident Response در نظر گرفته شوند.

Monitoring برای Cache Poisoning

Monitoring فقط Status 500 نیست.

Signalهای مفید:

افزایش Unexpected Cache HIT،

تغییر ناگهانی Response Hash،

Resource URLهای ناشناخته،

Host یا X-Forwarded-Host غیرمنتظره،

Cache Purge غیرعادی،

Cache Rule Change،

Redirect Destination غیرعادی،

CSP Violation Report،

افزایش Client-Side Error.

می‌توانید برای صفحات Critical یک Synthetic Monitor داشته باشید که Response را از چند Edge Location مقایسه کند.

Content Hash Monitoring

برای Resourceهای مهم مانند:

Homepage،

Login Page،

JavaScript Bootstrap،

Pricing Page

می‌توان Hash یا Semantic Signature Response را Monitor کرد.

اگر Cached Version میان Edgeها اختلاف غیرمنتظره داشته باشد، Alert ایجاد شود.

البته Dynamic Content باید Normalize شود تا False Positive زیاد نشود.

Log چه چیزهایی باید داشته باشد؟

برای Debug Cache Incident، Log مفید می‌تواند شامل:

Cache Status،

Cache Key یا Representation قابل Audit آن،

Host،

Relevant Forwarded Headerها،

Request Path،

Query Parameters مهم،

Origin Status،

Edge Location،

TTL،

Age

باشد.

اما:

Token،

Password،

Session Secret

نباید وارد Log شود.

Observability خودش نباید Information Disclosure بسازد.

چک‌لیست دفاعی جلوگیری از Web Cache Poisoning

کنترلسؤال امنیتی
Cache Inventoryآیا تمام CDN، Proxy، Plugin و Application Cacheها مشخص‌اند؟
Cache Keyآیا دقیقاً می‌دانیم چه Inputهایی داخل Key هستند؟
Unkeyed Inputsآیا Backend از Header/Parameter خارج از Key استفاده می‌کند؟
Response Variationآیا هر Input مؤثر بر Response در Policy Cache دیده شده؟
Static Contentآیا فقط Resourceهای مناسب Shared Cache می‌شوند؟
Sensitive Pagesآیا Account، Login، Admin و صفحات شخصی از Shared Cache خارج‌اند؟
Cache-Controlآیا no-store، private و سایر Directiveها صحیح‌اند؟
Varyآیا Headerهای Content Negotiation درست تعریف شده‌اند؟
Hostآیا Host و Forwarded Host اعتبارسنجی می‌شوند؟
Trusted Proxyآیا Client نمی‌تواند Forwarded Header مورد اعتماد را تعیین کند؟
GET Bodyآیا GET Body Response Cacheable را تغییر نمی‌دهد؟
Query Stringآیا Query Parameterهای حذف‌شده از Key واقعاً بی‌اثرند؟
Cookiesآیا Cookieهای مؤثر بر Response در Cache Policy لحاظ شده‌اند؟
Authenticationآیا Response احراز هویت‌شده Shared Cache نمی‌شود؟
WordPressآیا صفحات Dynamic و User-Specific از Full Page Cache خارج‌اند؟
WooCommerceآیا Cart، Checkout و Account درست Bypass می‌شوند؟
REST APIآیا Endpointهای User-Specific Cache عمومی ندارند؟
Purgeآیا تیم می‌تواند Entry مسموم را سریع پاک کند؟
Monitoringآیا Cache HIT/MISS و تغییرات Response قابل مشاهده‌اند؟
Safe Testingآیا تست Poisoning فقط در Staging یا Namespace ایزوله انجام می‌شود؟
Regressionآیا Cache Policy در CI/CD و Releaseها دوباره تست می‌شود؟

اشتباهات رایج در جلوگیری از Web Cache Poisoning

اعتماد به Cache چون CDN معتبر است

استفاده از یک CDN بزرگ Vulnerability داخل Application را خودکار حل نمی‌کند.

CDN نمی‌داند کدام Custom Header واقعاً Business Meaning دارد مگر شما Policy صحیح بدهید.

Cache کردن تمام GETها

GET به معنی Static نیست.

یک GET می‌تواند:

User-specific،

Language-specific،

Role-specific،

Search Result،

یا API Response داینامیک

باشد.

Cacheability باید از Semantics Response تعیین شود.

تصور اینکه Query String همیشه در Cache Key است

Provider یا Custom Rule ممکن است Query String را Ignore کند.

Configuration واقعی را بررسی کنید.

همه CDNها و Cacheها رفتار یکسانی ندارند.

برخی Cookieها ممکن است Ignore شوند.

اعتماد به X-Forwarded-Host

Forwarded Header بدون Trusted Proxy Configuration یک Client Input است.

استفاده از Vary: *

Vary: * در HTTP معنای خاصی دارد و عملاً Response را برای Reuse عمومی نامناسب می‌کند؛ برای جلوگیری از Cache معمولاً Cache-Control: no-store واضح‌تر است. MDN نیز no-store را برای اعلام اینکه Object نباید ذخیره شود قابل‌فهم‌تر معرفی می‌کند. (developer.mozilla.org)

استفاده بیش از حد از Vary

اضافه کردن Headerهای با Cardinality بسیار بالا می‌تواند Cache Efficiency را نابود کند.

Fix کردن Cache بدون Fix کردن Sink

اگر Input دارای XSS یا Open Redirect است، صرفاً Cache Bypass کردن آن Root Vulnerability را از بین نمی‌برد.

هر دو مشکل را Fix کنید.

Fix کردن Sink بدون Fix کردن Cache

برعکس نیز صادق است.

اگر Cache Key Design اشتباه است، Feature بعدی ممکن است دوباره Vulnerability مشابهی بسازد.

تست مستقیم روی Homepage Production

یکی از خطرناک‌ترین اشتباهات تیم تست است.

حتی Marker بی‌خطر ممکن است Cache عمومی را تغییر دهد.

از Staging استفاده کنید.

Web Cache Poisoning در WordPress؛ چک‌لیست عملی

برای سایت WordPress موارد زیر را بررسی کنید:

آیا CDN فعال است؟

آیا Plugin Page Cache نصب شده؟

آیا Hosting Provider Cache جداگانه دارد؟

آیا Nginx FastCGI Cache وجود دارد؟

کدام Cookie باعث Cache Bypass می‌شود؟

کاربر لاگین‌شده Cache می‌شود یا خیر؟

wp-admin و wp-login.php از Cache خارج‌اند؟

WooCommerce Cart/Checkout/My Account خارج‌اند؟

Custom Member Area چطور؟

REST Endpointهای شخصی Cache می‌شوند؟

Plugin سفارشی از HTTP_HOST یا X-Forwarded-Host استفاده می‌کند؟

صفحات حساس nocache_headers() یا Cache-Control مناسب دارند؟

Cache بعد از Logout یا Permission Change Purge می‌شود؟

این بررسی باید بعد از نصب هر Plugin Cache یا CDN جدید دوباره انجام شود.

نمونه معماری Cache امن‌تر برای WordPress

مدل ساده:

Internet
   ↓
CDN
   │
   ├── /wp-content/uploads/* → Cache
   ├── /wp-content/themes/*  → Cache
   ├── Public articles       → Cache
   │
   ├── /wp-admin/*           → Bypass
   ├── /wp-login.php         → Bypass
   ├── /cart                 → Bypass
   ├── /checkout             → Bypass
   └── /my-account           → Bypass
        ↓
Origin / WordPress

Custom Routeها باید براساس Semantics خود به این Policy اضافه شوند.

نباید صرفاً به Default Plugin Rule اکتفا کرد.

مهم‌ترین اصل درباره Cache و امنیت

Cache باید فقط Responseهایی را با یکدیگر معادل بداند که واقعاً برای تمام Userهایی که آن Key را دریافت می‌کنند معادل باشند.

این جمله اساس Cache Security است.

اگر:

Cache says A == B

ولی:

Application says A != B

یک Design Problem دارید.

ممکن است فقط Bug نمایشی باشد.

ممکن است Data Leakage باشد.

و ممکن است Web Cache Poisoning جدی ایجاد کند.

Incident Response در Web Cache Poisoning

اگر احتمال Poisoning وجود دارد، ابتدا Cache Layer آسیب‌دیده را مشخص کنید.

سپس:

Entryهای مشکوک Purge شوند.

Temporary Cache Bypass روی Route حساس فعال شود.

Origin Response مستقل از Cache بررسی شود.

Relevant Request Logها تحلیل شوند.

Cache Rule Changeها Audit شوند.

Input یا Sink آسیب‌پذیر Fix شود.

Cache Key اصلاح شود.

بعد از Deploy، Regression Test انجام شود.

در صورت XSS یا Credential Leakage، Incident Scope باید متناسب با آن ضعف گسترش یابد.

آیا Purge کردن Cache کافی است؟

خیر.

Purge فقط اثر جاری را حذف می‌کند.

اگر Root Cause باقی بماند، Attacker می‌تواند دوباره Cache را Poison کند.

ترتیب درست:

Contain
↓
Purge
↓
Fix root cause
↓
Fix cache policy
↓
Deploy
↓
Verify
↓
Monitor

ممکن است برای Containment ابتدا Cache مربوط به Route را کامل Disable کنید.

آیا Web Cache Poisoning همیشه High Severity است؟

خیر.

Severity به موارد زیر بستگی دارد:

کدام Route Poison می‌شود؟

چند User Cache را Share می‌کنند؟

TTL چقدر است؟

چه چیزی در Response قابل کنترل است؟

آیا Script Execution ممکن است؟

آیا فقط Text تغییر می‌کند؟

آیا Redirect ایجاد می‌شود؟

آیا Authentication لازم است؟

آیا Poisoning قابل تکرار است؟

آیا Homepage یا Login Page تحت تأثیر است؟

PortSwigger این Vulnerability را معمولاً Serious می‌داند، اما Impact نهایی کاملاً به Gadget و Cache Scope وابسته است. (portswigger.net)

گزارش حرفه‌ای Web Cache Poisoning

یک Report خوب باید دقیقاً Root Cause را توضیح دهد.

نه فقط:

Web Cache Poisoning exists.

گزارش بهتر:

Cache key:
Host + path

Unkeyed input:
X-Forwarded-Host

Backend behavior:
The value affects an absolute resource URL in the HTML response.

Cache behavior:
The altered response is stored under the same key as a normal request.

Impact:
Subsequent requests for the same public page can receive
the altered cached response.

سپس Remediation:

Do not trust client-supplied X-Forwarded-Host.
Overwrite it at the trusted proxy.
Use a configured public origin for absolute URLs.
Purge the affected cache entry.

این Report بسیار کاربردی‌تر از عنوان کلی Vulnerability است.

آیا WAF می‌تواند Web Cache Poisoning را متوقف کند؟

WAF می‌تواند بعضی Patternهای مشکوک را Block کند.

اما Web Cache Poisoning همیشه Payload عجیب ندارد.

گاهی فقط یک Host Name معتبر یا Header معمولی باعث Response متفاوت می‌شود.

Root Solution باید Cache Key و Application Data Flow را اصلاح کند.

WAF فقط Defense in Depth است.

آیا CSP کمک می‌کند؟

اگر Cache Poisoning برای Delivery یک XSS استفاده شود، Content Security Policy قوی ممکن است Impact بعضی Injectionها را کاهش دهد.

اما CSP خود Cache را اصلاح نمی‌کند.

همچنان ممکن است:

HTML تغییر کند،

Redirect Poison شود،

Resource اشتباه Serve شود،

یا DoS ایجاد شود.

بنابراین CSP مکمل است.

آیا HTTPS جلوی Web Cache Poisoning را می‌گیرد؟

خیر.

TLS از Communication در Transit محافظت می‌کند.

اما اگر CDN یا Cache قانونی سایت خودش Response Poisoned ذخیره کند، HTTPS همان Response را به‌صورت کاملاً رمزنگاری‌شده به User تحویل می‌دهد.

HTTPS نمی‌داند Content منطقی صحیح است یا خیر.

Defense in Depth برای Web Cache Poisoning

یک معماری مقاوم می‌تواند چنین باشد:

Internet Client
       ↓
Edge validation
       ↓
Header normalization
       ↓
Explicit cache key
       ↓
Cache policy
       ↓
Origin validation
       ↓
Safe response generation
       ↓
Monitoring

در کنار آن:

Sensitive pages → no shared cache
Trusted proxy only
Strict Host validation
Minimal unkeyed inputs
Safe Cache-Control
Correct Vary
Fast purge
Regression testing

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

سؤالات متداول درباره Web Cache Poisoning

Web Cache Poisoning چیست؟

Web Cache Poisoning زمانی رخ می‌دهد که مهاجم باعث ذخیره یک Response دستکاری‌شده در HTTP Cache شود و همان Response بعداً به Userهای دیگری که Cache Key مشابهی دارند تحویل داده شود. RFC 9111 Cache Poisoning را یکی از ریسک‌های امنیتی HTTP Cache معرفی می‌کند. (rfc-editor.org)

Cache Key چیست؟

Cache Key مجموعه اطلاعاتی است که Cache برای تشخیص Requestهای معادل استفاده می‌کند. طبق RFC 9111، Method و Target URI حداقل بخشی از این Key هستند و Cache می‌تواند اطلاعات دیگری نیز اضافه کند. (rfc-editor.org)

Unkeyed Input چیست؟

ورودی‌ای است که Cache هنگام ساخت Cache Key در نظر نمی‌گیرد. اگر Backend همان Input را برای تغییر Response استفاده کند، زمینه Web Cache Poisoning ممکن است ایجاد شود. (portswigger.net)

آیا X-Forwarded-Host می‌تواند باعث Cache Poisoning شود؟

بله، اگر Client بتواند مقدار آن را کنترل کند، Backend براساس آن Response را تغییر دهد و Cache مقدار آن را در Key لحاظ نکند. OWASP این سناریو را در Host Header Injection و Web Cache Poisoning بررسی می‌کند. (wstg.owasp.org)

آیا Query String همیشه بخشی از Cache Key است؟

خیر. بعضی Cache Ruleها تمام یا بخشی از Query String را Ignore می‌کنند. Configuration Provider باید بررسی شود. (portswigger.net)

Vary چیست؟

Vary Response Header مشخص می‌کند کدام Request Headerها باید هنگام انتخاب Cached Response در نظر گرفته شوند. مثلاً Vary: Accept-Language نسخه‌های زبانی مختلف را جدا نگه می‌دارد. (developer.mozilla.org)

تفاوت no-cache و no-store چیست؟

no-cache به معنی «اصلاً ذخیره نکن» نیست؛ Response می‌تواند ذخیره شود اما قبل از Reuse باید Revalidate شود. no-store برای جلوگیری از Storage Response استفاده می‌شود. (rfc-editor.org)

Cache-Control private چیست؟

private اعلام می‌کند Response برای Shared Cache مناسب نیست و باید برای User خصوصی در نظر گرفته شود.

تفاوت Web Cache Poisoning و Cache Deception چیست؟

در Cache Poisoning مهاجم محتوای دستکاری‌شده را وارد Cache می‌کند تا به سایر کاربران تحویل داده شود. در Cache Deception، Cache به ذخیره محتوای خصوصی قربانی فریب داده می‌شود و مهاجم آن را بازیابی می‌کند. (portswigger.net)

Web Cache Poisoning با Request Smuggling یکی است؟

خیر. Request Smuggling به اختلاف Parsing مرز Request بین چند Server مربوط است، در حالی که Cache Poisoning به ذخیره Response نامناسب زیر Cache Key قابل استفاده توسط دیگران مربوط می‌شود. Request Smuggling می‌تواند در برخی Attack Chainها به Cache Poisoning منجر شود.

آیا Web Cache Poisoning همان DNS Cache Poisoning است؟

خیر. DNS Cache Poisoning اطلاعات DNS را دستکاری می‌کند، در حالی که Web Cache Poisoning روی HTTP Response Cache اثر می‌گذارد.

آیا WordPress در برابر Web Cache Poisoning آسیب‌پذیر است؟

وجود WordPress به‌تنهایی Vulnerability ایجاد نمی‌کند. خطر معمولاً از تعامل WordPress، Pluginها، Page Cache، Reverse Proxy و CDN و Cache Ruleهای نادرست ایجاد می‌شود.

WordPress چگونه جلوی Cache شدن صفحات حساس را می‌گیرد؟

WordPress تابع nocache_headers() را دارد که Headerهای کنترل Cache را برای جلوگیری از Cache شدن Response ارسال می‌کند. wp_get_nocache_headers() نیز در نسخه‌های فعلی Directiveهایی مانند no-store و private ارائه می‌کند. (developer.wordpress.org)

آیا WooCommerce باید Cache شود؟

Static Assetها و بعضی صفحات عمومی قابل Cache هستند، اما Cart، Checkout، Account و سایر Contentهای Session-Specific معمولاً نباید Shared Full-Page Cache معمولی داشته باشند.

آیا CDN باعث افزایش خطر می‌شود؟

CDN به‌خودی‌خود ناامن نیست. اما چون Shared Cache بزرگی است، یک Cache Entry اشتباه می‌تواند دامنه Impact بیشتری داشته باشد. Cache Key و Cache Rule باید دقیق تنظیم شوند.

آیا Cache-Control: no-store همه Cache Poisoningها را حل می‌کند؟

برای Responseهایی که نباید Cache شوند بسیار مؤثر است، اما اگر Response واقعاً باید Cache شود، Root Problem Cache Key و Unsafe Input همچنان باید حل شود.

بهترین راه جلوگیری از Web Cache Poisoning چیست؟

ورودی‌هایی که Response را تغییر می‌دهند باید یا حذف شوند، یا در Cache Key لحاظ شوند، یا Response مربوط نباید در Shared Cache ذخیره شود. Cache Layerها باید Inventory و رفتار آنها در کنار Backend تست شود.

آیا می‌توان Web Cache Poisoning را روی Production تست کرد؟

بهتر است خیر. تست می‌تواند Shared Cache واقعی را تغییر دهد و کاربران را تحت تأثیر قرار دهد. PortSwigger نیز درباره خطر تست مستقیم روی سایت Live هشدار می‌دهد. Staging یا Cache Namespace ایزوله انتخاب امن‌تری است. (portswigger.net)

جمع‌بندی

Web Cache Poisoning یکی از آسیب‌پذیری‌هایی است که از شکاف میان دید Application و دید Cache نسبت به یک HTTP Request ایجاد می‌شود.

Application ممکن است بگوید:

«این دو Request متفاوت‌اند، چون Header یا Parameter متفاوتی دارند.»

اما Cache ممکن است بگوید:

«هر دو یک Cache Key دارند، پس Response آنها قابل استفاده مجدد است.»

اگر Attacker بتواند از این اختلاف استفاده کند تا Origin یک Response نامطلوب تولید کند و آن Response زیر Cache Key عادی ذخیره شود، کاربران دیگر نیز ممکن است همان محتوای مسموم را دریافت کنند.

RFC 9111 Cache را مکانیزمی برای ذخیره و استفاده مجدد HTTP Responseها معرفی می‌کند و در بخش امنیتی خود صراحتاً Cache Poisoning را به‌عنوان خطری مطرح می‌کند که می‌تواند با Shared Cache اثر حمله را به چندین Client گسترش دهد. (rfc-editor.org)

در مدل‌های مدرن، مهم‌ترین مفهوم برای درک Web Cache Poisoning «Unkeyed Input» است.

Header، Cookie یا Parameter ممکن است توسط Backend پردازش شود اما Cache آن را در Key نبیند.

اگر همان Input بتواند HTML، Redirect، Resource URL یا Behavior Response را تغییر دهد و Response قابل Cache باشد، Trust Boundary شکسته می‌شود.

Host و X-Forwarded-Host از نمونه‌های شناخته‌شده هستند؛ OWASP نیز Host Header Manipulation را از مسیرهای احتمالی Web Cache Poisoning می‌داند. (wstg.owasp.org)

Query String، Cookie، Custom Header و حتی GET Body نیز بسته به Cache Implementation می‌توانند همین نقش را داشته باشند.

راهکار اصلی فقط «اضافه کردن Inputهای بیشتر به Cache Key» نیست.

اول باید تعیین شود چرا Application اصلاً Input مذکور را Trust می‌کند.

Inputهای غیرضروری باید حذف شوند.

مقادیر Proxy فقط از Trusted Proxy پذیرفته شوند.

Absolute URLها از Configuration ثابت ساخته شوند.

Responseهای Sensitive یا User-Specific باید از Shared Cache خارج شوند.

برای Content Negotiation واقعی باید Vary و Cache Key درست استفاده شود.

Cache-Control نیز باید متناسب با Resource تعریف شود؛ مخصوصاً باید تفاوت no-cache، no-store و private درک شود.

در WordPress مسئله پیچیده‌تر می‌شود، چون چند Cache Layer هم‌زمان ممکن است وجود داشته باشد: CDN، Hosting Cache، Nginx، Plugin Page Cache و Browser. WordPress توابعی مانند nocache_headers() را برای Responseهایی که نباید Cache شوند ارائه می‌دهد، اما Custom Plugin، WooCommerce Flow، REST API و صفحات Member Area همچنان به Cache Policy صریح نیاز دارند. (developer.wordpress.org)

تست نیز باید مسئولانه انجام شود.

Web Cache Poisoning از آن دسته ضعف‌هایی است که حتی یک Test Request ساده ممکن است روی Userهای واقعی اثر بگذارد. به همین دلیل Staging، Cache Namespace جدا و Markerهای غیرمخرب باید به تست مستقیم Production ترجیح داده شوند.

در نهایت، مهم‌ترین سؤال هنگام Audit یک سیستم Cache این است:

«آیا دو Request که Cache آنها را معادل می‌داند، از دید Application نیز واقعاً باید دقیقاً یک Response قابل اشتراک دریافت کنند؟»

اگر پاسخ منفی باشد، Cache Key، Cache Policy یا طراحی Application باید بازنگری شود.

مطالب مرتبط