rack-protection 2.0.0 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/Gemfile +1 -1
- data/README.md +31 -17
- data/Rakefile +9 -1
- data/lib/rack/protection/authenticity_token.rb +143 -24
- data/lib/rack/protection/base.rb +1 -1
- data/lib/rack/protection/content_security_policy.rb +4 -5
- data/lib/rack/protection/http_origin.rb +11 -4
- data/lib/rack/protection/path_traversal.rb +6 -11
- data/lib/rack/protection/referrer_policy.rb +25 -0
- data/lib/rack/protection/session_hijacking.rb +1 -1
- data/lib/rack/protection/version.rb +1 -1
- data/lib/rack/protection.rb +4 -1
- data/rack-protection.gemspec +17 -2
- metadata +14 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: fa9d7fd8b6dd44cfd54fd833a1679efedfccdf76619d0b01e0aa2057f2e8b586
|
4
|
+
data.tar.gz: f0541b4bf7e3cc865bdca886de807968787e90a26ca4fcd3ff3787496e64a9de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 89f4d7bd2b9ee4a1e51fb9a7d9699f0d1d3881a82bb3b8813527f6ce8cce96274462fd502e8df4d5f6b21cbf2d79da9bd69bff6a4e6e7e289e69aa7cb4e18893
|
7
|
+
data.tar.gz: 381cdab10dffb59181caef1a80b46e9d584e1f20fa602843f1649164bbd16d44cbfd5ba5aef2ebd0a447e52fad7437644d29188e98b9998a19a4377abbc0051f
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# Rack::Protection
|
2
2
|
|
3
|
-
[](http://travis-ci.org/sinatra/rack-protection)
|
4
|
-
|
5
3
|
This gem protects against typical web attacks.
|
6
4
|
Should work for all Rack apps, including Rails.
|
7
5
|
|
@@ -40,55 +38,55 @@ run MyApp
|
|
40
38
|
|
41
39
|
Prevented by:
|
42
40
|
|
43
|
-
* `Rack::Protection::AuthenticityToken` (not included by `use Rack::Protection`)
|
44
|
-
* `Rack::Protection::FormToken` (not included by `use Rack::Protection`)
|
45
|
-
* `Rack::Protection::JsonCsrf`
|
46
|
-
* `Rack::Protection::RemoteReferrer` (not included by `use Rack::Protection`)
|
47
|
-
* `Rack::Protection::RemoteToken`
|
48
|
-
* `Rack::Protection::HttpOrigin`
|
41
|
+
* [`Rack::Protection::AuthenticityToken`][authenticity-token] (not included by `use Rack::Protection`)
|
42
|
+
* [`Rack::Protection::FormToken`][form-token] (not included by `use Rack::Protection`)
|
43
|
+
* [`Rack::Protection::JsonCsrf`][json-csrf]
|
44
|
+
* [`Rack::Protection::RemoteReferrer`][remote-referrer] (not included by `use Rack::Protection`)
|
45
|
+
* [`Rack::Protection::RemoteToken`][remote-token]
|
46
|
+
* [`Rack::Protection::HttpOrigin`][http-origin]
|
49
47
|
|
50
48
|
## Cross Site Scripting
|
51
49
|
|
52
50
|
Prevented by:
|
53
51
|
|
54
|
-
* `Rack::Protection::EscapedParams` (not included by `use Rack::Protection`)
|
55
|
-
* `Rack::Protection::XSSHeader` (Internet Explorer and Chrome only)
|
56
|
-
* `Rack::Protection::ContentSecurityPolicy`
|
52
|
+
* [`Rack::Protection::EscapedParams`][escaped-params] (not included by `use Rack::Protection`)
|
53
|
+
* [`Rack::Protection::XSSHeader`][xss-header] (Internet Explorer and Chrome only)
|
54
|
+
* [`Rack::Protection::ContentSecurityPolicy`][content-security-policy]
|
57
55
|
|
58
56
|
## Clickjacking
|
59
57
|
|
60
58
|
Prevented by:
|
61
59
|
|
62
|
-
* `Rack::Protection::FrameOptions`
|
60
|
+
* [`Rack::Protection::FrameOptions`][frame-options]
|
63
61
|
|
64
62
|
## Directory Traversal
|
65
63
|
|
66
64
|
Prevented by:
|
67
65
|
|
68
|
-
* `Rack::Protection::PathTraversal`
|
66
|
+
* [`Rack::Protection::PathTraversal`][path-traversal]
|
69
67
|
|
70
68
|
## Session Hijacking
|
71
69
|
|
72
70
|
Prevented by:
|
73
71
|
|
74
|
-
* `Rack::Protection::SessionHijacking`
|
72
|
+
* [`Rack::Protection::SessionHijacking`][session-hijacking]
|
75
73
|
|
76
74
|
## Cookie Tossing
|
77
75
|
|
78
76
|
Prevented by:
|
79
|
-
* `Rack::Protection::CookieTossing` (not included by `use Rack::Protection`)
|
77
|
+
* [`Rack::Protection::CookieTossing`][cookie-tossing] (not included by `use Rack::Protection`)
|
80
78
|
|
81
79
|
## IP Spoofing
|
82
80
|
|
83
81
|
Prevented by:
|
84
82
|
|
85
|
-
* `Rack::Protection::IPSpoofing`
|
83
|
+
* [`Rack::Protection::IPSpoofing`][ip-spoofing]
|
86
84
|
|
87
85
|
## Helps to protect against protocol downgrade attacks and cookie hijacking
|
88
86
|
|
89
87
|
Prevented by:
|
90
88
|
|
91
|
-
* `Rack::Protection::StrictTransport` (not included by `use Rack::Protection`)
|
89
|
+
* [`Rack::Protection::StrictTransport`][strict-transport] (not included by `use Rack::Protection`)
|
92
90
|
|
93
91
|
# Installation
|
94
92
|
|
@@ -102,3 +100,19 @@ use Rack::Protection, instrumenter: ActiveSupport::Notifications
|
|
102
100
|
```
|
103
101
|
|
104
102
|
The instrumenter is passed a namespace (String) and environment (Hash). The namespace is 'rack.protection' and the attack type can be obtained from the environment key 'rack.protection.attack'.
|
103
|
+
|
104
|
+
[authenticity-token]: http://www.sinatrarb.com/protection/authenticity_token
|
105
|
+
[content-security-policy]: http://www.sinatrarb.com/protection/content_security_policy
|
106
|
+
[cookie-tossing]: http://www.sinatrarb.com/protection/cookie_tossing
|
107
|
+
[escaped-params]: http://www.sinatrarb.com/protection/escaped_params
|
108
|
+
[form-token]: http://www.sinatrarb.com/protection/form_token
|
109
|
+
[frame-options]: http://www.sinatrarb.com/protection/frame_options
|
110
|
+
[http-origin]: http://www.sinatrarb.com/protection/http_origin
|
111
|
+
[ip-spoofing]: http://www.sinatrarb.com/protection/ip_spoofing
|
112
|
+
[json-csrf]: http://www.sinatrarb.com/protection/json_csrf
|
113
|
+
[path-traversal]: http://www.sinatrarb.com/protection/path_traversal
|
114
|
+
[remote-referrer]: http://www.sinatrarb.com/protection/remote_referrer
|
115
|
+
[remote-token]: http://www.sinatrarb.com/protection/remote_token
|
116
|
+
[session-hijacking]: http://www.sinatrarb.com/protection/session_hijacking
|
117
|
+
[strict-transport]: http://www.sinatrarb.com/protection/strict_transport
|
118
|
+
[xss-header]: http://www.sinatrarb.com/protection/xss_header
|
data/Rakefile
CHANGED
@@ -24,7 +24,15 @@ namespace :doc do
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
task :
|
27
|
+
task :index do
|
28
|
+
doc = File.read("README.md")
|
29
|
+
file = "doc/rack-protection-readme.md"
|
30
|
+
Dir.mkdir "doc" unless File.directory? "doc"
|
31
|
+
puts "writing #{file}"
|
32
|
+
File.open(file, "w") { |f| f << doc }
|
33
|
+
end
|
34
|
+
|
35
|
+
task :all => [:readmes, :index]
|
28
36
|
end
|
29
37
|
|
30
38
|
desc "generate documentation"
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rack/protection'
|
2
2
|
require 'securerandom'
|
3
|
+
require 'openssl'
|
3
4
|
require 'base64'
|
4
5
|
|
5
6
|
module Rack
|
@@ -9,54 +10,140 @@ module Rack
|
|
9
10
|
# Supported browsers:: all
|
10
11
|
# More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery
|
11
12
|
#
|
12
|
-
#
|
13
|
-
#
|
13
|
+
# This middleware only accepts requests other than <tt>GET</tt>,
|
14
|
+
# <tt>HEAD</tt>, <tt>OPTIONS</tt>, <tt>TRACE</tt> if their given access
|
15
|
+
# token matches the token included in the session.
|
14
16
|
#
|
15
|
-
#
|
17
|
+
# It checks the <tt>X-CSRF-Token</tt> header and the <tt>POST</tt> form
|
18
|
+
# data.
|
16
19
|
#
|
17
|
-
#
|
20
|
+
# Compatible with the {rack-csrf}[https://rubygems.org/gems/rack_csrf] gem.
|
18
21
|
#
|
19
|
-
#
|
22
|
+
# == Options
|
23
|
+
#
|
24
|
+
# [<tt>:authenticity_param</tt>] the name of the param that should contain
|
25
|
+
# the token on a request. Default value:
|
26
|
+
# <tt>"authenticity_token"</tt>
|
27
|
+
#
|
28
|
+
# [<tt>:key</tt>] the name of the param that should contain
|
29
|
+
# the token in the session. Default value:
|
30
|
+
# <tt>:csrf</tt>
|
31
|
+
#
|
32
|
+
# [<tt>:allow_if</tt>] a proc for custom allow/deny logic. Default value:
|
33
|
+
# <tt>nil</tt>
|
34
|
+
#
|
35
|
+
# == Example: Forms application
|
36
|
+
#
|
37
|
+
# To show what the AuthenticityToken does, this section includes a sample
|
38
|
+
# program which shows two forms. One with, and one without a CSRF token
|
39
|
+
# The one without CSRF token field will get a 403 Forbidden response.
|
40
|
+
#
|
41
|
+
# Install the gem, then run the program:
|
42
|
+
#
|
43
|
+
# gem install 'rack-protection'
|
44
|
+
# ruby server.rb
|
45
|
+
#
|
46
|
+
# Here is <tt>server.rb</tt>:
|
47
|
+
#
|
48
|
+
# require 'rack/protection'
|
49
|
+
#
|
50
|
+
# app = Rack::Builder.app do
|
51
|
+
# use Rack::Session::Cookie, secret: 'secret'
|
52
|
+
# use Rack::Protection::AuthenticityToken
|
53
|
+
#
|
54
|
+
# run -> (env) do
|
55
|
+
# [200, {}, [
|
56
|
+
# <<~EOS
|
57
|
+
# <!DOCTYPE html>
|
58
|
+
# <html lang="en">
|
59
|
+
# <head>
|
60
|
+
# <meta charset="UTF-8" />
|
61
|
+
# <title>rack-protection minimal example</title>
|
62
|
+
# </head>
|
63
|
+
# <body>
|
64
|
+
# <h1>Without Authenticity Token</h1>
|
65
|
+
# <p>This takes you to <tt>Forbidden</tt></p>
|
66
|
+
# <form action="" method="post">
|
67
|
+
# <input type="text" name="foo" />
|
68
|
+
# <input type="submit" />
|
69
|
+
# </form>
|
70
|
+
#
|
71
|
+
# <h1>With Authenticity Token</h1>
|
72
|
+
# <p>This successfully takes you to back to this form.</p>
|
73
|
+
# <form action="" method="post">
|
74
|
+
# <input type="hidden" name="authenticity_token" value="#{Rack::Protection::AuthenticityToken.token(env['rack.session'])}" />
|
75
|
+
# <input type="text" name="foo" />
|
76
|
+
# <input type="submit" />
|
77
|
+
# </form>
|
78
|
+
# </body>
|
79
|
+
# </html>
|
80
|
+
# EOS
|
81
|
+
# ]]
|
82
|
+
# end
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# Rack::Handler::WEBrick.run app
|
86
|
+
#
|
87
|
+
# == Example: Customize which POST parameter holds the token
|
88
|
+
#
|
89
|
+
# To customize the authenticity parameter for form data, use the
|
90
|
+
# <tt>:authenticity_param</tt> option:
|
91
|
+
# use Rack::Protection::AuthenticityToken, authenticity_param: 'your_token_param_name'
|
20
92
|
class AuthenticityToken < Base
|
21
93
|
TOKEN_LENGTH = 32
|
22
94
|
|
23
95
|
default_options :authenticity_param => 'authenticity_token',
|
96
|
+
:key => :csrf,
|
24
97
|
:allow_if => nil
|
25
98
|
|
26
|
-
def self.token(session)
|
27
|
-
self.new(nil).mask_authenticity_token(session)
|
99
|
+
def self.token(session, path: nil, method: :post)
|
100
|
+
self.new(nil).mask_authenticity_token(session, path: path, method: method)
|
28
101
|
end
|
29
102
|
|
30
103
|
def self.random_token
|
31
|
-
SecureRandom.
|
104
|
+
SecureRandom.urlsafe_base64(TOKEN_LENGTH, padding: false)
|
32
105
|
end
|
33
106
|
|
34
107
|
def accepts?(env)
|
35
|
-
session = session
|
108
|
+
session = session(env)
|
36
109
|
set_token(session)
|
37
110
|
|
38
111
|
safe?(env) ||
|
39
|
-
valid_token?(
|
40
|
-
valid_token?(
|
112
|
+
valid_token?(env, env['HTTP_X_CSRF_TOKEN']) ||
|
113
|
+
valid_token?(env, Request.new(env).params[options[:authenticity_param]]) ||
|
41
114
|
( options[:allow_if] && options[:allow_if].call(env) )
|
115
|
+
rescue
|
116
|
+
false
|
42
117
|
end
|
43
118
|
|
44
|
-
def mask_authenticity_token(session)
|
45
|
-
|
119
|
+
def mask_authenticity_token(session, path: nil, method: :post)
|
120
|
+
set_token(session)
|
121
|
+
|
122
|
+
token = if path && method
|
123
|
+
per_form_token(session, path, method)
|
124
|
+
else
|
125
|
+
global_token(session)
|
126
|
+
end
|
127
|
+
|
46
128
|
mask_token(token)
|
47
129
|
end
|
48
130
|
|
131
|
+
GLOBAL_TOKEN_IDENTIFIER = '!real_csrf_token'
|
132
|
+
private_constant :GLOBAL_TOKEN_IDENTIFIER
|
133
|
+
|
49
134
|
private
|
50
135
|
|
51
136
|
def set_token(session)
|
52
|
-
session[:
|
137
|
+
session[options[:key]] ||= self.class.random_token
|
53
138
|
end
|
54
139
|
|
55
140
|
# Checks the client's masked token to see if it matches the
|
56
141
|
# session token.
|
57
|
-
def valid_token?(
|
142
|
+
def valid_token?(env, token)
|
58
143
|
return false if token.nil? || token.empty?
|
59
144
|
|
145
|
+
session = session(env)
|
146
|
+
|
60
147
|
begin
|
61
148
|
token = decode_token(token)
|
62
149
|
rescue ArgumentError # encoded_masked_token is invalid Base64
|
@@ -67,13 +154,13 @@ module Rack
|
|
67
154
|
# to handle any unmasked tokens that we've issued without error.
|
68
155
|
|
69
156
|
if unmasked_token?(token)
|
70
|
-
compare_with_real_token
|
71
|
-
|
157
|
+
compare_with_real_token(token, session)
|
72
158
|
elsif masked_token?(token)
|
73
159
|
token = unmask_token(token)
|
74
160
|
|
75
|
-
|
76
|
-
|
161
|
+
compare_with_global_token(token, session) ||
|
162
|
+
compare_with_real_token(token, session) ||
|
163
|
+
compare_with_per_form_token(token, session, Request.new(env))
|
77
164
|
else
|
78
165
|
false # Token is malformed
|
79
166
|
end
|
@@ -83,7 +170,6 @@ module Rack
|
|
83
170
|
# on each request. The masking is used to mitigate SSL attacks
|
84
171
|
# like BREACH.
|
85
172
|
def mask_token(token)
|
86
|
-
token = decode_token(token)
|
87
173
|
one_time_pad = SecureRandom.random_bytes(token.length)
|
88
174
|
encrypted_token = xor_byte_strings(one_time_pad, token)
|
89
175
|
masked_token = one_time_pad + encrypted_token
|
@@ -112,20 +198,53 @@ module Rack
|
|
112
198
|
secure_compare(token, real_token(session))
|
113
199
|
end
|
114
200
|
|
201
|
+
def compare_with_global_token(token, session)
|
202
|
+
secure_compare(token, global_token(session))
|
203
|
+
end
|
204
|
+
|
205
|
+
def compare_with_per_form_token(token, session, request)
|
206
|
+
secure_compare(token,
|
207
|
+
per_form_token(session, request.path.chomp('/'), request.request_method)
|
208
|
+
)
|
209
|
+
end
|
210
|
+
|
115
211
|
def real_token(session)
|
116
|
-
decode_token(session[:
|
212
|
+
decode_token(session[options[:key]])
|
213
|
+
end
|
214
|
+
|
215
|
+
def global_token(session)
|
216
|
+
token_hmac(session, GLOBAL_TOKEN_IDENTIFIER)
|
217
|
+
end
|
218
|
+
|
219
|
+
def per_form_token(session, path, method)
|
220
|
+
token_hmac(session, "#{path}##{method.downcase}")
|
117
221
|
end
|
118
222
|
|
119
223
|
def encode_token(token)
|
120
|
-
Base64.
|
224
|
+
Base64.urlsafe_encode64(token)
|
121
225
|
end
|
122
226
|
|
123
227
|
def decode_token(token)
|
124
|
-
Base64.
|
228
|
+
Base64.urlsafe_decode64(token)
|
229
|
+
end
|
230
|
+
|
231
|
+
def token_hmac(session, identifier)
|
232
|
+
OpenSSL::HMAC.digest(
|
233
|
+
OpenSSL::Digest::SHA256.new,
|
234
|
+
real_token(session),
|
235
|
+
identifier
|
236
|
+
)
|
125
237
|
end
|
126
238
|
|
127
239
|
def xor_byte_strings(s1, s2)
|
128
|
-
|
240
|
+
s2 = s2.dup
|
241
|
+
size = s1.bytesize
|
242
|
+
i = 0
|
243
|
+
while i < size
|
244
|
+
s2.setbyte(i, s1.getbyte(i) ^ s2.getbyte(i))
|
245
|
+
i += 1
|
246
|
+
end
|
247
|
+
s2
|
129
248
|
end
|
130
249
|
end
|
131
250
|
end
|
data/lib/rack/protection/base.rb
CHANGED
@@ -13,7 +13,7 @@ module Rack
|
|
13
13
|
:session_key => 'rack.session', :status => 403,
|
14
14
|
:allow_empty_referrer => true,
|
15
15
|
:report_key => "protection.failed",
|
16
|
-
:html_types => %w[text/html application/xhtml]
|
16
|
+
:html_types => %w[text/html application/xhtml text/xml application/xml]
|
17
17
|
}
|
18
18
|
|
19
19
|
attr_reader :app, :options
|
@@ -36,16 +36,15 @@ module Rack
|
|
36
36
|
# to be used in a policy.
|
37
37
|
#
|
38
38
|
class ContentSecurityPolicy < Base
|
39
|
-
default_options default_src:
|
40
|
-
img_src: "'self'", style_src: "'self'",
|
41
|
-
connect_src: "'self'", report_only: false
|
39
|
+
default_options default_src: "'self'", report_only: false
|
42
40
|
|
43
41
|
DIRECTIVES = %i(base_uri child_src connect_src default_src
|
44
42
|
font_src form_action frame_ancestors frame_src
|
45
43
|
img_src manifest_src media_src object_src
|
46
44
|
plugin_types referrer reflected_xss report_to
|
47
45
|
report_uri require_sri_for sandbox script_src
|
48
|
-
style_src worker_src
|
46
|
+
style_src worker_src webrtc_src navigate_to
|
47
|
+
prefetch_src).freeze
|
49
48
|
|
50
49
|
NO_ARG_DIRECTIVES = %i(block_all_mixed_content disown_opener
|
51
50
|
upgrade_insecure_requests).freeze
|
@@ -62,7 +61,7 @@ module Rack
|
|
62
61
|
# Set these key values to boolean 'true' to include in policy
|
63
62
|
NO_ARG_DIRECTIVES.each do |d|
|
64
63
|
if options.key?(d) && options[d].is_a?(TrueClass)
|
65
|
-
directives << d.to_s.
|
64
|
+
directives << d.to_s.tr('_', '-')
|
66
65
|
end
|
67
66
|
end
|
68
67
|
|
@@ -9,11 +9,11 @@ module Rack
|
|
9
9
|
# http://tools.ietf.org/html/draft-abarth-origin
|
10
10
|
#
|
11
11
|
# Does not accept unsafe HTTP requests when value of Origin HTTP request header
|
12
|
-
# does not match default or
|
12
|
+
# does not match default or permitted URIs.
|
13
13
|
#
|
14
|
-
# If you want to
|
14
|
+
# If you want to permit a specific domain, you can pass in as the `:permitted_origins` option:
|
15
15
|
#
|
16
|
-
# use Rack::Protection,
|
16
|
+
# use Rack::Protection, permitted_origins: ["http://localhost:3000", "http://127.0.01:3000"]
|
17
17
|
#
|
18
18
|
# The `:allow_if` option can also be set to a proc to use custom allow/deny logic.
|
19
19
|
class HttpOrigin < Base
|
@@ -32,7 +32,14 @@ module Rack
|
|
32
32
|
return true unless origin = env['HTTP_ORIGIN']
|
33
33
|
return true if base_url(env) == origin
|
34
34
|
return true if options[:allow_if] && options[:allow_if].call(env)
|
35
|
-
|
35
|
+
|
36
|
+
if options.key? :origin_whitelist
|
37
|
+
warn env, "Rack::Protection origin_whitelist option is deprecated and will be removed, " \
|
38
|
+
"use permitted_origins instead.\n"
|
39
|
+
end
|
40
|
+
|
41
|
+
permitted_origins = options[:permitted_origins] || options[:origin_whitelist]
|
42
|
+
Array(permitted_origins).include? origin
|
36
43
|
end
|
37
44
|
|
38
45
|
end
|
@@ -19,19 +19,14 @@ module Rack
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def cleanup(path)
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
slash = '/'.encode(encoding)
|
27
|
-
else
|
28
|
-
# Ruby 1.8
|
29
|
-
dot = '.'
|
30
|
-
slash = '/'
|
31
|
-
end
|
22
|
+
encoding = path.encoding
|
23
|
+
dot = '.'.encode(encoding)
|
24
|
+
slash = '/'.encode(encoding)
|
25
|
+
backslash = '\\'.encode(encoding)
|
32
26
|
|
33
27
|
parts = []
|
34
|
-
unescaped = path.gsub(/%2e/i, dot).gsub(/%2f/i, slash)
|
28
|
+
unescaped = path.gsub(/%2e/i, dot).gsub(/%2f/i, slash).gsub(/%5c/i, backslash)
|
29
|
+
unescaped = unescaped.gsub(backslash, slash)
|
35
30
|
|
36
31
|
unescaped.split(slash).each do |part|
|
37
32
|
next if part.empty? or part == dot
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rack/protection'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Protection
|
5
|
+
##
|
6
|
+
# Prevented attack:: Secret leakage, third party tracking
|
7
|
+
# Supported browsers:: mixed support
|
8
|
+
# More infos:: https://www.w3.org/TR/referrer-policy/
|
9
|
+
# https://caniuse.com/#search=referrer-policy
|
10
|
+
#
|
11
|
+
# Sets Referrer-Policy header to tell the browser to limit the Referer header.
|
12
|
+
#
|
13
|
+
# Options:
|
14
|
+
# referrer_policy:: The policy to use (default: 'strict-origin-when-cross-origin')
|
15
|
+
class ReferrerPolicy < Base
|
16
|
+
default_options :referrer_policy => 'strict-origin-when-cross-origin'
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
status, headers, body = @app.call(env)
|
20
|
+
headers['Referrer-Policy'] ||= options[:referrer_policy]
|
21
|
+
[status, headers, body]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -14,7 +14,7 @@ module Rack
|
|
14
14
|
class SessionHijacking < Base
|
15
15
|
default_reaction :drop_session
|
16
16
|
default_options :tracking_key => :tracking, :encrypt_tracking => true,
|
17
|
-
:track => %w[HTTP_USER_AGENT
|
17
|
+
:track => %w[HTTP_USER_AGENT]
|
18
18
|
|
19
19
|
def accepts?(env)
|
20
20
|
session = session env
|
data/lib/rack/protection.rb
CHANGED
@@ -14,6 +14,7 @@ module Rack
|
|
14
14
|
autoload :IPSpoofing, 'rack/protection/ip_spoofing'
|
15
15
|
autoload :JsonCsrf, 'rack/protection/json_csrf'
|
16
16
|
autoload :PathTraversal, 'rack/protection/path_traversal'
|
17
|
+
autoload :ReferrerPolicy, 'rack/protection/referrer_policy'
|
17
18
|
autoload :RemoteReferrer, 'rack/protection/remote_referrer'
|
18
19
|
autoload :RemoteToken, 'rack/protection/remote_token'
|
19
20
|
autoload :SessionHijacking, 'rack/protection/session_hijacking'
|
@@ -32,9 +33,11 @@ module Rack
|
|
32
33
|
Rack::Builder.new do
|
33
34
|
# Off by default, unless added
|
34
35
|
use ::Rack::Protection::AuthenticityToken, options if use_these.include? :authenticity_token
|
35
|
-
use ::Rack::Protection::CookieTossing, options if use_these.include? :cookie_tossing
|
36
36
|
use ::Rack::Protection::ContentSecurityPolicy, options if use_these.include? :content_security_policy
|
37
|
+
use ::Rack::Protection::CookieTossing, options if use_these.include? :cookie_tossing
|
38
|
+
use ::Rack::Protection::EscapedParams, options if use_these.include? :escaped_params
|
37
39
|
use ::Rack::Protection::FormToken, options if use_these.include? :form_token
|
40
|
+
use ::Rack::Protection::ReferrerPolicy, options if use_these.include? :referrer_policy
|
38
41
|
use ::Rack::Protection::RemoteReferrer, options if use_these.include? :remote_referrer
|
39
42
|
use ::Rack::Protection::StrictTransport, options if use_these.include? :strict_transport
|
40
43
|
|
data/rack-protection.gemspec
CHANGED
@@ -5,7 +5,7 @@ Gem::Specification.new do |s|
|
|
5
5
|
s.name = "rack-protection"
|
6
6
|
s.version = version
|
7
7
|
s.description = "Protect against typical web attacks, works with all Rack apps, including Rails."
|
8
|
-
s.homepage = "http://
|
8
|
+
s.homepage = "http://sinatrarb.com/protection/"
|
9
9
|
s.summary = s.description
|
10
10
|
s.license = 'MIT'
|
11
11
|
s.authors = ["https://github.com/sinatra/sinatra/graphs/contributors"]
|
@@ -18,8 +18,23 @@ Gem::Specification.new do |s|
|
|
18
18
|
"rack-protection.gemspec"
|
19
19
|
]
|
20
20
|
|
21
|
+
if s.respond_to?(:metadata)
|
22
|
+
s.metadata = {
|
23
|
+
'source_code_uri' => 'https://github.com/sinatra/sinatra/tree/master/rack-protection',
|
24
|
+
'homepage_uri' => 'http://sinatrarb.com/protection/',
|
25
|
+
'documentation_uri' => 'https://www.rubydoc.info/gems/rack-protection'
|
26
|
+
}
|
27
|
+
else
|
28
|
+
raise <<-EOF
|
29
|
+
RubyGems 2.0 or newer is required to protect against public gem pushes. You can update your rubygems version by running:
|
30
|
+
gem install rubygems-update
|
31
|
+
update_rubygems:
|
32
|
+
gem update --system
|
33
|
+
EOF
|
34
|
+
end
|
35
|
+
|
21
36
|
# dependencies
|
22
37
|
s.add_dependency "rack"
|
23
38
|
s.add_development_dependency "rack-test"
|
24
|
-
s.add_development_dependency "rspec", "~> 3.
|
39
|
+
s.add_development_dependency "rspec", "~> 3.6"
|
25
40
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-protection
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- https://github.com/sinatra/sinatra/graphs/contributors
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-07-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 3.
|
47
|
+
version: '3.6'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 3.
|
54
|
+
version: '3.6'
|
55
55
|
description: Protect against typical web attacks, works with all Rack apps, including
|
56
56
|
Rails.
|
57
57
|
email: sinatrarb@googlegroups.com
|
@@ -76,6 +76,7 @@ files:
|
|
76
76
|
- lib/rack/protection/ip_spoofing.rb
|
77
77
|
- lib/rack/protection/json_csrf.rb
|
78
78
|
- lib/rack/protection/path_traversal.rb
|
79
|
+
- lib/rack/protection/referrer_policy.rb
|
79
80
|
- lib/rack/protection/remote_referrer.rb
|
80
81
|
- lib/rack/protection/remote_token.rb
|
81
82
|
- lib/rack/protection/session_hijacking.rb
|
@@ -83,11 +84,14 @@ files:
|
|
83
84
|
- lib/rack/protection/version.rb
|
84
85
|
- lib/rack/protection/xss_header.rb
|
85
86
|
- rack-protection.gemspec
|
86
|
-
homepage: http://
|
87
|
+
homepage: http://sinatrarb.com/protection/
|
87
88
|
licenses:
|
88
89
|
- MIT
|
89
|
-
metadata:
|
90
|
-
|
90
|
+
metadata:
|
91
|
+
source_code_uri: https://github.com/sinatra/sinatra/tree/master/rack-protection
|
92
|
+
homepage_uri: http://sinatrarb.com/protection/
|
93
|
+
documentation_uri: https://www.rubydoc.info/gems/rack-protection
|
94
|
+
post_install_message:
|
91
95
|
rdoc_options: []
|
92
96
|
require_paths:
|
93
97
|
- lib
|
@@ -102,9 +106,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
102
106
|
- !ruby/object:Gem::Version
|
103
107
|
version: '0'
|
104
108
|
requirements: []
|
105
|
-
|
106
|
-
|
107
|
-
signing_key:
|
109
|
+
rubygems_version: 3.0.3.1
|
110
|
+
signing_key:
|
108
111
|
specification_version: 4
|
109
112
|
summary: Protect against typical web attacks, works with all Rack apps, including
|
110
113
|
Rails.
|