Content Security Policy nonce does not apply to event handler attributes - javascript

I am in the process of adding CSP headers to a site that has a long way to go before it can adopt a strict policy. There are quite a few inline scripts, so I am using nonce- to allow specific inline scripts. I have found that it doesn't work on the onload attribute of a script tag with src. Here's an example:
// header:
Content-Security-Policy: script-src self https: 'nonce-d3adbe3fed'
<script async defer src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js" nonce="d3adbe3fed" onload="console.log('onload', _.VERSION)"></script>
Full working demo at https://brave-pasteur-0d438b.netlify.com/
Chrome gives the following error:
Refused to execute inline event handler because it violates the following Content Security Policy directive: "script-src self https: 'nonce-d3adbe3fed'". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution.
The message suggests that it should be possible to enable inline event handlers with a nonce, but as far I as I can tell, nonce is only intended to work on inline scripts.
This is just a demo, but the use case is an async/deferred tracking script which loads the tracking library, then in the onload handler makes a tracking call to the loaded library.
Is it possible to use a nonce on an onload or other event handler attribute, or will I need to change my implementation? Using script-src 'unsafe-inline' or script-src-attr 'unsafe-inline' is not an option, as those are the vulnerabilities I am specifically trying to address. And putting the contents of the onload handler into a separate script following the script tag is also not an option because the script is async deferred, and needs to stay that way.

If there is a way to use nonce on an inline handler, I will accept an answer that demonstrates it. Unfortunately, at the time of writing, I don't think there is.
As a workaround, the following script exhibits the same behavior and timing as an script with async/defer and an onload handler, while satisfying the specified CSP policy:
<script nonce="d3adbe3fed">
let s = document.createElement('script');
s.src = 'https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js';
s.onload = () => console.log(_.VERSION);
document.documentElement.appendChild(s);
</script>
Of course, the long term solution is to eliminate inline scripts completely, but in the short term that isn't always feasible, and it is better to implement a more lax policy quickly, than to put it off and have no CSP at all.

As previously noted nonces won't work (at least at the moment - January of 2023) for inline JS event handlers - but you can use the less safe unsafe-hashes option if you can't or don't want to change your inline script. The procedure is below.
Generate your hash using this command:
echo -n "console.log('onload', _.VERSION)" | openssl dgst -sha256 -binary | openssl base64
and then use it like:
Content-Security-Policy: script-src 'unsafe-hashes' 'sha256-YOUR_HASH_HERE';
in this case the hash YOUR_HASH_HERE would be: is6kBKp90zgPWiqfkihufUS6bhRViGwlIg8RlEV7MgA=

Related

Electron.js does not load jQuery due to security policy

I am trying to load jQuery in Electron (v. 16.0.0), but I get this error:
Inside the head element I have included this line:
<meta http-equiv="Content-Security-Policy" content="script-src 'self';">
Also, inside the body element, I am trying to load jQuery like this:
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
I have tried so many ways to find a solution for this, but to no avail. Previously, I also tried to load jQuery like this, but it gave me a similar error, shown below:
<script>window.$ = window.jQuery = require('./libraries/jQuery/jquery.min.js');</script>
Answers to a related question did not work for me either. What should I do?
The reason Electron, or any other Web browser that implements Content Security Policy, for that matter, would correctly refuse to load a script from an arbitrary origin (URL), or even an "inline" script (e.g. script text inside a script element), is because your security policy is explicitly specified to deny such attempts, with that meta element you said you added:
<meta http-equiv="Content-Security-Policy" content="script-src 'self';">
Why did you add it? Was it there by someone else's hand? Why is it there? It's the reason why Electron denies loading of the scripts in question.
The value of the content attribute above has the effect of instructing Electron to only allow loading scripts from the same origin as the origin of the document containing the meta element. That effectively excludes every other origin like https://code.jquery.com and inline scripts (which have to be allowed explicitly in this case because self denies these). Basically, the value is to be interpreted as "only allow loading scripts from the same site". Inline scripts are not considered as "same site".
Simpler put, you yourself prohibit loading of scripts from the kind of locations you then attempt to use, with that meta element.
You need to learn how Content Security Policy mechanism works and applies in your case. You will have to decide whether you want to allow loading of scripts from domains like code.jquery.com, or whether, for example, you will only want to allow loading scripts from your website, which in turn will probably necessitate you copying the JQuery library you want to use to be served by your website. You also will have to decide if you want to allow "inline" scripts on your site, for whatever reason you may consider necessary.
The security policy mechanism itself is very useful, don't shy away from it, it's there for a reason -- to help you prevent abuse of your site users by malicious scripts loaded by other malicious scripts or mechanisms. But you need to use it correctly, obviously.
You have 2 issues because of jQuery:
script-src 'self' does not allow to load script from https://code.jquery.com/jquery-3.6.0.min.js, that's why you observe Refused to load the script 'https://code.jquery.com/jquery-3.6.0.min.js'... error.
You have to adjust your CSP at least as script-src 'self' https://code.jquery.com;.
After page loads, the jQuery pick up all scripts having $() and place them into one inline script in the <head> section. That's why you observe Refused to execute inline script ... error.
This inline script can be resolved with either 'unsafe-inline' or 'unsafe-eval' or 'nonce-value'(for jQuery > 3.4).
Allowing 'unsafe-inline' is a very harmful advice, since such CSP will not protect against XSS at all (https://youtu.be/zlH_bBQMgkc?t=717).
Also Electron does not have the technical ability to refresh the 'nonce' value.
Therefore, the most secure CSP you can do is:
script-src 'self' 'unsafe-eval' https://code.jquery.com;
or much better:
default-src 'self'; script-src 'self' 'unsafe-eval' https://code.jquery.com;
Note: Contrary to a common misconception, 'self' does not mean the Same Origin Policy, CSP interprets 'self' much more broadly.

What is the correct way to load a script with the 'strict-dynamic' CSP directive?

Background
The idea of the Content Security Policy was to tell web-browsers what content to load from where. This means that attackers should not be able to inject their own code if, for example, 'unsafe-inline' was not explicitly allowed (which is not the best thing to do).
Google also released a CSP Evaluator, which is designed to find possible mistakes in your policy. With the default settings, the tool recommends using the 'strict-dynamic' policy for 'script-src'. The idea behind it is that you write a loader for whichever JavaScript sourcees you require and forbid everything else.
The Problem
What is considered the "correct" way to implement such a loader? Should the loader be written yourself (see below for example) or should a tool be used to create such a loader? (Please note that this question is not asking for a specific tool recommendation)
Example
var imported = document.createElement('script');
imported.src = '/path/to/imported/script';
document.head.appendChild(imported);
Context
My website currently has the following policy:
default-src 'none';
img-src 'self';
style-src 'self' https://stackpath.bootstrapcdn.com 'sha256-bviLPwiqrYk7TOtr5i2eb7I5exfGcGEvVuxmITyg//c=';
script-src https://use.fontawesome.com https://code.jquery.com https://cdnjs.cloudflare.com https://stackpath.bootstrapcdn.com;
base-uri 'none';
form-action 'none';
frame-ancestors 'none';
Google's tool suggested the following:
Host whitelists can frequently be bypassed. Consider using 'strict-dynamic' in combination with CSP nonces or hashes.
As such, I want to implement a loader to load these JS frameworks and I want to know how to best approach this issue.
An immediate answer is that as long as the script you're dynamically loading (/path/to/imported/script) is hosted in a domain that you've already whitelisted in script-src, you don't have to modify your CSP or change your loader -- everything will work as expected.
However, a broader problem is that your script-src whitelist includes domains that host Javascript which can be used by an attacker who finds a markup injection bug in your application to bypass your CSP. For example, https://cdnjs.cloudflare.com hosts Angular (https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.2/angular.min.js) which can be used by an attacker to convert an HTML injection into arbitrary script execution (here is a paper about this).
The suggestion in the CSP Evaluator tool is to switch your application to rely on a script-src which uses CSP nonces instead of the whitelist. To do this you would need to follow the process outlined at https://csp.withgoogle.com/docs/strict-csp.html -- basically, make sure that every <script> element has a correct nonce attribute which changes for every page load, or instead use CSP3 hashes for static scripts.
Your CSP would then look like:
... script-src 'nonce-[random-value]' 'strict-dynamic' 'unsafe-inline' https:; ...
If you use 'strict-dynamic', your script loader does not have to change because browsers will automatically trust scripts added to your page via programmatic APIs such as appendChild().

Browser is blocking my jquery

So I'm not sure why but I get an error message from Chrome.
Error message:
Refused to execute inline script because it violates the following Content Security Policy directive: "default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'". Either the 'unsafe-inline' keyword, a hash ('sha256-TakXxMuCq+J+ccgIY6WUXR+xy3/BdgRbqG7Y1mNRWJQ='), or a nonce ('nonce-...') is required to enable inline execution. Note also that 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
jQuery:
<script type="text/javascript">
$( document ).ready(function() {
$.ajax({
type:'GET',
url:'http://api.steampowered.com/ISteamNews/GetNewsForApp/v0002/?appid=440&count=3&maxlength=300&format=json'
success: function(data){
console.log('success',data);
}
});
});
</script>
Sorry if this is a duplicate question.
I just had a hard time finding the answer to my problem.
But can someone explain why I am getting the error message that I am getting?
I want to give some extra information, so that you may understand my situation.
I was writing an app in HTML.
All the files used in the HTML other then the JSON are all from my machine.
I did not run them from any server including xampp. I simply double clicked my HTML file.
Are you writing a Chrome extension? It's not immediately clear from your question, but that's where I would expect to find this error message.
Inline script tags are not executed by extensions. The Google Content Security Policy pages state:
Inline JavaScript will not be executed. This restriction bans both
inline <script> blocks and inline event handlers (e.g. <button
onclick="...">).
The first restriction wipes out a huge class of cross-site scripting
attacks by making it impossible for you to accidentally execute script
provided by a malicious third-party. It does, however, require you to
write your code with a clean separation between content and behavior
(which you should of course do anyway, right?).
There is a way to relax this policy though
As of Chrome 46, inline scripts can be whitelisted by specifying the
base64-encoded hash of the source code in the policy. This hash must
be prefixed by the used hash algorithm (sha256, sha384 or sha512). See
Hash usage for <script> elements for an example.

Active Content-Security-Policy (CSP) and Rails :back link

I want to allow the internal Rails :back link functionality for my application with an active Content-Security-Policy.
CSP:
%meta{"http-equiv" => "Content-Security-Policy", "content" => "default-src *;"}
Example link:
= link_to 'Back', :back
# Back *
* Rails links to the referer and only if no referer is set falls back to JS.
How can I whitelist only this tiny history.back() piece of javascript?
I tried to set an exception as described in https://www.w3.org/TR/2015/CR-CSP2-20150721/#script-src-hash-usage and generated the required hash like this:
echo -n "history.back()" | openssl dgst -sha256 -binary | openssl enc -base64
Result:
%meta{"http-equiv" => "Content-Security-Policy", "content" => "default-src *; script-src 'self' 'sha256-LdlORHyUW/rwezK0l13nW+IwcZmi78eWOCBjewMWRr4='"}
But the Chrome console displays the same error what means the hash is invalid:
Refused to execute JavaScript URL because it violates the following
Content Security Policy directive: "script-src 'self'
'sha256-SmahML3R6+R4SRnsB6tEJ8Z4OVa4Qhk7A/gv3eAiG6s='". Either the
'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce
('nonce-...') is required to enable inline execution.
Hash-whitelisting inline code oder inline styles is not possible with an active Content Security Policy. The above example would only work if history.back() was the content of a script-tag like this:
<script>history.back()</script>
Chrome's error message is misleading, because it suggests using the hash method for whitelisting the inline code which is actually not supported.
The same applies for inline styles like style="display:none" (used for example in nested_form gem).
The use of unsafe-inline was no option for my project. So I solved these rare problems by monkey patching the class or module to use different markup (for example class="hidden") plus some additional external javascript where required but of course there are drawbacks when updating the affected gems.
You can also move the JS to an external file on your server and include it with <script src=...>. Your CSP allows *, so also your own origin ('self'). Then use unobtrusive JavaScript to react to a click on the link. Here's the idea: http://guides.rubyonrails.org/working_with_javascript_in_rails.html#unobtrusive-javascript

Why isn't this inline javascript blocked by content security policy?

I have a page that I set the script-src of the content security policy like this:
script-src 'self' *.uservoice.com *.intuit.com ajax.googleapis.com localhost:*
When I load the page with a hard-coded inline script I have created myself to test, it is blocked like expected:
Refused to execute inline script because it violates the following
Content Security Policy directive: "script-src 'self'
*.uservoice.com *.intuit.com ajax.googleapis.com localhost:* ". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce
('nonce-...') is required to enable inline execution.
However, when I insert a new script tag dynamically, the script isn't blocked, for example, this still executes:
$("body").append("<script>alert('xss');</script>")
I am using Chrome as the browser here for testing. I was hoping that this script would be blocked as well, since that would really help to prevent xss. Is there something I can change to block this type of script injection as well?
The script you add with append or innerHtml won't be executed unless you use eval(). So it's not violating CSP.
Although this may look like a cross-site scripting attack, the result is harmless. HTML5 specifies that a tag inserted via innerHTML should not execute. 1
See script elements inserted using innerHTML do not execute when they are inserted.

Categories