-
Notifications
You must be signed in to change notification settings - Fork 252
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
script hash rails integration PoC #67
Conversation
Super hacky, no tests, no haml support(?), only works for rails 3.1+. Not going to supply a Capistrano task for now... suggesting guard-rake because if somehow you forgot to update your hashes before deploy... clearly you didn't run/have integration tests. |
Only works if entire tag is on one line lol. Don't use scan. |
# jank to make sure we're messing w/ the right header | ||
header_name = ContentSecurityPolicy.new(nil, :ua => env["HTTP_USER_AGENT"]).name | ||
|
||
csp = headers[header_name] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error if no CSP policy set
Writeup of the why and how of this branch: http://nmatatal.blogspot.com/2013/09/how-my-script-hash-poc-works.html |
JS equivalent hash computation with jQuery and cryptojs $.each($('script'), function(index, x) { console.log(CryptoJS.SHA256(x.innerHTML).toString(CryptoJS.enc.Base64));}); |
This PoC has served it's purpose |
Reopening because someone else might be motivated to make this a thing before I get to it. Some people would rather support dynamic hashing (in development only) rather than have an external process (guard) grep/populate hashes. I still disagree. Whatevs. |
Sweet, thanks for this PoC. What do you think about a more simple approach of using a specific Rails helper, say,
which explicitly computes a hash, and saves it to a list for later (controller instance variable, Or is this the approach you mention here:
|
FWIW I got this working by copying over your module InlineScriptHelper
def inline_script_with_hash(&block)
content = "\n#{capture(&block)}"
(request.env['script_hashes'] ||= []) << compute_inline_script_hash(content)
content_tag :script, content
end
def compute_inline_script_hash(content)
Digest::SHA256.base64digest(content)
end
end |
@spikebrehm I like it. A lot. I still have to support mustache but that shouldn't be too hard. |
Are there plans to change secureheaders to support computing the header in a middleware, in a less-hacky way? I notice this is a year old. If not, I suppose we'll fork this to support it, but I'd rather not fork if we can help it. |
This branch was just a PoC. I welcome any pull requests on this! Don't fork! |
One issue with this dynamic hashing is that it doesn't buy you anything. I'd want the hash values to be static or otherwise limited in production |
Sure, I can agree that hashing of dynamic script content may be a bad idea, and that the helper approach makes it easy to do that. However, I do think that the helper approach is simpler and easier to use than Rake task / YAML approach. |
|
||
# need to settle on stuffs | ||
def hash_source_expression(hashes, format = "sha256", delimeter = "-", hash_delimeter = " ", wrapper = "'") | ||
wrapper + format + delimeter + hashes.join(hash_delimeter) + wrapper |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This causes improperly formatted script-src
when there's multiple script hashes. As is, the output for two hashes, abcde
and fjhij
is:
script-src 'sha256-abcde fghij'
This should be:
script-src 'sha256-abcde' 'sha256-fghij'
Here's the proper source code:
def hash_source_expression(hashes, format = "sha256", delimeter = "-", hash_delimeter = " ", wrapper = "'")
hashes.map { |hash| wrapper + format + delimeter + hash + wrapper }.join(hash_delimeter)
end
If you like I can make a PR to this branch that fixes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should also be sha1. Only sha1 is supported for now afaik.
Please do submit a PR if you have the time, including your other changes. Or submit a fresh PR :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we go: #109
Should it really be sha1
? sha256
works for me in Chrome 37.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh sweet. Didn't know that.
There exists a bug which causes ian improperly formatted `script-src` expression for multiple script hashes. As is, the output for two hash `abcde` and `fjhij` is: script-src 'sha256-abcde fghij' This should be: script-src 'sha256-abcde' 'sha256-fghij'
Conflicts: lib/secure_headers/railtie.rb spec/lib/secure_headers_spec.rb
Fix support for mutliple script hashes
Totally. So how about the helper works in !production and then we just make the hash generation just another deploy step? I'd love to iron all this out and ship something somewhat soon. I should have the cycles too. |
Today I've actually been implementing the YAML approach, and there does seem to be a benefit in the fact that it's literally impossible to whitelist dynamic scripts. The helper approach doesn't have that benefit, because you will always be whitelisting the dynamic value post-evaluation. That is certainly a nice development experience, but it may be too permissive given an organization's needs. The downside of the YAML approach is that the developer would have to run the Rake task and restart the Rails server every time they add/update a You're proposing a hybrid approach? What would that look like? |
I received some feedback that a helper that dynamically computes the values during development is pretty much necessary. One option is to always calculate hashes dynamically AND compare to a list of known hashes and respond to mismatches in a configurable way. i.e. "OH NOEZ raise exception" vs "yeah, we logged that out and we'll clean it up in the next release, don't blow up in the meantime" csp = {
...,
rescue_from_script_hash_mismatch: lambda {
if RAILS.env.production?
raise "go home, you're drunk"
elsif RAILS.env.development?
puts "oops, unexpected hash, run be rake secure_headers:generate_hashes"
end
}
} Perhaps the lack of a rescue handler implies that the dynamic calculation should not even happen, and the inline script helper becomes a noop, falling back to the yaml-only behavior. |
Interesting, I like that. So the script-finding regex will also have to support finding the ERB statement containing the call to the helper. |
def script_hashes | ||
if @script_hashes | ||
@script_hashes | ||
elsif superclass.respond_to?(:script_hashes) # stop at application_controller |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be an issue if the superclass is itself a subclass of ApplicationController
, yeah? i.e.:
MyController < AuthenticatedController < ApplicationController
Is it too hacky to recursively look up the superclass tree until it finds superclass.name == 'ApplicationController'
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds about right, I think that's only the case when you override settings from one controller to another. I guess this has just never come up 🤷
Yeah, I just hope we don't run into whitespace issues. |
Application demonstrating this: https://github.com/oreoshake/script_hash_test
Video demo: https://www.youtube.com/embed/Bc2hvziTRxg
Two pieces:
AND
Based on "simple" script hash proposal.
Need to verify hashed values with various methods.
This makes me think the before_filters should create a ContentSecurityPolicy object and build the entire header in the middleware.