هدرهای امنیتی چه میکنند و چه نمیکنند
هدر امنیتی هدر پاسخی است که از مرورگر میخواهد با صفحهٔ شما سختگیرتر از حالت پیشفرض رفتار کند: فقط از HTTPS استفاده کن، نوع فایل را حدس نزن، در قاب سایتهای دیگر نرو، در Referer کمتر بگو، دوربین را خاموش نگه دار و فقط اسکریپتهایی را اجرا کن که تأیید شدهاند.
اینها دفاع چندلایهاند. هیچکدام اپلیکیشن آسیبپذیر را درست نمیکند: SQL injection یا یک فرایند ورود معیوب با بینقصترین مجموعهٔ هدر هم همانقدر معیوب است. کارشان محدودکردن آسیب خطاهایی است که مرورگر میتواند مهارشان کند، مثل cross-site scripting، clickjacking، پایینآوردن پروتکل و نشت داده از طریق referrer، برای هر بازدیدکننده و به بهای چند صد بایت.
و فقط در مرورگرها کار میکنند. یک کلاینت API، یک crawler یا اسکریپت مهاجم همهٔ آنها را نادیده میگیرد؛ برای همین خط اول دفاع همچنان یک WAF و خود کد اپلیکیشن است.
Strict-Transport-Security (HSTS)
Strict-Transport-Security به مرورگر میگوید برای hostname شما از HTTPS استفاده کند و این را max-age ثانیه به خاطر بسپارد، تا حتی آدرس http:// تایپشده هم بهصورت متن ساده از دستگاه خارج نشود. این هدر با کمترین زحمت بیشترین اثر را دارد، به شرطی که همهٔ URLهای سایت از قبل روی HTTPS کار کنند.
شروع امن max-age=31536000 است: یک سال، فقط برای همین hostname. includeSubDomains و preload دامنهٔ بیشتری را میگیرند و برگرداندنشان سخت است. راهنمای HSTS توضیح میدهد هر کدام شما را به چه متعهد میکند، هدر را چطور تأیید کنید و چطور عقبنشینی کنید.
در CDN.com.tr، HSTS یک preset حساب در قوانین تحویل است، پس لازم نیست برایش خط هدری بنویسید. دقیقاً max-age=31536000 را در پاسخهای HTTPS میفرستد و در HTTP ساده هیچ چیزی نمیفرستد.
X-Content-Type-Options: nosniff
مرورگرها قبلاً وقتی هدر Content-Type اشتباه به نظر میرسید، نوع فایل را از روی محتوایش حدس میزدند. این حدسزدن که MIME sniffing نام دارد، اجازه میداد فایل آپلودشدهای که شبیه اسکریپت بود بهعنوان اسکریپت اجرا شود. X-Content-Type-Options: nosniff آن را خاموش میکند: stylesheet باید با text/css و اسکریپت با یک نوع JavaScript برسد، وگرنه مرورگر ردش میکند.
تنها مقدارش nosniff است و این هدر روی هر پاسخی امن است، HTML باشد یا نه. فقط یک چیز را پیش از آن بررسی کنید: اینکه سرورتان فایلها را درست برچسب میزند. اسکریپتی که با text/plain سرو شود، به محض اضافهشدن هدر بارگذاری نمیشود، و این دقیقاً همان رفتاری است که خواستهاید.
X-Frame-Options و frame-ancestors: چه کسی صفحههای شما را قاب میگیرد
Clickjacking صفحهٔ شما را در قابی نامرئی روی سایت دیگری بارگذاری میکند و یکی از دکمههای آن را روی یکی از دکمههای شما میگذارد؛ بازدیدکننده خیال میکند «پخش» را زده، اما «حذف حساب» را زده است. دفاع این است که به مرورگر بگویید چه کسی میتواند صفحهٔ شما را در قاب بگذارد.
X-Frame-Options دو مقدار کاربردی دارد: DENY برای هیچکس و SAMEORIGIN برای صفحههای همان origin خودتان. مقدار قدیمی ALLOW-FROM را مرورگرهای امروزی نادیده میگیرند. برای مجازکردن فهرستی از سایتها از دستور CSP یعنی frame-ancestors استفاده کنید، مثلاً frame-ancestors 'self' https://partner.example.
وقتی پاسخ هر دو را داشته باشد، مرورگرهایی که frame-ancestors را میفهمند از آن پیروی میکنند و X-Frame-Options را نادیده میگیرند. فرستادن هر دو رایج و بیضرر است، چون هدر قدیمی مرورگرهای قدیمی را پوشش میدهد، به شرطی که هر دو یک چیز بگویند. یک محدودیت: frame-ancestors فقط بهعنوان هدر HTTP کار میکند، هرگز در تگ <meta>.
Referrer-Policy: با هر کلیک چه چیزی بیرون میرود
وقتی بازدیدکننده روی پیوندی کلیک میکند، یا صفحهٔ شما تصویر، اسکریپت یا فونتی را از سایت دیگری بارگذاری میکند، مرورگر ممکن است آدرس صفحهٔ شما را در هدر Referer بفرستد. URLهای کامل بیش از آنچه فکر میکنید لو میدهند: عبارتهای جستوجو، شمارهٔ سفارشها، توکن پیوند بازنشانی رمز عبور.
مقدار معقول strict-origin-when-cross-origin است: URL کامل درون سایت خودتان، فقط origin (https://example.com/) به سایتهای دیگر، و هیچ چیز وقتی صفحهٔ HTTPS به HTTP ساده پیوند میدهد. مرورگرهای امروزی وقتی صفحه چیزی نگوید همین یا سختگیرانهترش را اعمال میکنند؛ تنظیم صریح آن رفتار را ثابت نگه میدارد، پیشفرض هر مرورگری هر چه شود. برای اپلیکیشنی با URLهای حساس، no-referrer هیچ چیزی نمیفرستد، به بهای ازدسترفتن دادهٔ ارجاع در آنالیتیکس سایتهای دیگر.
Permissions-Policy: آنچه استفاده نمیکنید خاموش کنید
Permissions-Policy تعیین میکند صفحهٔ شما و قابهای درونش از کدام قابلیتهای قدرتمند مرورگر میتوانند استفاده کنند: دوربین، میکروفون، موقعیت مکانی، پرداخت، USB و غیره. فهرست خالی () قابلیت را برای همه خاموش میکند، (self) فقط به origin خودتان اجازه میدهد، و میتوانید origin محتواهای embed شدهای را که واقعاً لازمش دارند نام ببرید.
سایت شرکتی یا فروشگاه تقریباً به هیچکدام نیاز ندارد، پس camera=(), microphone=(), geolocation=() شروع معقولی است؛ هر چه آنچه را embed میکنید بازبینی کردید، بقیه را اضافه کنید. سودش مهار است: یک اسکریپت شخص ثالثِ نفوذشده یا یک قاب تبلیغاتی نمیتواند از بازدیدکننده دوربینش را بخواهد. مرورگرهای مبتنی بر Chromium این هدر را اجرا میکنند؛ آن را سختسازیِ اضافه بر کنترلهای دیگر ببینید، نه جایگزینشان.
Content-Security-Policy: قدرتمند و ارزش یک برنامه را دارد
Content Security Policy به مرورگر میگوید اسکریپتها، استایلها، تصاویر، فونتها، اتصالها و قابها از کجا میتوانند بیایند و آیا کد inline اجازهٔ اجرا دارد یا نه. اگر خوب تنظیم شود، بیشتر باگهای cross-site scripting را از «مهاجم در session کاربران شما کد اجرا میکند» به «مرورگر یک اسکریپت را مسدود و گزارش کرد» تبدیل میکند.
همین هدر است که صفحه را میشکند. بلوکهای inline <script>، attributeهای onclick، یک tag manager که اسکریپتهای دیگر تزریق میکند، یک endpoint آنالیتیکس که کسی یادش نبود: هر کدام باید مجاز یا بازنویسی شود. پس آن را در دو گام منتشر کنید. اول سیاست را به شکل Content-Security-Policy-Report-Only بفرستید: چیزی مسدود نمیشود و هر نقض در کنسول، و اگر endpoint برای report-to یا report-uri دارید در گزارشهایتان دیده میشود. آنچه گزارشها نشان میدهند را اصلاح یا مجاز کنید، بعد همان سیاست را به شکل Content-Security-Policy بفرستید.
سیاست زیر سختگیرانه اما خواناست. برای اسکریپتهای inline که نمیتوانید حذفشان کنید، برای هر پاسخ یک nonce تصادفی بسازید و آن را هم در سیاست و هم در تگ <script> بگذارید. با 'strict-dynamic'، اسکریپتهایی که یک اسکریپت مورد اعتماد بارگذاری میکند هم مورد اعتماد حساب میشوند و tag managerها بدون فهرستکردن همهٔ دامنههایی که سراغشان میروند کار میکنند. برای اسکریپتها از 'unsafe-inline' پرهیز کنید: بیشتر محافظتی را که سیاست برایش وجود دارد خاموش میکند.
یک سیاست شروع سختگیرانه، در حالت report-only
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests
مقدارهای امن برای کپی
بیشتر سایتها میتوانند با همین مجموعه شروع کنند و فقط منبعهای CSP و فهرست مجوزها را تنظیم کنند. ترتیب مهم نیست؛ مهم این است که هر هدر یک بار بیاید.
نبودِ X-XSS-Protection عمدی است. این هدر فیلتری را کنترل میکرد که مرورگرهای امروزی حذفش کردهاند و در مرورگرهای قدیمی خودِ فیلتر قابل سوءاستفاده بود؛ هیچ چیزی نفرستید یا X-XSS-Protection: 0 بفرستید. Expect-CT هم به دلیلی مشابه منسوخ است و میتواند کنار برود.
هدرهای پاسخ برای یک صفحهٔ HTML
Strict-Transport-Security: max-age=31536000
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Content-Security-Policy-Report-Only: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'
تنظیم آنها در CDN.com.tr
HSTS preset مخصوص خودش را دارد که بالاتر توضیح داده شد. بقیهٔ هدرهای تکمقداری در یک قانون تحویل قرار میگیرند: در قوانین تحویل قانونی را که صفحههای شما را سرو میکند، معمولاً /، ویرایش کنید و آنها را در فیلد هدرهای سفارشی هر کدام در یک خط به شکل Header-Name value اضافه کنید (در صفحهٔ قوانینِ زبانهدار: سرآیندها ← افزودن سرآیند به پاسخ). مقداری که فاصله دارد داخل گیومهٔ دوتایی نوشته میشود.
Content-Security-Policy کامل آنجا جا نمیشود. دستورهایش با نقطهویرگول جدا میشوند و پنل و edge کاراکتر ; را در خط هدر نمیپذیرند، چون نقطهویرگول دستور پیکربندی خودِ edge را تمام میکند. یک دستور تنها نقطهویرگول ندارد، پس frame-ancestors بهتنهایی روی edge کار میکند. جای سیاست کامل سرور origin شماست، در وبسرور یا اپلیکیشن، که آنجا میتواند برای هر درخواست یک nonce هم داشته باشد. تگ HTML یعنی <meta http-equiv="Content-Security-Policy"> هم کار میکند، به جز frame-ancestors، report-uri و sandbox که مرورگرها در تگ meta نادیده میگیرند.
هدرهایی که قانون اضافه میکند، به محض انتشار قانون روی پاسخهای موفق و redirect (2xx و 3xx)، از جمله پاسخهایی که از cache میآیند، اعمال میشوند؛ هدری که باید در صفحههای خطا هم باشد جایش سرور origin است. هدرهایی که سرور origin شما میفرستد همراه هر نسخهٔ cache شده ذخیره میشوند، پس پس از تغییرشان برای صفحههای متأثر کش CDN را پاک کنید. اگر سرور origin شما یکی از این هدرها را از قبل میفرستد، آن را در پنهانسازی هدرها در همان قانون انتخاب کنید، وگرنه دو نسخه به بازدیدکننده میرسد.
هدرهای سفارشی در قانونی که صفحههای شما را سرو میکند
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
Referrer-Policy strict-origin-when-cross-origin
Permissions-Policy "camera=(), microphone=(), geolocation=()"
Content-Security-Policy "frame-ancestors 'self'"
چطور آزمایش کنیم
با curl شروع کنید که دقیقاً همان چیزی را نشان میدهد که سرور میفرستد. یک صفحه، یک فایل static و صفحهای که وجود ندارد را بررسی کنید: هدرهایی که فقط در یک جا تنظیم شدهاند اغلب در دو جای دیگر نیستند.
بعد ابزار توسعهدهندهٔ مرورگر را باز کنید. تب Network هدرها را همانطور که مرورگر گرفته نشان میدهد، و کنسول هر نقض CSP و هر قاب ردشده را همراه با دستوری که باعثش شده گزارش میکند. اسکنرهای آنلاین مثل HTTP Observatory موزیلا کل مجموعه را نمره میدهند و هر یافته را توضیح میدهند، که نظر دوم خوبی است.
در پایان رفتار را بیازمایید، نه فقط حضور را: صفحه را در یک iframe روی origin دیگری embed کنید و مطمئن شوید مرورگر ردش میکند، و پیش از اجباریکردن سیاست report-only چند روز کنسول را زیر نظر بگیرید.
curl -sI https://www.example.com/ | grep -i -E \
'strict-transport|x-content-type|x-frame|referrer-policy|permissions-policy|content-security'
# a static file and a missing page, too
curl -sI https://www.example.com/assets/app.css | grep -i x-content-type
curl -sI https://www.example.com/no-such-page | grep -i -E 'x-frame|content-security'
پرسشهای رایج دربارهٔ هدرهای امنیتی
هر وبسایتی کدام هدرهای امنیتی را باید بفرستد؟
HSTS، وقتی کل سایت روی HTTPS کار میکند؛ X-Content-Type-Options: nosniff؛ X-Frame-Options: SAMEORIGIN یا frame-ancestors در CSP؛ Referrer-Policy: strict-origin-when-cross-origin؛ و یک Permissions-Policy که قابلیتهای بلااستفاده را خاموش کند. Content-Security-Policy را پس از یک دورهٔ report-only اضافه کنید.
آیا X-Frame-Options منسوخ شده است؟
بیشتر جانشین پیدا کرده تا اینکه حذف شده باشد. frame-ancestors در CSP همان کار را با کنترل بیشتر انجام میدهد و مرورگرهایی که از آن پشتیبانی میکنند وقتی هر دو برسند X-Frame-Options را نادیده میگیرند. فرستادن SAMEORIGIN یا DENY در کنار آن هنوز از مرورگرهای قدیمی محافظت میکند؛ فقط ALLOW-FROM مرده است.
آیا هنوز X-XSS-Protection بفرستم؟
نه. فیلتری که کنترل میکرد از مرورگرهای امروزی حذف شده و در مرورگرهای قدیمی میشد علیه خود صفحه از آن سوءاستفاده کرد. نفرستیدش یا 0 بفرستید و به جای آن به Content-Security-Policy تکیه کنید.
آیا میتوانم Content-Security-Policy را از طریق CDN.com.tr تنظیم کنم؟
یک دستور تنها، بله: مثلاً frame-ancestors 'self' در هدرهای سفارشی یک قانون تحویل. سیاست کامل بین دستورهایش نقطهویرگول لازم دارد که قوانین edge نمیپذیرند؛ آن را در وبسرور یا اپلیکیشن تنظیم کنید، یا برای همه چیز به جز frame-ancestors، report-uri و sandbox در یک تگ meta.
آیا هدرهای امنیتی روی SEO یا سرعت اثر دارند؟
نه بهطور محسوس. چند صد بایت اضافه میکنند و HTTP/2 و HTTP/3 هدرهای تکراری را فشرده میکنند. عامل رتبهبندیِ مستندی نیستند. تنها خطر SEO یک CSP است که اسکریپتهای خودتان را مسدود کند و نمایش صفحه را بشکند، که گام report-only آن را پیدا میکند.
آیا تصاویر، CSS و فایلهای JavaScript هم این هدرها را لازم دارند؟
nosniff و HSTS روی هر پاسخی مفیدند. بقیه، یعنی CSP، محافظت در برابر قابگرفتن، Referrer-Policy و Permissions-Policy، روی سندها عمل میکنند، پس صفحههای HTML مهماند. فرستادنشان روی همه چیز ضرری ندارد.