Google Analytics and Content-Security-Policy header - google-analytics

The Content-Security-Policy HTTP header is meant to block inline script and resources from untrusted servers. However, the sample Google Analytics code snippet depends on both. What are the best practices in this area?
This is the Content-Security-Policy header that I'm currently using:
default-src 'self'; script-src 'self' https://ssl.google-analytics.com; img-src 'self' http://www.google-analytics.com/__utm.gif https://ssl.google-analytics.com/__utm.gif;
So far, I've done the following:
I added two script tags to my html:
<script src="/js/google-analytics.js"></script>
<script src="https://ssl.google-analytics.com/ga.js" async="true"></script>
google-analytics.js sets up the _gaq array with _setAccount and _trackPageview.
I added the domain for ga.js to the script-src.
I noticed that ga.js was loading two images, so I added them to img-src.
Is there anything I'm missing? Will Google change things on me and break all of this? Is there any official recommendation?

This is mostly right:
You don't need the path to the image, just the protocol + host + (implied) port
Firefox differs slightly in its CSP implementation. For older versions, replace default-src with allow. There was a cutoff where Firefox supported default-src as equal to allow but most still implement with allow until it fully supports the spec (no citation included).

Related

Content Security Policy for OpenStreetMap

The following creates one file index.html and one folder index_files. Loaded locally, index.html looks and behaves as expected (zooms, pans etc...).
library(OpenStreetMap)
library(htmlwidgets)
m <- leaflet()
m <- addTiles(m) %>% addProviderTiles(provider=providers$OpenStreetMap)
saveWidget(m, file="index.html",selfcontained = FALSE)
Once I upload to the server, the only way I've been able to view/interact with the map was by installing a Chrome extension that disables CSP.
My question is what should the CSP string look like in this case? I've iterated quite a bit on this, what the current setting I'm using is:
img-src 'self'
openlayers.org
tile.openstreetmap.org
a.tile.openstreetmap.org
b.tile.openstreetmap.org
c.tile.openstreetmap.org
unpkg.com;
script-src 'self' 'unsafe-eval' openlayers.org;
style-src 'self' 'unsafe-inline' 'unsafe-eval' openlayers.org;
I started out being served a blank page, and now I do see the outline of the individual tiles, and how they change when I zoom in & out.
The errors I get are by inspecting the page content. The network tab indicates that all elements but the images are loaded / not blocked. The pngs are blocked with the following error:
Refused to load the image 'https://a.tile.openstreetmap.org/0/0/0.png' because it violates the following Content Security Policy directive: "img-src 'self'".
Seems I'm close the the mark - what is missing?
The most likely explanation is that there is an existing CSP that sets "img-src 'self'". Adding another CSP can only make it stricter. As it doesn't seem like you set "img-src 'self'", you can likely find this in the response headers of the document (the one with content-type text/html). You will need to figure out how it is set and modify/remove it. It can be set in code, in a framework, in a webserver or a proxy.

Google Tag Manager - Any possible way of adding CSP nonce to Custom HTML snippets? Script attributes get stripped

I'm implementing CSP on an existing website and have been following along with this article on passing a CSP nonce to GTM and using it as a Custom Variable in GTM.
<script nonce="9CZ9vGge7C9At2iwrPtSNG7Ev10=" id="gtmScript">
<!-- Google Tag Manager -->
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;var n=d.querySelector('[nonce]');
n&&j.setAttribute('nonce',n.nonce||n.getAttribute('nonce'));f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-MYID');
<!-- End Google Tag Manager -->
</script>
Variable gets added as a DOM Element Variable. Then I add the variable value to my custom script. Here is a demo script. It is the entirety of a Custom HTML tag in GTM.
<script nonce="{{nonce}}">
console.log("CSP-allowed script with nonce:", "{{nonce}}");
</script>
The issue is, CSP still blocks this. And it has nothing to do with the {{nonce}} variable - proven by changing CSP to 'unsafe-inline' and seeing the correct value output in console.
I have since been reading that GTM strips attributes out of the tags it injects inline. Which would be odd, as it would mean the linked article above actually never would have worked (it's only a 3 month old article). But does this mean that it's physically impossible to get scripts in Custom HTML GTM tags to function with CSP? The solution in the above link is impossible because the script will always be blocked by CSP.
UPDATE: Here is my CSP
<meta http-equiv="Content-Security-Policy" content="
default-src 'none' ;frame-src 'self';
script-src 'self' 'nonce-$CSPNonce' *.googletagmanager.com;
style-src 'self' 'nonce-$CSPNonce';
font-src 'self';
img-src 'self' 'nonce-$CSPNonce' data:;
connect-src 'self'">
And the console error
gtm.js?id=GTM-MYID:782 Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' 'nonce-mQoPSCSszFQ8loJF5jii6quCHeY=' *.googletagmanager.com". Either the 'unsafe-inline' keyword, a hash ('sha256-3kt898DvY8z+SqQyfz8g06pUzzBokMjvzcQ5uN50wTs='), or a nonce ('nonce-...') is required to enable inline execution.
OK I found the cause here. I'm leaving the question up in case someone stumbles across this in future.
When you create a Custom HTML tag in GTM, under the code window is a tickbox called "Support document.write". The tooltip beside it doesn't mention much other than allowing you to use document.write() in your scripts via a "new rendering engine".
For whatever reason, if this is not ticked, the nonce attribute is stripped. With it ticked (using the new rendering method I guess), it is not stripped.
You must both turn on support for document.write AND use an additional alternate name for your nonce attribute. Between GTA and chrome your nonce value will be eaten for security reasons.
<script nonce="NONCEHERE" data-dsn="NONCEHERE" id="gtmScript">
<!-- Google Tag Manager -->
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;var
n=d.querySelector('[nonce]');n&&j.setAttribute('nonce',n.nonce||n.getAttribute('nonce'));f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-MYID');
<!-- End Google Tag Manager -->
</script>
Then use the data-dsn attribute to pull it into GTM.

Fixed : Revolution slider not working / showing on Safari iOs iphone and Ipad

I just wanted to share with you on the subject because I spend 3 days searching and testing this problem until I found a solution. I wish to share my solution with you. (I'm a self-taught web designer, not a professional).
Context :
I was asked to update a 2009~2012 post-type WordPress website, to add a landing page for a promotional campaign, design for phone, and mobile-first.
I built the landing page in WordPress, added a revolution slider than test it.
It was beautiful on every desktop and Droid phone. But it didn't work on Safari iOs. All inline scripts and inline-CSS didn't work at all, shortcodes too.
I receive the bug error ' Refused to apply a stylesheet because its hash, its nonce, or ‘unsafe-inline’ appears in neither the style-src directive nor the default src directive of the Content Security Policy '
I try deactivating all the plugins one by one, I search on 90 pages and forums to find why it didn't work on SAFARI and how to solve it. Found out I needed to edit the Content-Security-Policy. I try adding the cache pluging W3Cache Total to use their Content-Security-Policy, even though it had a place to add and modify the CSP, it didn't make any change to the original code, as it didn't have the authorization to do it. It try adding this:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
But it didn't do anything. I must say I am working with an old website I did not build.
To solve my problem, I opened the HTCacces files and added this code to be the more permissive iOs browser ever.
#Header unset Content-Security-Policy
#Header add Content-Security-Policy "default-src * 'unsafe-eval' 'unsafe-inline' 'unsafe-dynamic' data: filesystem: about: blob: ws: wss:"
#Header unset X-Content-Security-Policy
Header add X-Content-Security-Policy "default-src * 'unsafe-eval' 'unsafe-inline' 'unsafe-dynamic' data: filesystem: about: blob: ws: wss:"
Header unset X-WebKit-CSP
Header add X-WebKit-CSP "default-src * 'unsafe-eval' 'unsafe-inline' 'unsafe-dynamic' data: filesystem: about: blob: ws: wss:"
Everything worked and I receive not bug code from iOs . I know the website might be insecure now, but I do not thing ever, nobody would attack this website, because it is so little in the Internet universe.
Please do not be harsh with me, I just want to share with you my solution, even though it might not be the best and a professional would have found out easily, I wrote it down for those like me who are self-taught web designers and learn every day by fixing bugs. Sorry for the typos, I'm an FR Quebecker.
Thank you Stack Overflow!

How to define a Content Security Policy (CSP) that utilizes 'strict-dynamic' but includes fallback to use 'unsafe-inline'?

I'm implementing the CSP header on a website that needs to dynamically load some scripts due to various reasons (mostly plugins).
This is easily achieved by defining script-src using 'strict-dynamic' along with a hash/nonce for the non-dynamic scripts.
The recommendations everywhere suggest also including some high level sources/schemes and 'unsafe-inline' as fallback, in case the browser doesn't support CSPv3 and thus, doesn't support 'strict-dynamic' and dynamic loading:
script-src: 'strict-dynamic' 'sha256-somesortofhash...' https: 'unsafe-inline';
This fallback method will work fine for browsers that only support CSPv1:
'strict-dynamic' is ignored (unrecognized).
hash value is ignored (unrecognized).
https: and 'unsafe-inline' are enforced.
But doesn't seem to work for CSPv2 browsers:
'strict-dynamic' is ignored (unrecognized).
hash value is enforced.
https: is enforced.
'unsafe-inline' is ignored (due to enforcement of hash).
How can I define a policy with proper fallback for CSPv2 browsers as well?
'strict-dynamic' was designed mostly to work in combination with nonces. You are right - there is no perfect CSPv2 fallback when used in combination with hashes.
The uncovered case is dynamically generated inline scripts - a rare pattern, in my experience:
var s = document.createElement('script');
s.innerText = 'alert(1)';
document.body.appendChild(s);
will not work in CSPv2-compatible browsers, and the best solution in that case is to do UA sniffing and not send a policy at all.
Sourced scripts will work just fine.
On the other hand, a nonce-based policy such as:
script-src 'nonce-r4nd0m' 'strict-dynamic' 'unsafe-inline' https:
will fall-back gracefully, because for the case above you can always set a nonce manually:
var s = document.createElement('script');
s.setAttribute('nonce', 'df7Af03DRTs66pP');
s.innerText = 'alert(1)';
document.body.appendChild(s);
More about backward compatibility and fallbacks in our presentations[1][2].
[1] https://speakerdeck.com/mikispag/content-security-policy-a-successful-mess-between-hardening-and-mitigation?slide=52
[2] https://speakerdeck.com/mikispag/so-we-broke-all-csps-dot-dot-dot-you-wont-guess-what-happened-next-michele-spagnuolo-and-lukas-weichselbaum?slide=15
Your policy:
script-src: 'strict-dynamic' 'sha256-somesortofhash...' https: 'unsafe-inline';
It utilizes hash and it is not well suited with strict-dynamic, I would suggest using nonce-{randomNumber} and it could solve your issues.
I would suggest checking your policy with https://csp-evaluator.withgoogle.com. It would help you with all the attributes as per the version whether they are supported with the corresponding CSP version.
And if you need more help around CSP3 you could look at this:
Firefox refuses to load any scripts with strict-dynamic set

Content Security Policy and Google Analytics without unsafe-inline?

Currently, my CSP config in Apache looks like that:
Header set Content-Security-Policy "default-src 'self' 'unsafe-inline' https:"
I'd like to remove the unsafe-inline directive to improve my site's security, see Mozilla's Observatory.
Nevertheless, whenever I remove it, my browser's console shows an error indicating that the inline GA couldn't load…
Is there a workaround?
Sorry to Edit again. The proposed solution in
New Google Analytics code into external file
did not work for me. instead i got it to work like this:
i add a script tag to my page to load the analytics.js:
<script src="https://ssl.google-analytics.com/analytics.js" async id="ga"></script>
<script src="my_other.js" async></script>
and then in my_other.js file i do this:
window.addEventListener("load", function(){
ga('create', 'UA-********-1', 'auto');
ga('send', 'pageview');
})
then in your csp header you have to set some exeption to script-src and image-src. somthing along these lines:
img-src data: 'self' *.google-analytics.com *.g.doubleclick.net;
script-src 'self' *.google-analytics.com
As an alternative work-around, you can allow specific, static scripts by adding the script's hash to your content security policy. (A nonce works for dynamic scripts):
Hash your script (e.g., using sha256). Do include white space/capitalization. Don't include the script tags.
Add script-src 'sha256-[MYHASH]' to your content security policy.
See MDN for details. Not supported on IE11 .

Resources