{"id":1832,"date":"2025-06-23T13:50:28","date_gmt":"2025-06-23T13:50:28","guid":{"rendered":"https:\/\/shreyapohekar.com\/blogs\/?p=1832"},"modified":"2025-06-23T13:54:32","modified_gmt":"2025-06-23T13:54:32","slug":"csp-part-2-securing-inline-scripts-with-nonces-and-hashes","status":"publish","type":"post","link":"https:\/\/shreyapohekar.com\/blogs\/csp-part-2-securing-inline-scripts-with-nonces-and-hashes\/","title":{"rendered":"CSP Part 2: Securing Inline Scripts with Nonces and Hashes"},"content":{"rendered":"\n<p>In <a href=\"https:\/\/shreyapohekar.com\/blogs\/content-security-policy-csp-a-key-mitigation-for-xss-and-clickjacking\/\">Part 1 of the CSP series<\/a>, we explored how CSP plays a major role in mitigating XSS and clickjacking attacks. Now that you&#8217;re familiar with the basics of setting up a CSP and its importance, let&#8217;s take it one step further.<\/p>\n\n\n\n<p>Today, we&#8217;ll dive into two powerful CSP techniques: <strong>nonces<\/strong> and <strong>hashes<\/strong>. These allow us to safely run specific inline scripts while still blocking potentially malicious ones \u2014 all without compromising security.<\/p>\n\n\n\n<p>Let&#8217;s get into it! <\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Problem with <code>'unsafe-inline'<\/code><\/h2>\n\n\n\n<p>We all love a quick fix, and <code>'unsafe-inline'<\/code> might seem like one. It lets your inline scripts run without much effort. But here\u2019s the catch:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: script-src 'self' 'unsafe-inline';<\/code><\/pre>\n\n\n\n<p>By allowing <code>'unsafe-inline'<\/code>, you&#8217;re letting <strong>any inline script<\/strong> execute, including those that attackers may inject using XSS.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>This completely defeats the purpose of CSP.<\/p>\n<\/blockquote>\n\n\n\n<p>Instead, we should use <strong>nonces<\/strong> or <strong>hashes<\/strong> to allow only those scripts that <strong>we explicitly trust<\/strong>.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Extras: Inline scripts<\/h3>\n\n\n\n<p>In case you are wondering what exactly are inline scripts, this section is for you.<\/p>\n\n\n\n<p><strong>Inline scripts<\/strong> are pieces of JavaScript code that are <strong>written directly inside your HTML file<\/strong>, within a <code>&lt;script&gt;<\/code> tag, instead of being linked from an external <code>.js<\/code> file.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Example of an Inline Script:<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">htmlCopyEdit<code>&lt;!DOCTYPE html&gt;\n&lt;html&gt;\n&lt;head&gt;\n  &lt;title&gt;Inline Script Example&lt;\/title&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;h1&gt;Hello, User!&lt;\/h1&gt;\n  \n  &lt;script&gt;\n    console.log('This is an inline script');\n  &lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/code><\/pre>\n\n\n\n<p>This JavaScript code is <em>inline<\/em> because it\u2019s embedded right inside the HTML document.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Using CSP Nonces<\/h2>\n\n\n\n<p>A <strong>nonce (number used once)<\/strong> is a randomly generated value that you assign to each inline script. This approach allows specific inline scripts to execute while blocking others, enhancing security.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><em>Implementation Steps:<\/em><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Generate a Unique Nonce:<\/strong> For each HTTP response, create a cryptographically secure random nonce.<\/li>\n\n\n\n<li><strong>Apply the Nonce to Inline Scripts:<\/strong> Include the nonce as an attribute in your inline <code>&lt;script&gt;<\/code> tags.<\/li>\n\n\n\n<li><strong>Set the CSP Header with the Nonce:<\/strong> Configure your server to include the nonce in the <code>Content-Security-Policy<\/code> header.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><em>Example:<\/em><\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">1. <strong>Generating a Nonce in Express (Node.js):<\/strong><\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>const crypto = require('crypto');\nconst app = require('express')();\n\napp.use((req, res, next) =&gt; {\n  \/\/ Generate a 16-byte random nonce\n  res.locals.nonce = crypto.randomBytes(16).toString('base64');\n  next();\n});\n\napp.get('\/', (req, res) =&gt; {\n  \/\/ Set the CSP header with the generated nonce\n  res.setHeader(\n    \"Content-Security-Policy\",\n    `script-src 'self' 'nonce-${res.locals.nonce}';`\n  );\n  \/\/ Render your HTML with the nonce applied to inline scripts\n  res.send(`\n    &lt;!DOCTYPE html&gt;\n    &lt;html&gt;\n      &lt;head&gt;\n        &lt;title&gt;Secure Page&lt;\/title&gt;\n      &lt;\/head&gt;\n      &lt;body&gt;\n        &lt;script nonce=\"${res.locals.nonce}\"&gt;\n          \/\/ Your inline script here\n          console.log('Secure inline script executed.');\n        &lt;\/script&gt;\n      &lt;\/body&gt;\n    &lt;\/html&gt;\n  `);\n});\n\napp.listen(3000, () =&gt; {\n  console.log('Server running on http:\/\/localhost:3000');\n});<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">2. <strong>CSP Header Configuration:<\/strong><\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: script-src 'self' 'nonce-randomlyGeneratedNonce';<\/code><\/pre>\n\n\n\n<p>Replace <code>'randomlyGeneratedNonce'<\/code> with the actual nonce value generated for each request.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Benefits of Using Nonces:<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Granular Control:<\/strong> Only scripts with the correct nonce can execute, reducing the risk of malicious code running on your site.<\/li>\n\n\n\n<li><strong>Flexibility:<\/strong> Allows necessary inline scripts to function without broadly enabling all inline code.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Key Considerations:<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Do Not Reuse Nonces:<\/strong> Each HTTP response must have a unique nonce to maintain security integrity.<\/li>\n\n\n\n<li><strong>Avoid Static Nonces:<\/strong> Using a static nonce value across multiple responses defeats the purpose of nonce-based CSP and can expose your application to attacks. (<a href=\"https:\/\/learn.microsoft.com\/en-us\/answers\/questions\/2116893\/add-support-for-dynamic-csp-nonces-to-enhance-secu?utm_source=chatgpt.com\">Microsoft Learn<\/a>)<\/li>\n\n\n\n<li><strong>Secure Generation:<\/strong> Utilize a reliable, cryptographically secure method to generate nonces, ensuring they are unpredictable and unique.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Hashes Instead of Nonces<\/h2>\n\n\n\n<p>If you&#8217;d rather not generate dynamic nonces, another secure method to allow specific inline scripts is <strong>using cryptographic hashes<\/strong>.<\/p>\n\n\n\n<p>Instead of enabling all inline scripts using <code>'unsafe-inline'<\/code> (which is insecure), you compute a hash of the exact script content and include it in your CSP header. When the browser loads the page, it compares the script\u2019s hash with the one in the policy \u2014 and <strong>executes it only if they match exactly<\/strong>.<\/p>\n\n\n\n<p>This method ensures that <strong>only the intended inline code<\/strong> runs, even if attackers inject malicious scripts elsewhere.<\/p>\n\n\n\n<p>Following are the steps on how to compute a hash of an inline script<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>1. Identify the Inline Script:<\/strong><\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;script&gt;\n  alert('Hello, world!');\n&lt;\/script&gt;<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>2. Compute the Script&#8217;s Hash:<\/strong><\/h3>\n\n\n\n<p>You can use one of two methods:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Using OpenSSL (Command Line):<\/strong><\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>echo -n \"alert('Hello, world!');\" | openssl dgst -sha256 -binary | openssl base64<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Using Browser Dev Tools:<\/strong>\n<ul class=\"wp-block-list\">\n<li>Apply a temporary CSP without <code>'unsafe-inline'<\/code><\/li>\n\n\n\n<li>Let the browser block the script<\/li>\n\n\n\n<li>Read the console message for the hash<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: script-src 'self';<\/code><\/pre>\n\n\n\n<p>You&#8217;ll see an error message like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Refused to execute inline script because it violates the following Content Security Policy directive: \"script-src 'self'\". Either the 'unsafe-inline' keyword, a hash ('sha256-abc123...'), or a nonce ('nonce-...') is required to enable inline execution.<\/code><\/pre>\n\n\n\n<p>Use the <code>sha256-abc123...<\/code> part in your CSP header.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>3. Update Your CSP Header:<\/strong><\/h3>\n\n\n\n<p>Update your configuration to have a sha of the script you just calculated and now the script will run without any errors<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: script-src 'self' 'sha256-abc123...';<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Important Considerations:<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Exact Match:<\/strong> The hash must correspond precisely to the script&#8217;s content. Any change will require a new hash.<\/li>\n\n\n\n<li><strong>Supported Algorithms:<\/strong> CSP supports <code>sha256<\/code>, <code>sha384<\/code>, and <code>sha512<\/code>.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Final Thoughts<\/h2>\n\n\n\n<p>Both <strong>nonces<\/strong> and <strong>hashes<\/strong> are powerful tools that help maintain the security of your web applications without disabling all inline functionality.<\/p>\n\n\n\n<p>Avoiding <code>'unsafe-inline'<\/code> and switching to these mechanisms ensures that only <strong>explicitly trusted scripts<\/strong> can run, which significantly strengthens your defense against XSS.<\/p>\n\n\n\n<p>Choose <strong>nonces<\/strong> if you&#8217;re okay with generating them dynamically.  Choose <strong>hashes<\/strong> if your scripts are static and don\u2019t change often.<\/p>\n\n\n\n<p>In either case, you&#8217;re staying true to CSP\u2019s mission: <strong>Stop bad scripts. Let good ones run.<\/strong><\/p>\n\n\n\n<p>Thats all for this blog post. I hope you enjoyed reading it! See you in the next one. Until then, happy defending!! \ud83d\ude42<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In Part 1 of the CSP series, we explored how CSP plays a major role in mitigating XSS and clickjacking attacks. Now that you&#8217;re familiar with the basics of setting up a CSP and its importance, let&#8217;s take it one step further. Today, we&#8217;ll dive into two powerful CSP techniques: nonces and hashes. These allow [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1836,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ocean_post_layout":"","ocean_both_sidebars_style":"","ocean_both_sidebars_content_width":0,"ocean_both_sidebars_sidebars_width":0,"ocean_sidebar":"","ocean_second_sidebar":"","ocean_disable_margins":"enable","ocean_add_body_class":"","ocean_shortcode_before_top_bar":"","ocean_shortcode_after_top_bar":"","ocean_shortcode_before_header":"","ocean_shortcode_after_header":"","ocean_has_shortcode":"","ocean_shortcode_after_title":"","ocean_shortcode_before_footer_widgets":"","ocean_shortcode_after_footer_widgets":"","ocean_shortcode_before_footer_bottom":"","ocean_shortcode_after_footer_bottom":"","ocean_display_top_bar":"default","ocean_display_header":"default","ocean_header_style":"","ocean_center_header_left_menu":"","ocean_custom_header_template":"","ocean_custom_logo":0,"ocean_custom_retina_logo":0,"ocean_custom_logo_max_width":0,"ocean_custom_logo_tablet_max_width":0,"ocean_custom_logo_mobile_max_width":0,"ocean_custom_logo_max_height":0,"ocean_custom_logo_tablet_max_height":0,"ocean_custom_logo_mobile_max_height":0,"ocean_header_custom_menu":"","ocean_menu_typo_font_family":"","ocean_menu_typo_font_subset":"","ocean_menu_typo_font_size":0,"ocean_menu_typo_font_size_tablet":0,"ocean_menu_typo_font_size_mobile":0,"ocean_menu_typo_font_size_unit":"px","ocean_menu_typo_font_weight":"","ocean_menu_typo_font_weight_tablet":"","ocean_menu_typo_font_weight_mobile":"","ocean_menu_typo_transform":"","ocean_menu_typo_transform_tablet":"","ocean_menu_typo_transform_mobile":"","ocean_menu_typo_line_height":0,"ocean_menu_typo_line_height_tablet":0,"ocean_menu_typo_line_height_mobile":0,"ocean_menu_typo_line_height_unit":"","ocean_menu_typo_spacing":0,"ocean_menu_typo_spacing_tablet":0,"ocean_menu_typo_spacing_mobile":0,"ocean_menu_typo_spacing_unit":"","ocean_menu_link_color":"","ocean_menu_link_color_hover":"","ocean_menu_link_color_active":"","ocean_menu_link_background":"","ocean_menu_link_hover_background":"","ocean_menu_link_active_background":"","ocean_menu_social_links_bg":"","ocean_menu_social_hover_links_bg":"","ocean_menu_social_links_color":"","ocean_menu_social_hover_links_color":"","ocean_disable_title":"default","ocean_disable_heading":"default","ocean_post_title":"","ocean_post_subheading":"","ocean_post_title_style":"","ocean_post_title_background_color":"","ocean_post_title_background":0,"ocean_post_title_bg_image_position":"","ocean_post_title_bg_image_attachment":"","ocean_post_title_bg_image_repeat":"","ocean_post_title_bg_image_size":"","ocean_post_title_height":0,"ocean_post_title_bg_overlay":0.5,"ocean_post_title_bg_overlay_color":"","ocean_disable_breadcrumbs":"default","ocean_breadcrumbs_color":"","ocean_breadcrumbs_separator_color":"","ocean_breadcrumbs_links_color":"","ocean_breadcrumbs_links_hover_color":"","ocean_display_footer_widgets":"default","ocean_display_footer_bottom":"default","ocean_custom_footer_template":"","ocean_post_oembed":"","ocean_post_self_hosted_media":"","ocean_post_video_embed":"","ocean_link_format":"","ocean_link_format_target":"self","ocean_quote_format":"","ocean_quote_format_link":"post","ocean_gallery_link_images":"on","ocean_gallery_id":[],"footnotes":""},"categories":[2,440,321,257,330],"tags":[446,449,450,448,451],"class_list":["post-1832","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-information-security","category-mitigations","category-owasp-top-10","category-web-application","category-xss","tag-csp","tag-hashes","tag-inline-scripts","tag-nonces","tag-unsafe-inline","entry","has-media"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/posts\/1832"}],"collection":[{"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/comments?post=1832"}],"version-history":[{"count":2,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/posts\/1832\/revisions"}],"predecessor-version":[{"id":1835,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/posts\/1832\/revisions\/1835"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/media\/1836"}],"wp:attachment":[{"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/media?parent=1832"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/categories?post=1832"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shreyapohekar.com\/blogs\/wp-json\/wp\/v2\/tags?post=1832"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}