کاهش اسکریپت بین سایتی (XSS) با یک خط مشی امنیتی سختگیرانه محتوا (CSP)

لوکاس ویچسلباوم
Lukas Weichselbaum

پشتیبانی مرورگر

  • کروم: 52.
  • لبه: 79.
  • فایرفاکس: 52.
  • سافاری: 15.4.

منبع

برنامه نویسی متقابل سایت (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 سختگیرانه غیر مبتنی بر چگونه کار می کند.

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 سختگیرانه، باید:

  1. تصمیم بگیرید که آیا برنامه شما باید یک CSP غیر یک یا هش تنظیم کند.
  2. CSP را از بخش ساختار Strict CSP کپی کنید و آن را به عنوان یک هدر پاسخ در برنامه خود تنظیم کنید.
  3. Refactor قالب های HTML و کد سمت سرویس گیرنده برای حذف الگوهای ناسازگار با CSP.
  4. CSP خود را مستقر کنید.

می‌توانید از Lighthouse (نسخه 7.3.0 و بالاتر با flag --preset=experimental ) در طول این فرآیند استفاده کنید تا بررسی کنید که آیا سایت شما دارای CSP است یا خیر، و آیا به اندازه کافی سختگیرانه است که در برابر XSS مؤثر باشد.

فانوس دریایی   گزارش هشداری مبنی بر اینکه هیچ CSP در حالت اجرا یافت نشد.
اگر سایت شما CSP ندارد، Lighthouse این هشدار را نشان می دهد.

مرحله 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 از حالت فقط گزارش پشتیبانی نمی‌کنند.

گزینه A: 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 در چارچوب های سمت سرور آورده شده است:

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 به آنها اجازه دهد.

گزینه B: سربرگ پاسخ 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}' .

اسکریپت های منبع را به صورت پویا بارگیری کنید

شما می توانید اسکریپت های شخص ثالث را به صورت پویا با استفاده از یک اسکریپت درون خطی بارگیری کنید.

نمونه ای از نحوه درون خط کردن اسکریپت های خود.
توسط CSP مجاز است
<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>
برای اینکه اجازه دهید این اسکریپت اجرا شود، باید هش اسکریپت درون خطی را محاسبه کرده و آن را به سرصفحه پاسخ CSP اضافه کنید و جایگزین مکان‌دار {HASHED_INLINE_SCRIPT} شود. برای کاهش تعداد هش ها، می توانید تمام اسکریپت های درون خطی را در یک اسکریپت ادغام کنید. برای مشاهده عملی آن، به این مثال و کد آن مراجعه کنید.
توسط CSP مسدود شده است
<script src="https://example.org/foo.js"></script>
<script src="https://example.org/bar.js"></script>
CSP این اسکریپت ها را مسدود می کند زیرا به صورت پویا اضافه نشده اند و هیچ ویژگی integrity ندارند که با منبع مجاز مطابقت داشته باشد.

ملاحظات بارگیری اسکریپت

مثال اسکریپت درون خطی 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 را در کنسول مشاهده کنید.

گزارش نقض CSP در کنسول برنامه‌نویس Chrome.
خطاهای کنسول برای کد مسدود شده

در بیشتر موارد، راه حل ساده است:

کنترل کننده رویداد درون خطی Refactor

توسط CSP مجاز است
<span id="things">A thing.</span>
<script nonce="${nonce}">
  document.getElementById('things').addEventListener('click', doThings);
</script>
CSP به گردانندگان رویداد اجازه می دهد که با استفاده از جاوا اسکریپت ثبت شوند.
توسط CSP مسدود شده است
<span onclick="doThings();">A thing.</span>
CSP کنترل کننده های رویداد درون خطی را مسدود می کند.

Refactor javascript: URIs

توسط CSP مجاز است
<a id="foo">foo</a>
<script nonce="${nonce}">
  document.getElementById('foo').addEventListener('click', linkClicked);
</script>
CSP به گردانندگان رویداد اجازه می دهد که با استفاده از جاوا اسکریپت ثبت شوند.
توسط CSP مسدود شده است
<a href="javascript:linkClicked()">foo</a>
CSP جاوا اسکریپت را مسدود می کند: URI ها.

eval() از جاوا اسکریپت خود حذف کنید

اگر برنامه شما از eval() برای تبدیل سریال‌سازی‌های رشته‌های JSON به اشیاء JS استفاده می‌کند، باید چنین نمونه‌هایی را به JSON.parse() تبدیل کنید، که سریع‌تر نیز است.

اگر نمی‌توانید همه استفاده‌های eval() را حذف کنید، همچنان می‌توانید یک CSP غیرمبتنی سخت تنظیم کنید، اما باید از کلمه کلیدی CSP 'unsafe-eval' استفاده کنید که باعث می‌شود خط‌مشی شما کمی ایمن‌تر شود.

شما می‌توانید این و نمونه‌های بیشتری از این نوع refactoring را در این کد CSP سخت‌گیرانه بیابید:

مرحله 4 (اختیاری): برای پشتیبانی از نسخه های قدیمی مرورگر، نسخه های جایگزین اضافه کنید

پشتیبانی مرورگر

  • کروم: 52.
  • لبه: 79.
  • فایرفاکس: 52.
  • سافاری: 15.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 خود را در مرحله مرحله بندی و سپس در محیط تولید خود مستقر کنید:

  1. (اختیاری) با استفاده از هدر Content-Security-Policy-Report-Only CSP خود را در حالت گزارش فقط اجرا کنید. حالت فقط گزارش برای آزمایش یک تغییر بالقوه شکست مانند یک CSP جدید در تولید قبل از شروع به اجرای محدودیت‌های CSP مفید است. در حالت فقط گزارش، CSP شما بر رفتار برنامه‌تان تأثیر نمی‌گذارد، اما مرورگر همچنان وقتی با الگوهای ناسازگار با CSP شما مواجه می‌شود، خطاهای کنسول و گزارش‌های تخلف ایجاد می‌کند، بنابراین می‌توانید ببینید چه چیزی برای کاربران نهایی شما خراب می‌شود. برای اطلاعات بیشتر، گزارش API را ببینید.
  2. وقتی مطمئن هستید که 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') )، از جمله به هر توابع کتابخانه‌ای که گره‌های DOM script بر اساس مقادیر آرگومان‌هایشان ایجاد می‌کنند، تزریق شده باشد. این شامل برخی از APIهای رایج مانند .html() jQuery و همچنین .get() و .post() در jQuery < 3.0 می شود.
  • اگر در برنامه های قدیمی AngularJS تزریق قالب وجود دارد. مهاجمی که می تواند به قالب AngularJS تزریق کند، می تواند از آن برای اجرای جاوا اسکریپت دلخواه استفاده کند.
  • اگر این خط‌مشی حاوی 'unsafe-eval' باشد، تزریق‌هایی به eval() ، setTimeout() و چند API دیگر که به ندرت استفاده می‌شوند.

توسعه دهندگان و مهندسان امنیتی باید در طول بررسی کدها و ممیزی های امنیتی به چنین الگوهایی توجه ویژه ای داشته باشند. می‌توانید جزئیات بیشتری در مورد این موارد در خط‌مشی امنیت محتوا بیابید: آشفتگی موفقیت‌آمیز بین سخت شدن و کاهش .

در ادامه مطلب