Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
张重言
rails
提交
b2f0a894
R
rails
项目概览
张重言
/
rails
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
R
rails
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
b2f0a894
编写于
2月 22, 2018
作者:
A
Andrew White
提交者:
Andrew White
2月 22, 2018
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Merge pull request #32018 from rails/add-nonce-support-to-csp
Add support for automatic nonce generation for Rails UJS
上级
da8d0c94
变更
16
隐藏空白更改
内联
并排
Showing
16 changed file
with
207 addition
and
52 deletion
+207
-52
actionpack/CHANGELOG.md
actionpack/CHANGELOG.md
+28
-0
actionpack/lib/action_controller/metal/content_security_policy.rb
...ck/lib/action_controller/metal/content_security_policy.rb
+18
-0
actionpack/lib/action_dispatch/http/content_security_policy.rb
...npack/lib/action_dispatch/http/content_security_policy.rb
+32
-0
actionpack/test/dispatch/content_security_policy_test.rb
actionpack/test/dispatch/content_security_policy_test.rb
+16
-0
actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
...onview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
+3
-1
actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee
actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee
+4
-0
actionview/lib/action_view/helpers.rb
actionview/lib/action_view/helpers.rb
+2
-0
actionview/lib/action_view/helpers/csp_helper.rb
actionview/lib/action_view/helpers/csp_helper.rb
+24
-0
actionview/lib/action_view/helpers/javascript_helper.rb
actionview/lib/action_view/helpers/javascript_helper.rb
+11
-0
actionview/test/ujs/public/test/call-ajax.js
actionview/test/ujs/public/test/call-ajax.js
+1
-2
actionview/test/ujs/server.rb
actionview/test/ujs/server.rb
+19
-7
actionview/test/ujs/views/layouts/application.html.erb
actionview/test/ujs/views/layouts/application.html.erb
+4
-3
railties/lib/rails/application.rb
railties/lib/rails/application.rb
+2
-1
railties/lib/rails/application/configuration.rb
railties/lib/rails/application/configuration.rb
+38
-37
railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
...s/app/templates/app/views/layouts/application.html.erb.tt
+1
-0
railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt
...mplates/config/initializers/content_security_policy.rb.tt
+4
-1
未找到文件。
actionpack/CHANGELOG.md
浏览文件 @
b2f0a894
*
Add support for automatic nonce generation for Rails UJS
Because the UJS library creates a script tag to process responses it
normally requires the script-src attribute of the content security
policy to include 'unsafe-inline'.
To work around this we generate a per-request nonce value that is
embedded in a meta tag in a similar fashion to how CSRF protection
embeds its token in a meta tag. The UJS library can then read the
nonce value and set it on the dynamically generated script tag to
enable it to execute without needing 'unsafe-inline' enabled.
Nonce generation isn't 100% safe - if your script tag is including
user generated content in someway then it may be possible to exploit
an XSS vulnerability which can take advantage of the nonce. It is
however an improvement on a blanket permission for inline scripts.
It is also possible to use the nonce within your own script tags by
using `nonce: true` to set the nonce value on the tag, e.g
<%= javascript_tag nonce: true do %>
alert('Hello, World!');
<% end %>
Fixes #31689.
*Andrew White*
*
Matches behavior of
`Hash#each`
in
`ActionController::Parameters#each`
.
*Dominic Cleal*
...
...
actionpack/lib/action_controller/metal/content_security_policy.rb
浏览文件 @
b2f0a894
...
...
@@ -5,6 +5,14 @@ module ContentSecurityPolicy
# TODO: Documentation
extend
ActiveSupport
::
Concern
include
AbstractController
::
Helpers
include
AbstractController
::
Callbacks
included
do
helper_method
:content_security_policy?
helper_method
:content_security_policy_nonce
end
module
ClassMethods
def
content_security_policy
(
**
options
,
&
block
)
before_action
(
options
)
do
...
...
@@ -22,5 +30,15 @@ def content_security_policy_report_only(report_only = true, **options)
end
end
end
private
def
content_security_policy?
request
.
content_security_policy
end
def
content_security_policy_nonce
request
.
content_security_policy_nonce
end
end
end
actionpack/lib/action_dispatch/http/content_security_policy.rb
浏览文件 @
b2f0a894
...
...
@@ -21,6 +21,12 @@ def call(env)
return
response
if
policy_present?
(
headers
)
if
policy
=
request
.
content_security_policy
if
policy
.
directives
[
"script-src"
]
if
nonce
=
request
.
content_security_policy_nonce
policy
.
directives
[
"script-src"
]
<<
"'nonce-
#{
nonce
}
'"
end
end
headers
[
header_name
(
request
)]
=
policy
.
build
(
request
.
controller_instance
)
end
...
...
@@ -51,6 +57,8 @@ def policy_present?(headers)
module
Request
POLICY
=
"action_dispatch.content_security_policy"
.
freeze
POLICY_REPORT_ONLY
=
"action_dispatch.content_security_policy_report_only"
.
freeze
NONCE_GENERATOR
=
"action_dispatch.content_security_policy_nonce_generator"
.
freeze
NONCE
=
"action_dispatch.content_security_policy_nonce"
.
freeze
def
content_security_policy
get_header
(
POLICY
)
...
...
@@ -67,6 +75,30 @@ def content_security_policy_report_only
def
content_security_policy_report_only
=
(
value
)
set_header
(
POLICY_REPORT_ONLY
,
value
)
end
def
content_security_policy_nonce_generator
get_header
(
NONCE_GENERATOR
)
end
def
content_security_policy_nonce_generator
=
(
generator
)
set_header
(
NONCE_GENERATOR
,
generator
)
end
def
content_security_policy_nonce
if
content_security_policy_nonce_generator
if
nonce
=
get_header
(
NONCE
)
nonce
else
set_header
(
NONCE
,
generate_content_security_policy_nonce
)
end
end
end
private
def
generate_content_security_policy_nonce
content_security_policy_nonce_generator
.
call
(
self
)
end
end
MAPPINGS
=
{
...
...
actionpack/test/dispatch/content_security_policy_test.rb
浏览文件 @
b2f0a894
...
...
@@ -253,6 +253,11 @@ class PolicyController < ActionController::Base
p
.
report_uri
"/violations"
end
content_security_policy
only: :script_src
do
|
p
|
p
.
default_src
false
p
.
script_src
:self
end
content_security_policy_report_only
only: :report_only
def
index
...
...
@@ -271,6 +276,10 @@ def report_only
head
:ok
end
def
script_src
head
:ok
end
private
def
condition?
params
[
:condition
]
==
"true"
...
...
@@ -284,6 +293,7 @@ def condition?
get
"/inline"
,
to:
"policy#inline"
get
"/conditional"
,
to:
"policy#conditional"
get
"/report-only"
,
to:
"policy#report_only"
get
"/script-src"
,
to:
"policy#script_src"
end
end
...
...
@@ -298,6 +308,7 @@ def initialize(app)
def
call
(
env
)
env
[
"action_dispatch.content_security_policy"
]
=
POLICY
env
[
"action_dispatch.content_security_policy_nonce_generator"
]
=
proc
{
"iyhD0Yc0W+c="
}
env
[
"action_dispatch.content_security_policy_report_only"
]
=
false
env
[
"action_dispatch.show_exceptions"
]
=
false
...
...
@@ -337,6 +348,11 @@ def test_generates_report_only_content_security_policy
assert_policy
"default-src 'self'; report-uri /violations"
,
report_only:
true
end
def
test_adds_nonce_to_script_src_content_security_policy
get
"/script-src"
assert_policy
"script-src 'self' 'nonce-iyhD0Yc0W+c='"
end
private
def
env_config
...
...
actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
浏览文件 @
b2f0a894
#= require ./csp
#= require ./csrf
#= require ./event
{
CSRFProtection
,
fire
}
=
Rails
{
cspNonce
,
CSRFProtection
,
fire
}
=
Rails
AcceptHeaders
=
'*'
:
'*/*'
...
...
@@ -65,6 +66,7 @@ processResponse = (response, type) ->
try
response
=
JSON
.
parse
(
response
)
else
if
type
.
match
(
/\b(?:java|ecma)script\b/
)
script
=
document
.
createElement
(
'script'
)
script
.
nonce
=
cspNonce
()
script
.
text
=
response
document
.
head
.
appendChild
(
script
).
parentNode
.
removeChild
(
script
)
else
if
type
.
match
(
/\b(xml|html|svg)\b/
)
...
...
actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee
0 → 100644
浏览文件 @
b2f0a894
# Content-Security-Policy nonce for inline scripts
cspNonce
=
Rails
.
cspNonce
=
->
meta
=
document
.
querySelector
(
'meta[name=csp-nonce]'
)
meta
and
meta
.
content
actionview/lib/action_view/helpers.rb
浏览文件 @
b2f0a894
...
...
@@ -13,6 +13,7 @@ module Helpers #:nodoc:
autoload
:CacheHelper
autoload
:CaptureHelper
autoload
:ControllerHelper
autoload
:CspHelper
autoload
:CsrfHelper
autoload
:DateHelper
autoload
:DebugHelper
...
...
@@ -46,6 +47,7 @@ def self.eager_load!
include
CacheHelper
include
CaptureHelper
include
ControllerHelper
include
CspHelper
include
CsrfHelper
include
DateHelper
include
DebugHelper
...
...
actionview/lib/action_view/helpers/csp_helper.rb
0 → 100644
浏览文件 @
b2f0a894
# frozen_string_literal: true
module
ActionView
# = Action View CSP Helper
module
Helpers
#:nodoc:
module
CspHelper
# Returns a meta tag "csp-nonce" with the per-session nonce value
# for allowing inline <script> tags.
#
# <head>
# <%= csp_meta_tag %>
# </head>
#
# This is used by the Rails UJS helper to create dynamically
# loaded inline <script> elements.
#
def
csp_meta_tag
if
content_security_policy?
tag
(
"meta"
,
name:
"csp-nonce"
,
content:
content_security_policy_nonce
)
end
end
end
end
end
actionview/lib/action_view/helpers/javascript_helper.rb
浏览文件 @
b2f0a894
...
...
@@ -63,6 +63,13 @@ def escape_javascript(javascript)
# <%= javascript_tag defer: 'defer' do -%>
# alert('All is good')
# <% end -%>
#
# If you have a content security policy enabled then you can add an automatic
# nonce value by passing +nonce: true+ as part of +html_options+. Example:
#
# <%= javascript_tag nonce: true do -%>
# alert('All is good')
# <% end -%>
def
javascript_tag
(
content_or_options_with_block
=
nil
,
html_options
=
{},
&
block
)
content
=
if
block_given?
...
...
@@ -72,6 +79,10 @@ def javascript_tag(content_or_options_with_block = nil, html_options = {}, &bloc
content_or_options_with_block
end
if
html_options
[
:nonce
]
==
true
html_options
[
:nonce
]
=
content_security_policy_nonce
end
content_tag
(
"script"
.
freeze
,
javascript_cdata_section
(
content
),
html_options
)
end
...
...
actionview/test/ujs/public/test/call-ajax.js
浏览文件 @
b2f0a894
...
...
@@ -8,7 +8,6 @@ module('call-ajax', {
})
asyncTest
(
'
call ajax without "ajax:beforeSend"
'
,
1
,
function
()
{
var
link
=
$
(
'
#qunit-fixture a
'
)
link
.
bindNative
(
'
click
'
,
function
()
{
Rails
.
ajax
({
...
...
@@ -21,7 +20,7 @@ asyncTest('call ajax without "ajax:beforeSend"', 1, function() {
})
link
.
triggerNative
(
'
click
'
)
setTimeout
(
function
()
{
start
()
},
13
)
setTimeout
(
function
()
{
start
()
},
50
)
})
})()
actionview/test/ujs/server.rb
浏览文件 @
b2f0a894
...
...
@@ -23,18 +23,30 @@ class Server < Rails::Application
config
.
public_file_server
.
enabled
=
true
config
.
logger
=
Logger
.
new
(
STDOUT
)
config
.
log_level
=
:error
config
.
content_security_policy
do
|
policy
|
policy
.
default_src
:self
,
:https
policy
.
font_src
:self
,
:https
,
:data
policy
.
img_src
:self
,
:https
,
:data
policy
.
object_src
:none
policy
.
script_src
:self
,
:https
policy
.
style_src
:self
,
:https
end
config
.
content_security_policy_nonce_generator
=
->
(
req
)
{
SecureRandom
.
base64
(
16
)
}
end
end
module
TestsHelper
def
test_to
(
*
names
)
names
=
[
"/vendor/qunit.js"
,
"settings"
]
+
names
names
.
map
{
|
name
|
script_tag
name
}.
join
(
"
\n
"
).
html_safe
end
names
=
names
.
map
{
|
name
|
"/test/
#{
name
}
.js"
}
names
=
%w[/vendor/qunit.js /test/settings.js]
+
names
def
script_tag
(
src
)
src
=
"/test/
#{
src
}
.js"
unless
src
.
index
(
"/"
)
%(<script src="#{src}" type="text/javascript"></script>)
.
html_safe
capture
do
names
.
each
do
|
name
|
concat
(
javascript_include_tag
(
name
))
end
end
end
end
...
...
@@ -56,7 +68,7 @@ def echo
elsif
params
[
:iframe
]
payload
=
JSON
.
generate
(
data
).
gsub
(
"<"
,
"<"
).
gsub
(
">"
,
">"
)
html
=
<<-
HTML
<script>
<script
nonce="
#{
request
.
content_security_policy_nonce
}
"
>
if (window.top && window.top !== window)
window.top.jQuery.event.trigger('iframe:loaded',
#{
payload
}
)
</script>
...
...
actionview/test/ujs/views/layouts/application.html.erb
浏览文件 @
b2f0a894
...
...
@@ -2,9 +2,10 @@
<html
id=
"html"
>
<head>
<title>
<%=
@title
%>
</title>
<%=
csp_meta_tag
%>
<link
href=
"/vendor/qunit.css"
media=
"screen"
rel=
"stylesheet"
type=
"text/css"
media=
"screen, projection"
/>
<script
src=
"/vendor/jquery-2.2.0.js"
type=
"text/javascript"
></script>
<
script
>
<
%=
javascript_tag
nonce:
true
do
%
>
// This is for test in override.js.
// Must go before rails-ujs.
document.addEventListener('rails:attachBindings', function() {
...
...
@@ -15,8 +16,8 @@
e.preventDefault();
});
});
<
/script
>
<%=
script
_tag
"/rails-ujs.js"
%>
<
%
end
%
>
<%=
javascript_include
_tag
"/rails-ujs.js"
%>
</head>
<body
id=
"body"
>
...
...
railties/lib/rails/application.rb
浏览文件 @
b2f0a894
...
...
@@ -268,7 +268,8 @@ def env_config
"action_dispatch.cookies_digest"
=>
config
.
action_dispatch
.
cookies_digest
,
"action_dispatch.cookies_rotations"
=>
config
.
action_dispatch
.
cookies_rotations
,
"action_dispatch.content_security_policy"
=>
config
.
content_security_policy
,
"action_dispatch.content_security_policy_report_only"
=>
config
.
content_security_policy_report_only
"action_dispatch.content_security_policy_report_only"
=>
config
.
content_security_policy_report_only
,
"action_dispatch.content_security_policy_nonce_generator"
=>
config
.
content_security_policy_nonce_generator
)
end
end
...
...
railties/lib/rails/application/configuration.rb
浏览文件 @
b2f0a894
...
...
@@ -17,48 +17,49 @@ class Configuration < ::Rails::Engine::Configuration
:session_options
,
:time_zone
,
:reload_classes_only_on_change
,
:beginning_of_week
,
:filter_redirect
,
:x
,
:enable_dependency_loading
,
:read_encrypted_secrets
,
:log_level
,
:content_security_policy_report_only
,
:require_master_key
:
content_security_policy_nonce_generator
,
:
require_master_key
attr_reader
:encoding
,
:api_only
,
:loaded_config_version
def
initialize
(
*
)
super
self
.
encoding
=
Encoding
::
UTF_8
@allow_concurrency
=
nil
@consider_all_requests_local
=
false
@filter_parameters
=
[]
@filter_redirect
=
[]
@helpers_paths
=
[]
@public_file_server
=
ActiveSupport
::
OrderedOptions
.
new
@public_file_server
.
enabled
=
true
@public_file_server
.
index_name
=
"index"
@force_ssl
=
false
@ssl_options
=
{}
@session_store
=
nil
@time_zone
=
"UTC"
@beginning_of_week
=
:monday
@log_level
=
:debug
@generators
=
app_generators
@cache_store
=
[
:file_store
,
"
#{
root
}
/tmp/cache/"
]
@railties_order
=
[
:all
]
@relative_url_root
=
ENV
[
"RAILS_RELATIVE_URL_ROOT"
]
@reload_classes_only_on_change
=
true
@file_watcher
=
ActiveSupport
::
FileUpdateChecker
@exceptions_app
=
nil
@autoflush_log
=
true
@log_formatter
=
ActiveSupport
::
Logger
::
SimpleFormatter
.
new
@eager_load
=
nil
@secret_token
=
nil
@secret_key_base
=
nil
@api_only
=
false
@debug_exception_response_format
=
nil
@x
=
Custom
.
new
@enable_dependency_loading
=
false
@read_encrypted_secrets
=
false
@content_security_policy
=
nil
@content_security_policy_report_only
=
false
@require_master_key
=
false
@loaded_config_version
=
nil
self
.
encoding
=
Encoding
::
UTF_8
@allow_concurrency
=
nil
@consider_all_requests_local
=
false
@filter_parameters
=
[]
@filter_redirect
=
[]
@helpers_paths
=
[]
@public_file_server
=
ActiveSupport
::
OrderedOptions
.
new
@public_file_server
.
enabled
=
true
@public_file_server
.
index_name
=
"index"
@force_ssl
=
false
@ssl_options
=
{}
@session_store
=
nil
@time_zone
=
"UTC"
@beginning_of_week
=
:monday
@log_level
=
:debug
@generators
=
app_generators
@cache_store
=
[
:file_store
,
"
#{
root
}
/tmp/cache/"
]
@railties_order
=
[
:all
]
@relative_url_root
=
ENV
[
"RAILS_RELATIVE_URL_ROOT"
]
@reload_classes_only_on_change
=
true
@file_watcher
=
ActiveSupport
::
FileUpdateChecker
@exceptions_app
=
nil
@autoflush_log
=
true
@log_formatter
=
ActiveSupport
::
Logger
::
SimpleFormatter
.
new
@eager_load
=
nil
@secret_token
=
nil
@secret_key_base
=
nil
@api_only
=
false
@debug_exception_response_format
=
nil
@x
=
Custom
.
new
@enable_dependency_loading
=
false
@read_encrypted_secrets
=
false
@content_security_policy
=
nil
@content_security_policy_report_only
=
false
@content_security_policy_nonce_generator
=
nil
@require_master_key
=
false
@loaded_config_version
=
nil
end
def
load_defaults
(
target_version
)
...
...
railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
浏览文件 @
b2f0a894
...
...
@@ -3,6 +3,7 @@
<head>
<title><
%=
camelized
%
></title>
<
%%=
csrf_meta_tags
%
>
<
%%=
csp_meta_tag
%
>
<
%
-
if
options
[
:skip_javascript
]
-
%
>
<
%%=
stylesheet_link_tag
'
application
',
media:
'
all
'
%
>
...
...
railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt
浏览文件 @
b2f0a894
...
...
@@ -10,12 +10,15 @@
# policy.img_src :self, :https, :data
# policy.object_src :none
# policy.script_src :self, :https
# policy.style_src :self, :https
, :unsafe_inline
# policy.style_src :self, :https
# # Specify URI for violation reports
# # policy.report_uri "/csp-violation-report-endpoint"
# end
# If you are using UJS then enable automatic nonce generation
# Rails.application.config.content_security_policy_nonce_generator = -> { SecureRandom.base64(16) }
# Report CSP violations to a specified URI
# For further information see the following documentation:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录