Running injected scripts in your web app with CSP enabled

Author: Bjørnar Hagen

Date published: 2023-08-05T11:15:00Z

Running injected scripts in your web app with CSP enabled

If you have CSP enabled in your web app you might have ran into the issue with injected scripts, like Browser Sync, getting blocked. To fix this, we can find the sha384 hash of the script, and add it to our CSP header. This way we can keep our CSP header strict, and still have the injected scripts we want working.

In this post I will show you how to do this with Laravel CSP and Browser Sync, but the same principle applies to any other web app and injected script.

  1. Install and setup “Laravel CSP” from Spatie.
  2. Find the Browser Sync injected script.
 1<script id="__bs_script__">
 2  //<![CDATA[
 3  ;(function () {
 4    try {
 5      var script = document.createElement('script')
 6      if ('async') {
 7        script.async = true
 8      }
 9      script.src = '/browser-sync/browser-sync-client.js?v=2.29.3'.replace('HOST', location.hostname)
10      if (document.body) {
11        document.body.appendChild(script)
12      } else if (document.head) {
13        document.head.appendChild(script)
14      }
15    } catch (e) {
16      console.error('Browsersync: could not append script tag', e)
17    }
18  })()
19  //]]>
20</script>
  1. Copy everything inside the script tag (even the //<![CDATA[ stuff) and paste it into a file, for example data.txt. Make sure to save it exactly as copied, don’t apply any formatting.
  2. Now run the following command cat data.txt | openssl dgst -sha384 -binary | openssl base64 -A. I got this output VJ54nlS+flZSG9OXg+tLU2fi0X9vUtpMr9KR3NuzNCwdV8HmZuVokdkiY4rFdohU. Note that you might get a % at the end of the output. That’s not part of the hash, it’s just a indicator of no empty line ending, just remove it.
  3. Add this hash to src/app/Policies/GlobalCSPPolicy.php, with sha384- prepended to it, like so:
 1namespace App\Policies;
 2
 3use Spatie\Csp\Directive;
 4use Spatie\Csp\Keyword;
 5use Spatie\Csp\Policies\Policy;
 6
 7class GlobalCSPPolicy extends Policy {
 8  public function configure()
 9  {
10    $this
11      ->addDirective(Directive::BASE, Keyword::SELF)
12      ->addDirective(Directive::SCRIPT, 'sha384-VJ54nlS+flZSG9OXg+tLU2fi0X9vUtpMr9KR3NuzNCwdV8HmZuVokdkiY4rFdohU') // Browser Sync 2.29.3
13      ->addNonceForDirective(Directive::SCRIPT)
14      ->addNonceForDirective(Directive::STYLE);
15  }
16}

That directive alone should give us a CSP header like this: content-security-policy: script-src 'sha384-VJ54nlS+flZSG9OXg+tLU2fi0X9vUtpMr9KR3NuzNCwdV8HmZuVokdkiY4rFdohU';

The complete example from above should look like this: content-security-policy: base-uri 'self';script-src 'self' 'sha384-VJ54nlS+flZSG9OXg+tLU2fi0X9vUtpMr9KR3NuzNCwdV8HmZuVokdkiY4rFdohU' 'nonce-7CADofPhUXjXBMENRcHg3kVZEiLa0L4V';style-src 'self' 'nonce-7CADofPhUXjXBMENRcHg3kVZEiLa0L4V';

With a CSP header like this, we can run injected scripts like Browser Sync, without compromising on security.

More info can be found here