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

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

Browser Support

  • کروم: ۵۲.
  • لبه: ۷۹.
  • فایرفاکس: ۵۲.
  • سافاری: ۱۵.۴.

اسکریپت نویسی بین سایتی (XSS) ، توانایی تزریق اسکریپت‌های مخرب به یک برنامه وب، بیش از یک دهه است که یکی از بزرگترین آسیب‌پذیری‌های امنیتی وب بوده است.

سیاست امنیت محتوا (CSP) یک لایه امنیتی اضافه شده است که به کاهش XSS کمک می‌کند. برای پیکربندی CSP، هدر HTTP Content-Security-Policy را به یک صفحه وب اضافه کنید و مقادیری را تنظیم کنید که منابعی را که عامل کاربر می‌تواند برای آن صفحه بارگیری کند، کنترل می‌کنند.

این صفحه نحوه استفاده از یک CSP مبتنی بر nonces یا hashها را برای کاهش XSS توضیح می‌دهد، به جای CSPهای رایج مبتنی بر host-allowlist که اغلب صفحه را در معرض XSS قرار می‌دهند زیرا در اکثر پیکربندی‌ها می‌توان آنها را دور زد .

اصطلاح کلیدی: نانس یک عدد تصادفی است که فقط یک بار استفاده می‌شود و می‌توانید از آن برای علامت‌گذاری یک تگ <script> به عنوان تگ قابل اعتماد استفاده کنید.

اصطلاح کلیدی: تابع هش یک تابع ریاضی است که یک مقدار ورودی را به یک مقدار عددی فشرده به نام هش تبدیل می‌کند. می‌توانید از یک هش (مثلاً SHA-256 ) برای علامت‌گذاری یک تگ <script> درون‌خطی به عنوان تگ قابل اعتماد استفاده کنید.

یک سیاست امنیتی محتوا که بر اساس nonceها یا هش‌ها باشد، اغلب یک CSP سختگیرانه نامیده می‌شود. هنگامی که یک برنامه از CSP سختگیرانه استفاده می‌کند، مهاجمانی که نقص‌های تزریق HTML را پیدا می‌کنند، عموماً نمی‌توانند از آنها برای مجبور کردن مرورگر به اجرای اسکریپت‌های مخرب در یک سند آسیب‌پذیر استفاده کنند. دلیل این امر آن است که CSP سختگیرانه فقط به اسکریپت‌های هش شده یا اسکریپت‌هایی با مقدار nonce صحیح تولید شده در سرور اجازه می‌دهد، بنابراین مهاجمان نمی‌توانند اسکریپت را بدون دانستن nonce صحیح برای یک پاسخ مشخص اجرا کنند.

چرا باید از یک CSP دقیق استفاده کنید؟

اگر سایت شما از قبل CSP ای شبیه script-src www.googleapis.com دارد، احتمالاً در برابر حملات بین سایتی (cross-site) مؤثر نیست. این نوع CSP، CSP با لیست مجاز (allowlist) نامیده می‌شود. آنها به سفارشی‌سازی زیادی نیاز دارند و مهاجمان می‌توانند از آنها عبور کنند .

CSP های دقیق مبتنی بر نانس‌های رمزنگاری یا هش‌ها از این مشکلات جلوگیری می‌کنند.

ساختار دقیق CSP

یک سیاست امنیتی محتوای سختگیرانه و اساسی از یکی از هدرهای پاسخ HTTP زیر استفاده می‌کند:

CSP دقیق مبتنی بر Nonce

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none&#39;;
  base-uri 'none';
نحوه عملکرد یک CSP دقیق مبتنی بر nonce.

CSP دقیق مبتنی بر هش

Content-Security-Policy:
  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none&#39;;
  base-uri 'none';

ویژگی‌های زیر، یک CSP مانند این را "دقیق" و بنابراین ایمن می‌کند:

  • این اسکریپت از nonces 'nonce-{RANDOM}' یا hashes 'sha256-{HASHED_INLINE_SCRIPT}' برای نشان دادن تگ‌های <script> که توسعه‌دهنده سایت برای اجرا در مرورگر کاربر به آنها اعتماد دارد، استفاده می‌کند.
  • این قابلیت 'strict-dynamic' را تنظیم می‌کند تا با اجازه دادن خودکار به اجرای اسکریپت‌هایی که یک اسکریپت مورد اعتماد ایجاد می‌کند، زحمت پیاده‌سازی یک CSP مبتنی بر nonce یا hash را کاهش دهد. این قابلیت همچنین استفاده از اکثر کتابخانه‌ها و ویجت‌های جاوا اسکریپت شخص ثالث را از حالت مسدود خارج می‌کند.
  • این مبتنی بر لیست‌های مجاز URL نیست، بنابراین از دور زدن‌های رایج CSP رنج نمی‌برد.
  • این اسکریپت‌های درون‌خطی غیرقابل اعتماد مانند کنترل‌کننده‌های رویداد درون‌خطی یا javascript: URIها را مسدود می‌کند.
  • این ویژگی object-src را محدود می‌کند تا افزونه‌های خطرناکی مانند فلش را غیرفعال کند.
  • این ابزار base-uri محدود می‌کند تا تزریق تگ‌های <base> را مسدود کند. این کار مانع از تغییر مکان اسکریپت‌های بارگذاری شده از URLهای نسبی توسط مهاجمان می‌شود.

یک CSP سختگیرانه اتخاذ کنید

برای اتخاذ یک CSP دقیق، باید:

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

شما می‌توانید در طول این فرآیند از ممیزی بهترین شیوه‌های Lighthouse (نسخه ۷.۳.۰ و بالاتر با پرچم --preset=experimental ) استفاده کنید تا بررسی کنید که آیا سایت شما CSP دارد یا خیر، و آیا به اندازه کافی دقیق است که در برابر XSS مؤثر باشد یا خیر.

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

مرحله ۱: تصمیم بگیرید که آیا به یک CSP مبتنی بر نانس یا هش نیاز دارید

در اینجا نحوه عملکرد دو نوع CSP سختگیرانه آورده شده است:

CSP مبتنی بر نانس

با یک CSP مبتنی بر nonce، شما یک عدد تصادفی در زمان اجرا تولید می‌کنید، آن را در CSP خود قرار می‌دهید و آن را با هر تگ اسکریپت در صفحه خود مرتبط می‌کنید. یک مهاجم نمی‌تواند یک اسکریپت مخرب را در صفحه شما قرار دهد یا اجرا کند، زیرا باید عدد تصادفی صحیح را برای آن اسکریپت حدس بزند. این فقط در صورتی کار می‌کند که عدد قابل حدس زدن نباشد و برای هر پاسخ به تازگی در زمان اجرا تولید شود.

برای صفحات HTML رندر شده روی سرور، از یک CSP مبتنی بر nonce استفاده کنید. برای این صفحات، می‌توانید برای هر پاسخ، یک عدد تصادفی جدید ایجاد کنید.

CSP مبتنی بر هش

برای یک CSP مبتنی بر هش، هش هر تگ اسکریپت درون‌خطی به CSP اضافه می‌شود. هر اسکریپت یک هش متفاوت دارد. یک مهاجم نمی‌تواند یک اسکریپت مخرب را در صفحه شما قرار دهد یا اجرا کند، زیرا هش آن اسکریپت برای اجرا باید در CSP شما باشد.

از یک CSP مبتنی بر هش برای صفحات HTML که به صورت ایستا ارائه می‌شوند یا صفحاتی که نیاز به ذخیره شدن در حافظه پنهان دارند، استفاده کنید. به عنوان مثال، می‌توانید از یک CSP مبتنی بر هش برای برنامه‌های وب تک صفحه‌ای ساخته شده با چارچوب‌هایی مانند Angular، React یا موارد دیگر که به صورت ایستا و بدون رندر سمت سرور ارائه می‌شوند، استفاده کنید.

مرحله ۲: یک CSP دقیق تنظیم کنید و اسکریپت‌های خود را آماده کنید

هنگام تنظیم CSP، چند گزینه دارید:

  • حالت فقط گزارش ( Content-Security-Policy-Report-Only ) یا حالت اجرا ( Content-Security-Policy ). در حالت فقط گزارش، CSP هنوز منابع را مسدود نمی‌کند، بنابراین هیچ چیزی در سایت شما خراب نمی‌شود، اما می‌توانید خطاها را ببینید و برای هر چیزی که می‌توانست مسدود شود، گزارش دریافت کنید. به صورت محلی، وقتی CSP خود را تنظیم می‌کنید، این موضوع واقعاً مهم نیست، زیرا هر دو حالت خطاها را در کنسول مرورگر به شما نشان می‌دهند. در هر صورت، حالت اجرا می‌تواند به شما در یافتن منابعی که CSP پیش‌نویس شما مسدود می‌کند، کمک کند، زیرا مسدود کردن یک منبع می‌تواند باعث شود صفحه شما خراب به نظر برسد. حالت فقط گزارش در مراحل بعدی فرآیند مفیدتر می‌شود (به مرحله 5 مراجعه کنید).
  • تگ <meta> هدر یا HTML. برای توسعه محلی، تگ <meta> می‌تواند برای تنظیم CSP شما و مشاهده سریع تأثیر آن بر سایت شما، راحت‌تر باشد. با این حال:
    • بعداً، هنگام استقرار CSP خود در محیط عملیاتی، توصیه می‌کنیم آن را به عنوان یک هدر HTTP تنظیم کنید.
    • اگر می‌خواهید CSP خود را در حالت فقط گزارش تنظیم کنید، باید آن را به عنوان سرصفحه تنظیم کنید، زیرا متا تگ‌های CSP از حالت فقط گزارش پشتیبانی نمی‌کنند.

گزینه الف: CSP مبتنی بر Nonce

هدر پاسخ HTTP مربوط به Content-Security-Policy را در برنامه خود تنظیم کنید:

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

یک nonce برای CSP ایجاد کنید

یک nonce یک عدد تصادفی است که فقط یک بار در هر بار بارگذاری صفحه استفاده می‌شود. یک CSP مبتنی بر nonce تنها در صورتی می‌تواند XSS را کاهش دهد که مهاجمان نتوانند مقدار nonce را حدس بزنند. یک nonce CSP باید:

  • یک مقدار تصادفی قوی از نظر رمزنگاری (در حالت ایده‌آل، طول آن ۱۲۸+ بیت باشد)
  • برای هر پاسخ، به تازگی تولید شده است
  • کدگذاری شده با 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(&>quot;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 به آنها اجازه دهد.

گزینه ب: هدر پاسخ 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.hea<d.appen>dChild(s);
  });
/script
برای اجرای این اسکریپت، باید هش اسکریپت درون‌خطی را محاسبه کرده و آن را به هدر پاسخ CSP اضافه کنید و جایگزین جای‌نگهدار {HASHED_INLINE_SCRIPT} کنید. برای کاهش تعداد هش‌ها، می‌توانید تمام اسکریپت‌های درون‌خطی را در یک اسکریپت واحد ادغام کنید. برای مشاهده این مورد در عمل، به این مثال و کد آن مراجعه کنید.
مسدود شده توسط CSP
<script src="https://example.org/fo><o.js&qu>o<t;/script
script src="https://exam><ple.org>/bar.js"/script
CSP این اسکریپت‌ها را مسدود می‌کند زیرا به صورت پویا اضافه نشده‌اند و هیچ ویژگی integrity ندارند که با یک منبع مجاز مطابقت داشته باشد.

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

مثال اسکریپت درون‌خطی s.async = false را اضافه می‌کند تا اطمینان حاصل شود که foo قبل از bar اجرا می‌شود، حتی اگر bar ابتدا بارگیری شود. در این قطعه کد، s.async = false تجزیه‌گر را در حین بارگیری اسکریپت‌ها مسدود نمی‌کند، زیرا اسکریپت‌ها به صورت پویا اضافه می‌شوند. تجزیه‌گر فقط در حین اجرای اسکریپت‌ها متوقف می‌شود، همانطور که برای اسکریپت‌های async اتفاق می‌افتد. با این حال، در مورد این قطعه کد، به خاطر داشته باشید:

  • ممکن است یک یا هر دو اسکریپت قبل از اتمام دانلود سند اجرا شوند. اگر می‌خواهید سند تا زمان اجرای اسکریپت‌ها آماده باشد، قبل از اضافه کردن اسکریپت‌ها منتظر رویداد DOMContentLoaded باشید. اگر این امر باعث ایجاد مشکل در عملکرد می‌شود زیرا اسکریپت‌ها به موقع شروع به دانلود نمی‌کنند، از تگ‌های preload در صفحه زودتر استفاده کنید.
  • defer = true هیچ کاری انجام نمی‌دهد. اگر به این رفتار نیاز دارید، اسکریپت را در صورت نیاز به صورت دستی اجرا کنید.

مرحله ۳: بازسازی قالب‌های HTML و کد سمت کلاینت

می‌توان از کنترل‌کننده‌های رویداد درون‌خطی (مانند onclick="…" ، onerror="…" ) و URIهای جاوااسکریپت ( <a href="javascript:…"> ) برای اجرای اسکریپت‌ها استفاده کرد. این بدان معناست که مهاجمی که یک باگ XSS پیدا می‌کند، می‌تواند این نوع HTML را تزریق کرده و جاوااسکریپت مخرب را اجرا کند. یک CSP مبتنی بر nonce یا hash استفاده از این نوع نشانه‌گذاری را ممنوع می‌کند. اگر سایت شما از هر یک از این الگوها استفاده می‌کند، باید آنها را به جایگزین‌های امن‌تری تبدیل کنید.

اگر CSP را در مرحله قبل فعال کرده باشید، می‌توانید هر بار که CSP یک الگوی ناسازگار را مسدود می‌کند، تخلفات CSP را در کنسول مشاهده کنید.

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

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

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

مجاز توسط CSP
<span id="th>ings&quo<t;A t>h<ing./span
script nonce=>"${nonce}"
  document.getElementById('things').addEventL<istener>('click', doThings);
/script
CSP به کنترل‌کننده‌های رویدادی که با استفاده از جاوااسکریپت ثبت شده‌اند، اجازه می‌دهد.
مسدود شده توسط CSP
<span onclick="doThing>s();&quo<t;A t>hing./span
CSP کنترل‌کننده‌های رویداد درون‌خطی را مسدود می‌کند.

اصلاح javascript: URI ها

مجاز توسط CSP
<a id=">;fo<o&>q<uot;foo/a
script nonce=>"${nonce}"
  document.getElementById('foo').addEventList<ener(&#>39;click', linkClicked);
/script
CSP به کنترل‌کننده‌های رویدادی که با استفاده از جاوااسکریپت ثبت شده‌اند، اجازه می‌دهد.
مسدود شده توسط CSP
<a href="javascript:linkClick>ed(<)&>quot;foo/a
بلوک‌های CSP جاوا اسکریپت: URIها.

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

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

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

می‌توانید این و نمونه‌های بیشتری از چنین بازسازی‌هایی را در این آزمایشگاه کد CSP دقیق پیدا کنید:

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

Browser Support

  • کروم: ۵۲.
  • لبه: ۷۹.
  • فایرفاکس: ۵۲.
  • سافاری: ۱۵.۴.

اگر نیاز به پشتیبانی از نسخه‌های قدیمی‌تر مرورگر دارید:

  • استفاده از strict-dynamic مستلزم اضافه کردن https: به عنوان یک جایگزین برای نسخه‌های قبلی سافاری است. وقتی این کار را انجام می‌دهید:
    • همه مرورگرهایی که strict-dynamic پشتیبانی می‌کنند https: fallback را نادیده می‌گیرند، بنابراین این موضوع از قدرت این سیاست نمی‌کاهد.
    • در مرورگرهای قدیمی، اسکریپت‌های خارجی فقط در صورتی می‌توانند بارگذاری شوند که از مبدا HTTPS باشند. این روش نسبت به CSP سختگیرانه امنیت کمتری دارد، اما همچنان از برخی از دلایل رایج XSS مانند تزریق جاوا اسکریپت به javascript: URI) جلوگیری می‌کند.
  • برای اطمینان از سازگاری با نسخه‌های بسیار قدیمی مرورگر (۴+ سال)، می‌توانید unsafe-inline به عنوان یک جایگزین اضافه کنید. همه مرورگرهای جدید در صورت وجود یک CSP nonce یا hash unsafe-inline را نادیده می‌گیرند.
Content-Security-Policy:
  script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
  object-src 9;none';
  base-uri 'none';

مرحله ۵: CSP خود را مستقر کنید

پس از تأیید اینکه CSP شما هیچ اسکریپت قانونی را در محیط توسعه محلی شما مسدود نمی‌کند، می‌توانید CSP خود را ابتدا در محیط آزمایشی و سپس در محیط عملیاتی خود مستقر کنید:

  1. (اختیاری) CSP خود را در حالت فقط گزارش با استفاده از هدر Content-Security-Policy-Report-Only مستقر کنید. حالت فقط گزارش برای آزمایش یک تغییر بالقوه مخرب مانند یک CSP جدید در محیط عملیاتی، قبل از شروع اعمال محدودیت‌های CSP، مفید است. در حالت فقط گزارش، CSP شما بر رفتار برنامه شما تأثیری نمی‌گذارد، اما مرورگر همچنان هنگام مواجهه با الگوهای ناسازگار با CSP شما، خطاهای کنسول و گزارش‌های تخلف را تولید می‌کند، بنابراین می‌توانید ببینید چه چیزی برای کاربران نهایی شما مشکل ایجاد می‌کرد. برای اطلاعات بیشتر، به Reporting API مراجعه کنید.
  2. وقتی مطمئن شدید که CSP شما سایت شما را برای کاربران نهایی خراب نمی‌کند، CSP خود را با استفاده از هدر پاسخ Content-Security-Policy مستقر کنید. توصیه می‌کنیم CSP خود را با استفاده از یک هدر HTTP سمت سرور تنظیم کنید زیرا از یک تگ <meta> امن‌تر است. پس از تکمیل این مرحله، CSP شما شروع به محافظت از برنامه شما در برابر XSS می‌کند.

محدودیت‌ها

یک CSP سختگیرانه عموماً یک لایه امنیتی قوی اضافه شده فراهم می‌کند که به کاهش XSS کمک می‌کند. در بیشتر موارد، CSP با رد الگوهای خطرناکی مانند javascript: URIها، سطح حمله را به میزان قابل توجهی کاهش می‌دهد. با این حال، بر اساس نوع CSP که استفاده می‌کنید (nonces، hashها، با یا بدون '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 دیگر که به ندرت استفاده می‌شوند، انجام می‌شود.

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

مطالعه بیشتر