برنامه نویسی متقابل سایت (XSS) ، توانایی تزریق اسکریپت های مخرب به یک برنامه وب، یکی از بزرگترین آسیب پذیری های امنیتی وب برای بیش از یک دهه بوده است.
سیاست امنیتی محتوا (CSP) یک لایه امنیتی اضافه شده است که به کاهش XSS کمک می کند. برای پیکربندی یک CSP، سربرگ Content-Security-Policy
HTTP را به یک صفحه وب اضافه کنید و مقادیری را تنظیم کنید که کنترل کند عامل کاربر چه منابعی را برای آن صفحه بارگذاری کند.
این صفحه نحوه استفاده از CSP بر اساس nonces یا هش را برای کاهش XSS، به جای CSPهای معمول مبتنی بر لیست مجاز میزبان که اغلب صفحه را در معرض XSS قرار میدهند، توضیح میدهد، زیرا در اکثر پیکربندیها میتوان آنها را دور زد .
اصطلاح کلیدی: nonce یک عدد تصادفی است که فقط یک بار استفاده می شود و می توانید از آن برای علامت گذاری یک تگ <script>
به عنوان مورد اعتماد استفاده کنید.
اصطلاح کلیدی: تابع هش یک تابع ریاضی است که یک مقدار ورودی را به یک مقدار عددی فشرده به نام هش تبدیل میکند. شما می توانید از یک هش (به عنوان مثال SHA-256 ) برای علامت گذاری یک تگ <script>
درون خطی به عنوان مورد اعتماد استفاده کنید.
یک خطمشی امنیت محتوا مبتنی بر nonces یا هش اغلب CSP سختگیرانه نامیده میشود. هنگامی که یک برنامه کاربردی از یک CSP سختگیرانه استفاده می کند، مهاجمانی که نقص های تزریق HTML را پیدا می کنند معمولاً نمی توانند از آنها برای وادار کردن مرورگر به اجرای اسکریپت های مخرب در یک سند آسیب پذیر استفاده کنند. این به این دلیل است که CSP سخت فقط به اسکریپتهای هش شده یا اسکریپتهایی با مقدار nonce صحیح تولید شده در سرور اجازه میدهد، بنابراین مهاجمان نمیتوانند اسکریپت را بدون دانستن nonce صحیح برای یک پاسخ مشخص، اجرا کنند.
چرا باید از CSP سختگیرانه استفاده کنید؟
اگر سایت شما قبلاً دارای یک CSP شبیه script-src www.googleapis.com
است، احتمالاً در برابر متقابل سایت مؤثر نیست. به این نوع CSP ، CSP لیست مجوز می گویند. آنها نیاز به سفارشی سازی زیادی دارند و مهاجمان می توانند از آنها عبور کنند .
CSPهای سختگیرانه مبتنی بر nonces یا هش رمزنگاری از این تله ها جلوگیری می کنند.
ساختار دقیق CSP
یک خطمشی امنیتی سختگیرانه محتوا از یکی از سرصفحههای پاسخ HTTP زیر استفاده میکند:
CSP سختگیرانه غیر مبتنی بر
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
CSP سختگیرانه مبتنی بر هش
Content-Security-Policy:
script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
ویژگی های زیر یک CSP مانند این را "سخت" و در نتیجه ایمن می کند:
- از nonces
'nonce-{RANDOM}'
یا هش'sha256-{HASHED_INLINE_SCRIPT}'
استفاده میکند تا نشان دهد توسعهدهنده سایت به کدام برچسب<script>
اعتماد دارد تا در مرورگر کاربر اجرا شود. -
'strict-dynamic'
را تنظیم می کند تا تلاش برای استقرار یک CSP غیر مبتنی بر هش را با اجازه دادن خودکار به اجرای اسکریپت هایی که یک اسکریپت قابل اعتماد ایجاد می کند، کاهش دهد. این همچنین استفاده از اکثر کتابخانه ها و ویجت های جاوا اسکریپت شخص ثالث را از حالت انسداد خارج می کند. - این بر اساس لیست های مجاز URL نیست، بنابراین از دور زدن های معمول CSP رنج نمی برد.
- این اسکریپت های درون خطی غیرقابل اعتماد مانند کنترل کننده رویداد درون خطی یا
javascript:
URI ها را مسدود می کند. -
object-src
برای غیرفعال کردن پلاگین های خطرناک مانند Flash محدود می کند. -
base-uri
برای جلوگیری از تزریق تگ های<base>
محدود می کند. این مانع از تغییر مکان اسکریپت های بارگیری شده از URL های نسبی توسط مهاجمان می شود.
یک CSP سختگیرانه را اتخاذ کنید
برای اتخاذ یک CSP سختگیرانه، باید:
- تصمیم بگیرید که آیا برنامه شما باید یک CSP غیر یک یا هش تنظیم کند.
- CSP را از بخش ساختار Strict CSP کپی کنید و آن را به عنوان یک هدر پاسخ در برنامه خود تنظیم کنید.
- Refactor قالب های HTML و کد سمت سرویس گیرنده برای حذف الگوهای ناسازگار با CSP.
- CSP خود را مستقر کنید.
میتوانید از Lighthouse (نسخه 7.3.0 و بالاتر با flag --preset=experimental
) در طول این فرآیند استفاده کنید تا بررسی کنید که آیا سایت شما دارای CSP است یا خیر، و آیا به اندازه کافی سختگیرانه است که در برابر XSS مؤثر باشد.
مرحله 1: تصمیم بگیرید که آیا به یک CSP غیر مبتنی بر هش نیاز دارید یا خیر
در اینجا نحوه عملکرد دو نوع CSP سختگیرانه آمده است:
CSP غیر مبتنی بر
با یک CSP غیرمبتنی، شما یک عدد تصادفی در زمان اجرا تولید میکنید، آن را در CSP خود قرار میدهید، و آن را با هر تگ اسکریپتی در صفحه خود مرتبط میکنید. مهاجم نمی تواند یک اسکریپت مخرب را در صفحه شما قرار دهد یا اجرا کند، زیرا باید عدد تصادفی صحیح آن اسکریپت را حدس بزند. این فقط در صورتی کار می کند که عدد قابل حدس زدن نباشد و در زمان اجرا برای هر پاسخ به تازگی تولید شود.
برای صفحات HTML ارائه شده بر روی سرور از یک CSP غیر مبتنی بر استفاده کنید. برای این صفحات، می توانید برای هر پاسخ یک عدد تصادفی جدید ایجاد کنید.
CSP مبتنی بر هش
برای یک CSP مبتنی بر هش، هش هر تگ اسکریپت درون خطی به CSP اضافه می شود. هر اسکریپت هش متفاوتی دارد. مهاجم نمی تواند یک اسکریپت مخرب را در صفحه شما قرار دهد یا اجرا کند، زیرا هش آن اسکریپت برای اجرا باید در CSP شما باشد.
از یک CSP مبتنی بر هش برای صفحات HTML که به صورت ایستا ارائه می شوند یا صفحاتی که نیاز به کش دارند استفاده کنید. برای مثال، میتوانید از یک CSP مبتنی بر هش برای برنامههای وب تک صفحهای که با فریمورکهایی مانند Angular، React یا موارد دیگر ساخته شدهاند، استفاده کنید که بهصورت ایستا و بدون رندر سمت سرور ارائه میشوند.
مرحله 2: یک CSP سختگیرانه تنظیم کنید و اسکریپت های خود را آماده کنید
هنگام تنظیم یک CSP، چند گزینه دارید:
- حالت فقط گزارش (
Content-Security-Policy-Report-Only
) یا حالت اجرایی (Content-Security-Policy
). در حالت فقط گزارش، CSP هنوز منابع را مسدود نمی کند، بنابراین هیچ چیز در سایت شما خراب نمی شود، اما می توانید خطاها را ببینید و برای هر چیزی که مسدود شده بود گزارش دریافت کنید. به صورت محلی، وقتی CSP خود را تنظیم می کنید، این واقعاً مهم نیست، زیرا هر دو حالت خطاهای موجود در کنسول مرورگر را به شما نشان می دهند. در هر صورت، حالت اجرا می تواند به شما کمک کند منابعی را که بلوک های پیش نویس CSP خود را پیدا می کنید، پیدا کنید، زیرا مسدود کردن یک منبع می تواند صفحه شما را شکسته به نظر برساند. حالت فقط گزارش بعداً در این فرآیند بسیار کاربردی می شود ( مرحله 5 را ببینید). - هدر یا تگ
<meta>
HTML. برای توسعه محلی، یک تگ<meta>
می تواند برای بهینه سازی CSP شما و مشاهده سریع تاثیر آن بر سایت شما راحت تر باشد. با این حال:- بعداً، هنگام استقرار CSP خود در تولید، توصیه می کنیم آن را به عنوان یک هدر HTTP تنظیم کنید.
- اگر میخواهید CSP خود را در حالت فقط گزارش تنظیم کنید، باید آن را به عنوان یک هدر تنظیم کنید، زیرا متا تگهای CSP از حالت فقط گزارش پشتیبانی نمیکنند.
هدر پاسخ HTTP Content-Security-Policy
زیر را در برنامه خود تنظیم کنید:
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
یک nonce برای CSP ایجاد کنید
Nonce یک عدد تصادفی است که فقط یک بار در هر بارگذاری صفحه استفاده می شود. یک CSP مبتنی بر غیرانس تنها زمانی می تواند XSS را کاهش دهد که مهاجمان نتوانند مقدار nonce را حدس بزنند. یک nonce CSP باید باشد:
- یک مقدار تصادفی رمزنگاری قوی (طول ایده آل 128+ بیت)
- برای هر پاسخی که به تازگی تولید شده است
- Base64 کدگذاری شده است
در اینجا چند نمونه از نحوه اضافه کردن یک CSP nonce در چارچوب های سمت سرور آورده شده است:
- جانگو (پایتون)
- Express (جاوا اسکریپت):
const app = express();
app.get('/', function(request, response) {
// Generate a new random nonce value for every response.
const nonce = crypto.randomBytes(16).toString("base64");
// Set the strict nonce-based CSP response header
const csp = `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`;
response.set("Content-Security-Policy", csp);
// Every <script> tag in your application should set the `nonce` attribute to this value.
response.render(template, { nonce: nonce });
});
یک ویژگی nonce
به عناصر <script>
اضافه کنید
با یک CSP مبتنی بر nonce، هر عنصر <script>
باید دارای یک ویژگی nonce
باشد که با مقدار nonce تصادفی مشخص شده در هدر CSP مطابقت داشته باشد. همه اسکریپت ها می توانند nonce یکسانی داشته باشند. اولین قدم این است که این ویژگی ها را به همه اسکریپت ها اضافه کنید تا CSP به آنها اجازه دهد.
هدر پاسخ HTTP Content-Security-Policy
زیر را در برنامه خود تنظیم کنید:
Content-Security-Policy:
script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
برای چند اسکریپت درون خطی، نحو به شرح زیر است: 'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'
.
اسکریپت های منبع را به صورت پویا بارگیری کنید
شما می توانید اسکریپت های شخص ثالث را به صورت پویا با استفاده از یک اسکریپت درون خطی بارگیری کنید.
<script>
var scripts = [ 'https://example.org/foo.js', 'https://example.org/bar.js'];
scripts.forEach(function(scriptUrl) {
var s = document.createElement('script');
s.src = scriptUrl;
s.async = false; // to preserve execution order
document.head.appendChild(s);
});
</script>
<script src="https://example.org/foo.js"></script>
<script src="https://example.org/bar.js"></script>
ملاحظات بارگیری اسکریپت
مثال اسکریپت درون خطی s.async = false
را اضافه می کند تا اطمینان حاصل شود که foo
قبل bar
اجرا می شود، حتی اگر bar
ابتدا بارگیری شود. در این قطعه، s.async = false
هنگام بارگیری اسکریپت ها تجزیه کننده را مسدود نمی کند، زیرا اسکریپت ها به صورت پویا اضافه می شوند. تجزیه کننده فقط در حین اجرای اسکریپت ها متوقف می شود، همانطور که برای اسکریپت های async
انجام می شود. با این حال، با این قطعه، به خاطر داشته باشید:
- ممکن است یک یا هر دو اسکریپت قبل از پایان دانلود سند اجرا شوند. اگر می خواهید سند تا زمان اجرای اسکریپت ها آماده شود، قبل از اینکه اسکریپت ها را اضافه کنید منتظر رویداد
DOMContentLoaded
باشید. اگر این باعث مشکل در عملکرد می شود زیرا اسکریپت ها به اندازه کافی زود دانلود نمی شوند، از برچسب های پیش بارگذاری زودتر در صفحه استفاده کنید. -
defer = true
کاری نمی کند. اگر به آن رفتار نیاز دارید، اسکریپت را در صورت نیاز به صورت دستی اجرا کنید.
مرحله 3: الگوهای HTML و کد سمت مشتری را Refactor کنید
کنترلکنندههای رویداد درون خطی (مانند onclick="…"
, onerror="…"
) و URIهای جاوا اسکریپت ( <a href="javascript:…">
) میتوانند برای اجرای اسکریپتها استفاده شوند. این بدان معناست که مهاجمی که باگ XSS را پیدا میکند میتواند این نوع HTML را تزریق کرده و جاوا اسکریپت مخرب را اجرا کند. یک CSP غیر مبتنی بر هش استفاده از این نوع نشانه گذاری را ممنوع می کند. اگر سایت شما از هر یک از این الگوها استفاده می کند، باید آنها را به جایگزین های ایمن تر تبدیل کنید.
اگر CSP را در مرحله قبل فعال کرده باشید، هر بار که CSP یک الگوی ناسازگار را مسدود می کند، می توانید نقض CSP را در کنسول مشاهده کنید.
در بیشتر موارد، راه حل ساده است:
کنترل کننده رویداد درون خطی Refactor
<span id="things">A thing.</span>
<script nonce="${nonce}">
document.getElementById('things').addEventListener('click', doThings);
</script>
<span onclick="doThings();">A thing.</span>
Refactor javascript:
URIs
<a id="foo">foo</a>
<script nonce="${nonce}">
document.getElementById('foo').addEventListener('click', linkClicked);
</script>
<a href="javascript:linkClicked()">foo</a>
eval()
از جاوا اسکریپت خود حذف کنید
اگر برنامه شما از eval()
برای تبدیل سریالسازیهای رشتههای JSON به اشیاء JS استفاده میکند، باید چنین نمونههایی را به JSON.parse()
تبدیل کنید، که سریعتر نیز است.
اگر نمیتوانید همه استفادههای eval()
را حذف کنید، همچنان میتوانید یک CSP غیرمبتنی سخت تنظیم کنید، اما باید از کلمه کلیدی CSP 'unsafe-eval'
استفاده کنید که باعث میشود خطمشی شما کمی ایمنتر شود.
شما میتوانید این و نمونههای بیشتری از این نوع refactoring را در این کد CSP سختگیرانه بیابید:
مرحله 4 (اختیاری): برای پشتیبانی از نسخه های قدیمی مرورگر، نسخه های جایگزین اضافه کنید
اگر نیاز به پشتیبانی از نسخه های قدیمی مرورگر دارید:
- استفاده از
strict-dynamic
مستلزم افزودنhttps:
به عنوان نسخه بازگشتی برای نسخه های قبلی سافاری است. وقتی این کار را انجام می دهید:- همه مرورگرهایی که از
strict-dynamic
پشتیبانی میکنندhttps:
بازگشتی را نادیده میگیرند، بنابراین این امر قدرت سیاست را کاهش نمیدهد. - در مرورگرهای قدیمی، اسکریپتهای با منبع خارجی تنها در صورتی میتوانند بارگیری شوند که از منبع HTTPS باشند. این امنیت کمتر از یک CSP سختگیرانه است، اما همچنان از برخی دلایل رایج XSS مانند تزریق
javascript:
URIها جلوگیری می کند.
- همه مرورگرهایی که از
- برای اطمینان از سازگاری با نسخههای مرورگر بسیار قدیمی (4 سال به بالا)، میتوانید بهعنوان نسخهی بازگشتی
unsafe-inline
اضافه کنید. همه مرورگرهای اخیر در صورت وجود یک CSP nonce یا هش، ازunsafe-inline
چشم پوشی می کنند.
Content-Security-Policy:
script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';
مرحله 5: CSP خود را مستقر کنید
پس از تأیید اینکه CSP شما هیچ اسکریپت قانونی را در محیط توسعه محلی شما مسدود نمی کند، می توانید CSP خود را در مرحله مرحله بندی و سپس در محیط تولید خود مستقر کنید:
- (اختیاری) با استفاده از هدر
Content-Security-Policy-Report-Only
CSP خود را در حالت گزارش فقط اجرا کنید. حالت فقط گزارش برای آزمایش یک تغییر بالقوه شکست مانند یک CSP جدید در تولید قبل از شروع به اجرای محدودیتهای CSP مفید است. در حالت فقط گزارش، CSP شما بر رفتار برنامهتان تأثیر نمیگذارد، اما مرورگر همچنان وقتی با الگوهای ناسازگار با CSP شما مواجه میشود، خطاهای کنسول و گزارشهای تخلف ایجاد میکند، بنابراین میتوانید ببینید چه چیزی برای کاربران نهایی شما خراب میشود. برای اطلاعات بیشتر، گزارش API را ببینید. - وقتی مطمئن هستید که CSP شما سایت شما را برای کاربران نهایی شما خراب نمی کند، CSP خود را با استفاده از هدر پاسخ
Content-Security-Policy
گسترش دهید. توصیه می کنیم CSP خود را با استفاده از هدر HTTP سمت سرور تنظیم کنید زیرا از تگ<meta>
ایمن تر است. پس از تکمیل این مرحله، CSP شما شروع به محافظت از برنامه شما در برابر XSS می کند.
محدودیت ها
یک CSP سختگیرانه به طور کلی یک لایه امنیتی قوی اضافه می کند که به کاهش XSS کمک می کند. در بیشتر موارد، CSP با رد الگوهای خطرناک مانند javascript:
URI، سطح حمله را به میزان قابل توجهی کاهش می دهد. با این حال، بر اساس نوع CSP که استفاده می کنید (nonces، هش، با یا بدون 'strict-dynamic'
)، مواردی وجود دارد که CSP از برنامه شما نیز محافظت نمی کند:
- اگر یک اسکریپت ندارید، اما مستقیماً به بدنه یا پارامتر
src
آن عنصر<script>
تزریق شده است. - اگر به مکانهای اسکریپتهای ایجاد شده بهصورت پویا (
document.createElement('script')
)، از جمله به هر توابع کتابخانهای که گرههای DOMscript
بر اساس مقادیر آرگومانهایشان ایجاد میکنند، تزریق شده باشد. این شامل برخی از APIهای رایج مانند.html()
jQuery و همچنین.get()
و.post()
در jQuery < 3.0 می شود. - اگر در برنامه های قدیمی AngularJS تزریق قالب وجود دارد. مهاجمی که می تواند به قالب AngularJS تزریق کند، می تواند از آن برای اجرای جاوا اسکریپت دلخواه استفاده کند.
- اگر این خطمشی حاوی
'unsafe-eval'
باشد، تزریقهایی بهeval()
،setTimeout()
و چند API دیگر که به ندرت استفاده میشوند.
توسعه دهندگان و مهندسان امنیتی باید در طول بررسی کدها و ممیزی های امنیتی به چنین الگوهایی توجه ویژه ای داشته باشند. میتوانید جزئیات بیشتری در مورد این موارد در خطمشی امنیت محتوا بیابید: آشفتگی موفقیتآمیز بین سخت شدن و کاهش .