Strict-dynamic nonce directive breaks scripts called by other scripts in CSPv2 browsers (e.g., Safari) - safari

In browsers like Safari that (still) only support CSPv2, when I have 'strict-dynamic' and 'nonce-AAA' directives in my Content Security Policy along with all the appropriate domains, the browser will call a script loader but not execute a 2nd script called by the script loader.
For instance, if I have a CSP like:
script-src 'self' 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' 'nonce-AAA' https:;
on a site that includes googletagmanager, convertexperiments, or another script loader, the first script will execute but the second script will not, and the browser will throw a standard script-src CSP error.
I expected that Safari, etc would ignore the 'strict-dynamic' and 'nonce-' directives, but it seems to only ignore the 'strict-dynamic' directive, where the 'nonce-' directive causes script loaders to break.
Recommendations about how to craft the CSP so that it can be UA-agnostic/work with both modern browsers and CSPv2 browsers without losing 'strict-dynamic'?
Tangentially, why would a CSPv2 browser be somewhat nonce aware without being able to use it in conjunction with 'strict-dynamic'?
Please note that the question isn't about whether 'unsafe-inline' is a good idea; the example is about how permissive I'm trying to make my policy to get it to work.

For instance, if I have a CSP like: script-src 'self' 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' 'nonce-AAA' https:; on a site that includes googletagmanager,
convertexperiments, or another script loader, the first script will
execute but the second script will not, and the browser will throw a
standard script-src CSP error.
GoogleTagManager propagating 'nonce-value' into child scripts except Custom HTML tags. To allow inline scripts in Custom HTML tags you can use 'hash-source'.
The convertexperiments WP plugin can be easily modified to support 'nonce-value' (minor edits in class-convert-script.php file) or just move inline script to external file.
I expected that Safari, etc would ignore the 'strict-dynamic' and
'nonce-' directives, but it seems to only ignore the 'strict-dynamic'
directive, where the 'nonce-' directive causes script loaders to
break.
Safari does not support 'strict-dynamic', but is does support 'nonce-value'. And 'nonce-value' cancells 'unsafe-inline' therefore all inline scripts became blocked.
You have to craft CSP in browsers backward compatibility mode as Google does it (you can check the CSP header in https://www.google.com/recaptcha/api2/anchor for example). Since your instance of CSP already is in this mode, you need just to modify your code for 'nonce-value' support (+ 'hash-value' where it suitable).
Yes, Safari left no easy ways to implement CSP.

Related

Safari 13.1 refused to load Shopify embeded applications

Our application was working just fine, but recent changes in Safari caused our application to break.
Safari 13.1 starts blocking applications embedded in Shopify using an iframe.
The error it throws is:
refused to load https://xxdddddd.com/admin/auth/login because it does not appear in the frame-ancestor directive of content security policy.
We tried all sorts of content security policy and chrome and Firefox works just fine but safari always breaks,
We removed that header altogether.
We even added:
header("Content-Security-Policy: frame-ancestors * 'unsafe-inline' 'unsafe-eval' img-src * data:");
header("Sec-Fetch-Dest: iframe");
header("Sec-Fetch-Mode: navigate");
header("Sec-Fetch-Site: cross-site");
We tried all sorts of combinations, but it failed every time in Safari. I can find that several of other applications work just fine in embedded mode in Shopify and Safari 13.1, so it definitely means it is possible.
One thing I noticed is that the URL needs to be changed post authorization and in our cast it is not changing as Safari blocks, but in other applications it changes the URL in the browser. However I found nothing different in their code using view-source. I tried to replicate all headers as well. They are giving, but nothing worked,
Your CSP frame-ancestors * 'unsafe-inline' 'unsafe-eval' img-src * data: is completely wrong:
directives blocks should be comma-separated
the frame-ancestors directive does not supports 'unsafe-inline' and 'unsafe-eval' tokens,
Correct syntax CSP should look like frame-ancestors *; img-src * data:;
But the paradox is that this wrong CSP does allow frame-ancestors from any sources. After removing all unsupported sources, the effective CSP is frame-ancestors * data: in your case.
In the error:
refused to load https://xxdddddd.com/admin/auth/login because it does not appear in the frame-ancestor directive of content security policy.
confuses two things:
phrase in the frame-ancestor directive contains wrong directive name (s is omitted at the end of directive name). Is this a real Safari browser error?
the /admin/auth/ part - is this really public accessible URL? Or does this error appear in the administrator script, which could have other CSP rules?
You say in Chrome and Firefox all worked fine, but frame-ancestors * 'unsafe-inline' 'unsafe-eval' img-src * data: rules disallow images loading from any sources.
So it's possible you are editing one CSP header, but the app really have another. If you are familiar with the Safari browser console, you could check real CSP HTTP header delivered to the app's page.
Anyway, here you could test does Safari 13.1 support * in frame-ancestors (I can't do that due to Safari browser absence). It will exclude a case of a current version browser bug.

Laravel Mix VueJS production page blank

After long frustrating hours of research I finally found the cause for my Laravel7 VueJS (within blade templates) application running on nginx throwing a blank screen. Yet I lack the explanation or correct config.
When I accessed any route it would be seen for around 200ms and then switch to a white screen without any errors in npm run prod config.
Body tag would be completely empty (it looked uncommented in the inspector)
[...]
<body>
<!-- -->
</body>
[...]
Funny enough (sarcastically speaking) on page reload the login page would work normally but after accessing any other route it would revert to the behaviour described above.
After switching to npm run dev the console threw the following error:
[Vue warn]: It seems you are using the standalone build of Vue.js in an environment with Content Security Policy that prohibits unsafe-eval. The template compiler cannot work in this environment. Consider relaxing the policy to allow unsafe-eval or pre-compiling your templates into render functions.
and
EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "default-src 'self' http: https: data: blob: 'unsafe-inline'".
Which made me realize my nginx security config which I generated using the fabulous Tool provided by DigitalOcean included the following line:
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
After removing and restarting nginx everything works and looks as it does on my dev environment.
From what I get I now theoretically make my site vulnerable to XSS attacks but I do not fully understand what the option does or if it is safe to run without it with these circumstance.
The CSP header just lets your browser know what sources of JS are allowed. However, it is really a backup plan - what causes XSS is some code on your system that would render unauthorised JavaScript.
For example, the comment boxes on Stack Overflow allow me to enter the following text: <script src="http://evilserver.com/malicious.js"></script>. Stack Overflow know what they are doing, so they make sure that what is rendered is HTML, and not a literal <script> tag. Thus, you need to make sure you take the necessary care when rendering user supplied content and you will be OK (in general you do this in your output layer, not when accepting/storing input).
If you accept HTML input from users (as Stack Overflow does) then you have to be especially careful to allow only certain tags, and to ensure that any JS vectors are blocked up. So, the following text:
is actually in an <i> tag, which is rendered literally in the web app
That one is OK, since it is safe. You do need to make sure that any attributes are carefully checked, and any invalid markup is rejected. These can also be the source of security problems. This is a complex enough problem that, if you need users to be able to input HTML, you should not attempt to filter it yourself. Use a well-tested and well-regarded library instead.

Google font not loading because of content security policy

I have a progressive web app. I am using Google Fonts and I am using workbox in my service worker.
My Content Security Policy is defined as:
// Omitting all the other directives
style-src 'self' https://fonts.googleapis.com 'unsafe-inline';
font-src 'self' https://fonts.gstatic.com;
connect-src 'self';
I have set up workbox to cache the fonts by following the recipe here. The code looks like:
workbox.routing.registerRoute(
/^https:\/\/fonts\.googleapis\.com/,
new workbox.strategies.StaleWhileRevalidate({
cacheName: 'google-fonts-stylesheets',
})
);
workbox.routing.registerRoute(
/^https:\/\/fonts\.gstatic\.com/,
new workbox.strategies.CacheFirst({
cacheName: 'google-fonts-webfonts',
plugins: [
new workbox.cacheableResponse.Plugin({
statuses: [0, 200],
}),
new workbox.expiration.Plugin({
maxAgeSeconds: 60 * 60 * 24 * 365,
maxEntries: 30,
}),
],
})
);
The problem here is that when I try to load my app, in the browser (Google Chrome / Safari) or in the standalone app, the font does not load. After many hair pulling moments, Chrome finally gave me an error in the console:
Refused to connect to 'https://fonts.googleapis.com/css?family=Montserrat|Quicksand&display=swap' because it violates the following Content Security Policy directive: "connect-src 'self'".
Uncaught (in promise) no-response: no-response :: [{"url":"https://fonts.googleapis.com/css?family=Montserrat|Quicksand&display=swap","error":{}}]
at o.makeRequest (https://storage.googleapis.com/workbox-cdn/releases/4.2.0/workbox-strategies.prod.js:1:3983)
GET https://fonts.googleapis.com/css?family=Montserrat|Quicksand&display=swap net::ERR_FAILED
It appears that I need to declare google fonts under connect-src too. I did not see that mentioned anywhere (and I googled a lot) so I wanted to know if this is a bug or I indeed need to define the font in the connect-src CSP directive?
connect-src 'fonts.googleapis.com' would be required, notwithstanding your current Content-Security-Policy. If I may answer this with additional material that you did not specifically request:
The purpose of CSP is Security; having unsafe-inline set for style-src is not secure. From MDN on the page dedicated to style-src - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src :
Note: Disallowing inline styles and inline scripts is one of the biggest security wins CSP provides. However, if you absolutely have to use it, there are a few mechanisms that will allow them.
Focusing on this alone, Google’s fonts don’t play well with SRI (subresource integrity) which would solve the security issue. A better option if security is something to which one needs to pay respect would be using a secondary server strictly for your font (unless you choose to implement SRI-friendly web fonts loaded from, e.g., CDNJS). This would allow you to implement hashing with google fonts, just be sure to have the proper CORS settings between SERVER and font server. I’d also highly recommend locking down your default-src to 'none' and thereafter define each following fetch directive as detailed by MDN here: https://developer.mozilla.org/en-US/docs/Glossary/Fetch_directive , just be sure not to use unsafe-inline in script-src OR style-src and avoid unsafe-eval as well. frame-ancestors 'none', upgrade-insecure-requests (as well as block-all-mixed-content for anyone using I.E. or Edge) and if you do decide on implementing SRI, require-sri-for script style.
I hope I haven’t overstated my answer and that, ofc, it helps you.

Content Security Policy: allowing all external images?

I'd like to allow scripts only from my local server with certain exceptions like jQuery etc., but be flexible to load external images.
I'm aware that there is a directive like
Content-Security-Policy: script-src 'self' https://apis.google.com; img-src 'self' https://www.flickr.com;
to allow images from both, my own webserver and Flickr, but is it possible to allow images from all sources - or would this violate the whole concept of CSP and thus be impossible? I'm maintaining a blog often requiring to embed external images, so it basically comes up to a decision on whether it makes sense and is manageable to add CSP to my website or not.
Including images from all sources is a mostly safe practice in terms of security, but you may not like the content of the images that can be used.
To allow all images, use:
img-src * data:;
It's probably reasonable to limit this to https: sources so your users don't get a mixed content (broken lock) error:
img-src https: data:;
In either case, be sure to send X-Content-Type-Options: nosniff" to prevent content type sniffing that happens in Chrome/IE. I'm not sure if firefox will treat an image tag that points to a javascript file will treat that as Javascript due to sniffing, but your script-src should prevent that from being terrible. I'm not sure if apis.google.com hosts user scripts or if it's limited to typical open source libraries.

CSP nonce ignored by Safari

So I implemented CSP for my web app and it works perfectly fine in Chrome. All inline scripts with nonce are executed; and the ones without it are not executed.
In Safari however, this is the message I see in the console:
The source list for Content Security Policy directive 'script-src'
contains an invalid source:
''nonce-fbe23fb21d40c38e8df7c0a16357dd3ec4be86ca233cb41206ac5f897cf9a103''.
It will be ignored.
Header:
Content-Security-Policy script-src 'nonce-cb28e5c8a2b833169bb8d1fa686f659fed9b3bf8ea52b86916bcaf20a04b3209' 'self'
None of the inline scripts are executed , even the ones with nonce.
Safari does not yet support nonces (please bug your local webkit representative to support this) but Firefox and Chrome have implemented the standard behavior which is backwards compatible. Namely, if a nonce is present then 'unsafe-inline' is ignored.
Send both 'unsafe-inline' and your nonce and you will get the desired behavior. Safari will complain about the "unknown source value" but it will work as intended.
See http://www.w3.org/TR/CSP2/#directive-script-src