Content Security Policy hash not recognized by Saf

2019-07-20 08:20发布

问题:

I have a meta tag with the following directive inside of it:

<meta http-equiv="Content-Security-Policy" content="base-uri 'self'; script-src 'self' 'sha256-s5EeESrvuQPpk2bpz5I3zn/R8Au2DYB1Z+YUH9p0fUE=' 'sha256-PYYfGnkbZ44B9ZBpgv8NbP3MXT560LMfrDSas2BveJo=';">

I then have 2 inline scripts further down the page, each which should match one of the generated shas in the policy.

In Chrome and Firefox, I get no complaints and my scripts run as expected.

In Safari Version 11.0.3 (13604.5.6), I get the following error:

Refused to execute a script because its hash, its nonce, or 'unsafe-inline' does not appear in the script-src directive of the Content Security Policy

and I am confused as to why!

Unfortunately, I am unable to produce a minimum reproducible repo with the issue inside of it - smaller examples work in Safari for me, so it leads me to believe it's to do with something specific in my app, possibly related to the second thing I have tried below.

Any help would be much appreciated!

Things I have tried:

Are hashes supported?

According to this Stack Overflow post and the Safari release notes, CSP 2.0 which supports hashes was implemented in Safari 10

Correct charset?

Previously, I was seeing issues because I was calculating the hashes based on a UTF-8 charset, but was outputting the JS to the browser without a charset meta tag in place. Special characters in my JS were being mangled and were causing differences in the shas when the browser tried calculating them.

I don't believe this is affecting me now since Chrome and Firefox see no issues, but maybe I'm wrong here?

unsafe-inline for Safari, and then allow hashes to override that in Chrome and Firefox?

According to the CSP spec, unsafe-inline is ignored if a hash or nonce is present. Safari 11 also adheres to this, so adding the unsafe-inline keyword has no effect

回答1:

Turns out this was a charset issue.

I managed to get a minimal reproducible issue (after some trial and error, and a lot of luck!) and found that one of my characters had a different sha before and after it was rendered in Safari.

Before it was rendered in Safari, the character was the following:

After Safari had rendered the character, it was the following (even in the source of the code):

Strangely, Chrome and Firefox both don't have this issue, so it either must be Safari normalizing the character after it has rendered, or a difference in when the sha256 hashes are calculated between the browsers.

The solution was to turn off character compression in UglifyJS so that the character stays as \uF900 instead of being compressed to the single character in the picture above.

I achieved this with the following option in my webpack.config.js file:

new UglifyJsPlugin({
    uglifyOptions: {
        output: {
            // necessary to stop the minification of escaped unicode sequences into their actual chars.
            // some unicode breaks CSP checks in safari
            ascii_only: true,
        },
    },
}),

I have reported this to Apple to see if they will consider fixing this.