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 چیست؟
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 یک تکنیک حمله است که در آن مهاجم شرایطی ایجاد میکند تا 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
یکی از شناختهشدهترین 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)
Unkeyed Cookie
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:
مهاجم Response مخرب یا دستکاریشدهای را وارد Cache میکند و Userهای دیگر آن را دریافت میکنند.
Web Cache Deception:
مهاجم Cache را فریب میدهد تا Content خصوصی قربانی را ذخیره کند، سپس مهاجم همان Content Cache شده را بازیابی میکند.
PortSwigger این تفاوت را بهصورت روشن بیان میکند: در Poisoning محتوای مخرب به قربانیان تحویل داده میشود؛ در Deception محتوای حساس قربانی Cache میشود تا مهاجم آن را بخواند. (portswigger.net)
جدول مقایسه:
| ویژگی | Web Cache Poisoning | Web Cache Deception |
|---|---|---|
| هدف اصلی | دستکاری Response برای سایر کاربران | سرقت Response خصوصی |
| مهاجم چه چیزی وارد Cache میکند؟ | Response تغییرکرده یا مخرب | Response حساس قربانی |
| چه کسی Response را میگیرد؟ | قربانیان | مهاجم |
| مشکل اصلی | Cache Key / Unkeyed Input / Response Influence | اختلاف Cache و Origin در تشخیص Resource |
| اثر رایج | Integrity، XSS، Redirect، DoS | Confidentiality |
تفاوت 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
اصل اول:
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های Security-Relevant را Inventory کنید
برای هر 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 واقعی را بررسی کنید.
تصور اینکه Cookie همیشه مانع Cache است
همه 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 باید بازنگری شود.