rack-protection 1.0.0 → 1.5.5
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 +7 -0
- data/README.md +9 -13
- data/Rakefile +15 -4
- data/lib/rack/protection/authenticity_token.rb +10 -3
- data/lib/rack/protection/base.rb +52 -4
- data/lib/rack/protection/escaped_params.rb +34 -8
- data/lib/rack/protection/frame_options.rb +14 -3
- data/lib/rack/protection/http_origin.rb +32 -0
- data/lib/rack/protection/ip_spoofing.rb +1 -1
- data/lib/rack/protection/json_csrf.rb +14 -4
- data/lib/rack/protection/path_traversal.rb +26 -5
- data/lib/rack/protection/remote_referrer.rb +0 -3
- data/lib/rack/protection/session_hijacking.rb +5 -5
- data/lib/rack/protection/version.rb +6 -34
- data/lib/rack/protection/xss_header.rb +4 -6
- data/lib/rack/protection.rb +13 -8
- data/rack-protection.gemspec +57 -4
- data/spec/authenticity_token_spec.rb +15 -0
- data/spec/base_spec.rb +40 -0
- data/spec/escaped_params_spec.rb +9 -0
- data/spec/frame_options_spec.rb +19 -4
- data/spec/http_origin_spec.rb +38 -0
- data/spec/json_csrf_spec.rb +26 -0
- data/spec/path_traversal_spec.rb +20 -2
- data/spec/protection_spec.rb +100 -0
- data/spec/session_hijacking_spec.rb +19 -4
- data/spec/spec_helper.rb +7 -1
- data/spec/xss_header_spec.rb +35 -3
- metadata +82 -32
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2b7d78da301d9f7fc81ae73e46a389c2b8ce10ab8121f169fd760018ac506d47
|
4
|
+
data.tar.gz: a91bd28f8624f325d6714262ac11d83e0a347405f14e600780cb1bfd846e5b34
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0c5de92c0283313c00d50c1f9a219c808ad587caabff81c4d1530abd8f0e7d9c0f3753ad9bab7c06a29ce97b2a717fddc04ced642adff058f3431419286e4da6
|
7
|
+
data.tar.gz: d3bf5830bf30475871b73ba54ee38f962bd93c2e1f420b59b649d6dcb7f97d89f091d3add3f692ba847ffe5f5c6cade665d522c86220087d977fb3706e41bd58
|
data/README.md
CHANGED
@@ -43,13 +43,14 @@ Prevented by:
|
|
43
43
|
* `Rack::Protection::JsonCsrf`
|
44
44
|
* `Rack::Protection::RemoteReferrer` (not included by `use Rack::Protection`)
|
45
45
|
* `Rack::Protection::RemoteToken`
|
46
|
+
* `Rack::Protection::HttpOrigin`
|
46
47
|
|
47
48
|
## Cross Site Scripting
|
48
49
|
|
49
50
|
Prevented by:
|
50
51
|
|
51
|
-
* `Rack::Protection::EscapedParams`
|
52
|
-
* `Rack::Protection::
|
52
|
+
* `Rack::Protection::EscapedParams` (not included by `use Rack::Protection`)
|
53
|
+
* `Rack::Protection::XSSHeader` (Internet Explorer only)
|
53
54
|
|
54
55
|
## Clickjacking
|
55
56
|
|
@@ -79,16 +80,11 @@ Prevented by:
|
|
79
80
|
|
80
81
|
gem install rack-protection
|
81
82
|
|
82
|
-
#
|
83
|
+
# Instrumentation
|
83
84
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
## v1.0.0 (2011/09/02)
|
89
|
-
|
90
|
-
First stable release.
|
91
|
-
|
92
|
-
Changes:
|
85
|
+
Instrumentation is enabled by passing in an instrumenter as an option.
|
86
|
+
```
|
87
|
+
use Rack::Protection, instrumenter: ActiveSupport::Notifications
|
88
|
+
```
|
93
89
|
|
94
|
-
|
90
|
+
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'.
|
data/Rakefile
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
2
3
|
|
3
4
|
begin
|
@@ -13,14 +14,19 @@ task(:spec) { ruby '-S rspec spec' }
|
|
13
14
|
desc "generate gemspec"
|
14
15
|
task 'rack-protection.gemspec' do
|
15
16
|
require 'rack/protection/version'
|
16
|
-
content = File.
|
17
|
+
content = File.binread 'rack-protection.gemspec'
|
17
18
|
|
19
|
+
# fetch data
|
18
20
|
fields = {
|
19
|
-
:authors => `git shortlog -sn`.scan(/[^\d\s].*/),
|
20
|
-
:email => `git shortlog -sne`.scan(/[^<]+@[^>]+/),
|
21
|
-
:files => `git ls-files`.split("\n").reject { |f| f =~ /^(\.|Gemfile)/ }
|
21
|
+
:authors => `git shortlog -sn`.force_encoding('utf-8').scan(/[^\d\s].*/),
|
22
|
+
:email => `git shortlog -sne`.force_encoding('utf-8').scan(/[^<]+@[^>]+/),
|
23
|
+
:files => `git ls-files`.force_encoding('utf-8').split("\n").reject { |f| f =~ /^(\.|Gemfile)/ }
|
22
24
|
}
|
23
25
|
|
26
|
+
# double email :(
|
27
|
+
fields[:email].delete("konstantin.haase@gmail.com")
|
28
|
+
|
29
|
+
# insert data
|
24
30
|
fields.each do |field, values|
|
25
31
|
updated = " s.#{field} = ["
|
26
32
|
updated << values.map { |v| "\n %p" % v }.join(',')
|
@@ -28,7 +34,12 @@ task 'rack-protection.gemspec' do
|
|
28
34
|
content.sub!(/ s\.#{field} = \[\n( .*\n)* \]/, updated)
|
29
35
|
end
|
30
36
|
|
37
|
+
# set version
|
31
38
|
content.sub! /(s\.version.*=\s+).*/, "\\1\"#{Rack::Protection::VERSION}\""
|
39
|
+
|
40
|
+
# escape unicode
|
41
|
+
content.gsub!(/./) { |c| c.bytesize > 1 ? "\\u{#{c.codepoints.first.to_s(16)}}" : c }
|
42
|
+
|
32
43
|
File.open('rack-protection.gemspec', 'w') { |f| f << content }
|
33
44
|
end
|
34
45
|
|
@@ -11,13 +11,20 @@ module Rack
|
|
11
11
|
# included in the session.
|
12
12
|
#
|
13
13
|
# Compatible with Rails and rack-csrf.
|
14
|
+
#
|
15
|
+
# Options:
|
16
|
+
#
|
17
|
+
# authenticity_param: Defines the param's name that should contain the token on a request.
|
18
|
+
#
|
14
19
|
class AuthenticityToken < Base
|
20
|
+
default_options :authenticity_param => 'authenticity_token'
|
21
|
+
|
15
22
|
def accepts?(env)
|
16
|
-
return true if safe? env
|
17
23
|
session = session env
|
18
24
|
token = session[:csrf] ||= session['_csrf_token'] || random_string
|
19
|
-
env
|
20
|
-
|
25
|
+
safe?(env) ||
|
26
|
+
secure_compare(env['HTTP_X_CSRF_TOKEN'].to_s, token) ||
|
27
|
+
secure_compare(Request.new(env).params[options[:authenticity_param]].to_s, token)
|
21
28
|
end
|
22
29
|
end
|
23
30
|
end
|
data/lib/rack/protection/base.rb
CHANGED
@@ -10,7 +10,9 @@ module Rack
|
|
10
10
|
:reaction => :default_reaction, :logging => true,
|
11
11
|
:message => 'Forbidden', :encryptor => Digest::SHA1,
|
12
12
|
:session_key => 'rack.session', :status => 403,
|
13
|
-
:allow_empty_referrer => true
|
13
|
+
:allow_empty_referrer => true,
|
14
|
+
:report_key => "protection.failed",
|
15
|
+
:html_types => %w[text/html application/xhtml]
|
14
16
|
}
|
15
17
|
|
16
18
|
attr_reader :app, :options
|
@@ -41,7 +43,7 @@ module Rack
|
|
41
43
|
|
42
44
|
def call(env)
|
43
45
|
unless accepts? env
|
44
|
-
|
46
|
+
instrument env
|
45
47
|
result = react env
|
46
48
|
end
|
47
49
|
result or app.call(env)
|
@@ -58,10 +60,22 @@ module Rack
|
|
58
60
|
l.warn(message)
|
59
61
|
end
|
60
62
|
|
63
|
+
def instrument(env)
|
64
|
+
return unless i = options[:instrumenter]
|
65
|
+
env['rack.protection.attack'] = self.class.name.split('::').last.downcase
|
66
|
+
i.instrument('rack.protection', env)
|
67
|
+
end
|
68
|
+
|
61
69
|
def deny(env)
|
70
|
+
warn env, "attack prevented by #{self.class}"
|
62
71
|
[options[:status], {'Content-Type' => 'text/plain'}, [options[:message]]]
|
63
72
|
end
|
64
73
|
|
74
|
+
def report(env)
|
75
|
+
warn env, "attack reported by #{self.class}"
|
76
|
+
env[options[:report_key]] = true
|
77
|
+
end
|
78
|
+
|
65
79
|
def session?(env)
|
66
80
|
env.include? options[:session_key]
|
67
81
|
end
|
@@ -79,11 +93,16 @@ module Rack
|
|
79
93
|
ref = env['HTTP_REFERER'].to_s
|
80
94
|
return if !options[:allow_empty_referrer] and ref.empty?
|
81
95
|
URI.parse(ref).host || Request.new(env).host
|
96
|
+
rescue URI::InvalidURIError
|
97
|
+
end
|
98
|
+
|
99
|
+
def origin(env)
|
100
|
+
env['HTTP_ORIGIN'] || env['HTTP_X_ORIGIN']
|
82
101
|
end
|
83
102
|
|
84
103
|
def random_string(secure = defined? SecureRandom)
|
85
|
-
secure ? SecureRandom.hex(
|
86
|
-
rescue
|
104
|
+
secure ? SecureRandom.hex(16) : "%032x" % rand(2**128-1)
|
105
|
+
rescue NotImplementedError
|
87
106
|
random_string false
|
88
107
|
end
|
89
108
|
|
@@ -91,7 +110,36 @@ module Rack
|
|
91
110
|
options[:encryptor].hexdigest value.to_s
|
92
111
|
end
|
93
112
|
|
113
|
+
# The implementations of secure_compare and bytesize are taken from
|
114
|
+
# Rack::Utils to be able to support rack older than XXXX.
|
115
|
+
def secure_compare(a, b)
|
116
|
+
return false unless bytesize(a) == bytesize(b)
|
117
|
+
|
118
|
+
l = a.unpack("C*")
|
119
|
+
|
120
|
+
r, i = 0, -1
|
121
|
+
b.each_byte { |v| r |= v ^ l[i+=1] }
|
122
|
+
r == 0
|
123
|
+
end
|
124
|
+
|
125
|
+
# Return the bytesize of String; uses String#size under Ruby 1.8 and
|
126
|
+
# String#bytesize under 1.9.
|
127
|
+
if ''.respond_to?(:bytesize)
|
128
|
+
def bytesize(string)
|
129
|
+
string.bytesize
|
130
|
+
end
|
131
|
+
else
|
132
|
+
def bytesize(string)
|
133
|
+
string.size
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
94
137
|
alias default_reaction deny
|
138
|
+
|
139
|
+
def html?(headers)
|
140
|
+
return false unless header = headers.detect { |k,v| k.downcase == 'content-type' }
|
141
|
+
options[:html_types].include? header.last[/^\w+\/\w+/]
|
142
|
+
end
|
95
143
|
end
|
96
144
|
end
|
97
145
|
end
|
@@ -1,5 +1,10 @@
|
|
1
1
|
require 'rack/protection'
|
2
|
-
require '
|
2
|
+
require 'rack/utils'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'escape_utils'
|
6
|
+
rescue LoadError
|
7
|
+
end
|
3
8
|
|
4
9
|
module Rack
|
5
10
|
module Protection
|
@@ -16,14 +21,28 @@ module Rack
|
|
16
21
|
# escape:: What escaping modes to use, should be Symbol or Array of Symbols.
|
17
22
|
# Available: :html (default), :javascript, :url
|
18
23
|
class EscapedParams < Base
|
19
|
-
|
24
|
+
extend Rack::Utils
|
25
|
+
|
26
|
+
class << self
|
27
|
+
alias escape_url escape
|
28
|
+
public :escape_html
|
29
|
+
end
|
30
|
+
|
31
|
+
default_options :escape => :html,
|
32
|
+
:escaper => defined?(EscapeUtils) ? EscapeUtils : self
|
20
33
|
|
21
34
|
def initialize(*)
|
22
35
|
super
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
36
|
+
|
37
|
+
modes = Array options[:escape]
|
38
|
+
@escaper = options[:escaper]
|
39
|
+
@html = modes.include? :html
|
40
|
+
@javascript = modes.include? :javascript
|
41
|
+
@url = modes.include? :url
|
42
|
+
|
43
|
+
if @javascript and not @escaper.respond_to? :escape_javascript
|
44
|
+
fail("Use EscapeUtils for JavaScript escaping.")
|
45
|
+
end
|
27
46
|
end
|
28
47
|
|
29
48
|
def call(env)
|
@@ -32,7 +51,7 @@ module Rack
|
|
32
51
|
post_was = handle(request.POST) rescue nil
|
33
52
|
app.call env
|
34
53
|
ensure
|
35
|
-
request.GET.replace get_was
|
54
|
+
request.GET.replace get_was if get_was
|
36
55
|
request.POST.replace post_was if post_was
|
37
56
|
end
|
38
57
|
|
@@ -47,7 +66,7 @@ module Rack
|
|
47
66
|
when Hash then escape_hash(object)
|
48
67
|
when Array then object.map { |o| escape(o) }
|
49
68
|
when String then escape_string(object)
|
50
|
-
else
|
69
|
+
else nil
|
51
70
|
end
|
52
71
|
end
|
53
72
|
|
@@ -56,6 +75,13 @@ module Rack
|
|
56
75
|
hash.each { |k,v| hash[k] = escape(v) }
|
57
76
|
hash
|
58
77
|
end
|
78
|
+
|
79
|
+
def escape_string(str)
|
80
|
+
str = @escaper.escape_url(str) if @url
|
81
|
+
str = @escaper.escape_html(str) if @html
|
82
|
+
str = @escaper.escape_javascript(str) if @javascript
|
83
|
+
str
|
84
|
+
end
|
59
85
|
end
|
60
86
|
end
|
61
87
|
end
|
@@ -16,10 +16,21 @@ module Rack
|
|
16
16
|
# frame_options:: Defines who should be allowed to embed the page in a
|
17
17
|
# frame. Use :deny to forbid any embedding, :sameorigin
|
18
18
|
# to allow embedding from the same origin (default).
|
19
|
-
class FrameOptions <
|
19
|
+
class FrameOptions < Base
|
20
20
|
default_options :frame_options => :sameorigin
|
21
|
-
|
22
|
-
|
21
|
+
|
22
|
+
def frame_options
|
23
|
+
@frame_options ||= begin
|
24
|
+
frame_options = options[:frame_options]
|
25
|
+
frame_options = options[:frame_options].to_s.upcase unless frame_options.respond_to? :to_str
|
26
|
+
frame_options.to_str
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def call(env)
|
31
|
+
status, headers, body = @app.call(env)
|
32
|
+
headers['X-Frame-Options'] ||= frame_options if html? headers
|
33
|
+
[status, headers, body]
|
23
34
|
end
|
24
35
|
end
|
25
36
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rack/protection'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Protection
|
5
|
+
##
|
6
|
+
# Prevented attack:: CSRF
|
7
|
+
# Supported browsers:: Google Chrome 2, Safari 4 and later
|
8
|
+
# More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery
|
9
|
+
# http://tools.ietf.org/html/draft-abarth-origin
|
10
|
+
#
|
11
|
+
# Does not accept unsafe HTTP requests when value of Origin HTTP request header
|
12
|
+
# does not match default or whitelisted URIs.
|
13
|
+
class HttpOrigin < Base
|
14
|
+
DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
|
15
|
+
default_reaction :deny
|
16
|
+
|
17
|
+
def base_url(env)
|
18
|
+
request = Rack::Request.new(env)
|
19
|
+
port = ":#{request.port}" unless request.port == DEFAULT_PORTS[request.scheme]
|
20
|
+
"#{request.scheme}://#{request.host}#{port}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def accepts?(env)
|
24
|
+
return true if safe? env
|
25
|
+
return true unless origin = env['HTTP_ORIGIN']
|
26
|
+
return true if base_url(env) == origin
|
27
|
+
Array(options[:origin_whitelist]).include? origin
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -13,7 +13,7 @@ module Rack
|
|
13
13
|
|
14
14
|
def accepts?(env)
|
15
15
|
return true unless env.include? 'HTTP_X_FORWARDED_FOR'
|
16
|
-
ips = env['HTTP_X_FORWARDED_FOR'].split
|
16
|
+
ips = env['HTTP_X_FORWARDED_FOR'].split(/\s*,\s*/)
|
17
17
|
return false if env.include? 'HTTP_CLIENT_IP' and not ips.include? env['HTTP_CLIENT_IP']
|
18
18
|
return false if env.include? 'HTTP_X_REAL_IP' and not ips.include? env['HTTP_X_REAL_IP']
|
19
19
|
true
|
@@ -11,14 +11,24 @@ module Rack
|
|
11
11
|
# Array prototype has been patched to track data. Checks the referrer
|
12
12
|
# even on GET requests if the content type is JSON.
|
13
13
|
class JsonCsrf < Base
|
14
|
-
|
14
|
+
alias react deny
|
15
15
|
|
16
16
|
def call(env)
|
17
|
+
request = Request.new(env)
|
17
18
|
status, headers, body = app.call(env)
|
18
|
-
|
19
|
-
|
19
|
+
|
20
|
+
if has_vector? request, headers
|
21
|
+
warn env, "attack prevented by #{self.class}"
|
22
|
+
react(env) or [status, headers, body]
|
23
|
+
else
|
24
|
+
[status, headers, body]
|
20
25
|
end
|
21
|
-
|
26
|
+
end
|
27
|
+
|
28
|
+
def has_vector?(request, headers)
|
29
|
+
return false if request.xhr?
|
30
|
+
return false unless headers['Content-Type'].to_s.split(';', 2).first =~ /^\s*application\/json\s*$/
|
31
|
+
origin(request.env).nil? and referrer(request.env) != request.host
|
22
32
|
end
|
23
33
|
end
|
24
34
|
end
|
@@ -12,17 +12,38 @@ module Rack
|
|
12
12
|
class PathTraversal < Base
|
13
13
|
def call(env)
|
14
14
|
path_was = env["PATH_INFO"]
|
15
|
-
env["PATH_INFO"] = cleanup path_was
|
15
|
+
env["PATH_INFO"] = cleanup path_was if path_was && !path_was.empty?
|
16
16
|
app.call env
|
17
17
|
ensure
|
18
18
|
env["PATH_INFO"] = path_was
|
19
19
|
end
|
20
20
|
|
21
21
|
def cleanup(path)
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
if path.respond_to?(:encoding)
|
23
|
+
# Ruby 1.9+ M17N
|
24
|
+
encoding = path.encoding
|
25
|
+
dot = '.'.encode(encoding)
|
26
|
+
slash = '/'.encode(encoding)
|
27
|
+
backslash = '\\'.encode(encoding)
|
28
|
+
else
|
29
|
+
# Ruby 1.8
|
30
|
+
dot = '.'
|
31
|
+
slash = '/'
|
32
|
+
backslash = '\\'
|
33
|
+
end
|
34
|
+
|
35
|
+
parts = []
|
36
|
+
unescaped = path.gsub(/%2e/i, dot).gsub(/%2f/i, slash).gsub(/%5c/i, backslash)
|
37
|
+
unescaped = unescaped.gsub(backslash, slash)
|
38
|
+
|
39
|
+
unescaped.split(slash).each do |part|
|
40
|
+
next if part.empty? or part == dot
|
41
|
+
part == '..' ? parts.pop : parts << part
|
42
|
+
end
|
43
|
+
|
44
|
+
cleaned = slash + parts.join(slash)
|
45
|
+
cleaned << slash if parts.any? and unescaped =~ %r{/\.{0,2}$}
|
46
|
+
cleaned
|
26
47
|
end
|
27
48
|
end
|
28
49
|
end
|
@@ -9,9 +9,6 @@ module Rack
|
|
9
9
|
#
|
10
10
|
# Does not accept unsafe HTTP requests if the Referer [sic] header is set to
|
11
11
|
# a different host.
|
12
|
-
#
|
13
|
-
# Combine with NoReferrer to also block remote requests from non-HTTP pages
|
14
|
-
# (FTP/HTTPS/...).
|
15
12
|
class RemoteReferrer < Base
|
16
13
|
default_reaction :deny
|
17
14
|
|
@@ -9,13 +9,12 @@ module Rack
|
|
9
9
|
#
|
10
10
|
# Tracks request properties like the user agent in the session and empties
|
11
11
|
# the session if those properties change. This essentially prevents attacks
|
12
|
-
# from Firesheep. Since all headers taken into consideration
|
13
|
-
# spoofed, too, this will not prevent
|
12
|
+
# from Firesheep. Since all headers taken into consideration can be
|
13
|
+
# spoofed, too, this will not prevent determined hijacking attempts.
|
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
|
18
|
-
HTTP_VERSION]
|
17
|
+
:track => %w[HTTP_USER_AGENT HTTP_ACCEPT_LANGUAGE]
|
19
18
|
|
20
19
|
def accepts?(env)
|
21
20
|
session = session env
|
@@ -29,7 +28,8 @@ module Rack
|
|
29
28
|
end
|
30
29
|
|
31
30
|
def encrypt(value)
|
32
|
-
|
31
|
+
value = value.to_s.downcase
|
32
|
+
options[:encrypt_tracking] ? super(value) : value
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
@@ -4,41 +4,13 @@ module Rack
|
|
4
4
|
VERSION
|
5
5
|
end
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
SIGNATURE = [1, 5, 5]
|
8
|
+
VERSION = SIGNATURE.join('.')
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
SIGNATURE
|
14
|
-
STRING = SIGNATURE.join '.'
|
15
|
-
|
16
|
-
def self.major; MAJOR end
|
17
|
-
def self.minor; MINOR end
|
18
|
-
def self.tiny; TINY end
|
19
|
-
def self.to_s; STRING end
|
20
|
-
|
21
|
-
def self.hash
|
22
|
-
STRING.hash
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.<=>(other)
|
26
|
-
other = other.split('.').map { |i| i.to_i } if other.respond_to? :split
|
27
|
-
SIGNATURE <=> Array(other)
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.inspect
|
31
|
-
STRING.inspect
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.respond_to?(meth, *)
|
35
|
-
meth.to_s !~ /^__|^to_str$/ and STRING.respond_to? meth unless super
|
36
|
-
end
|
37
|
-
|
38
|
-
def self.method_missing(meth, *args, &block)
|
39
|
-
return super unless STRING.respond_to?(meth)
|
40
|
-
STRING.send(meth, *args, &block)
|
41
|
-
end
|
10
|
+
VERSION.extend Comparable
|
11
|
+
def VERSION.<=>(other)
|
12
|
+
other = other.split('.').map { |i| i.to_i } if other.respond_to? :split
|
13
|
+
SIGNATURE <=> Array(other)
|
42
14
|
end
|
43
15
|
end
|
44
16
|
end
|
@@ -12,15 +12,13 @@ module Rack
|
|
12
12
|
# Options:
|
13
13
|
# xss_mode:: How the browser should prevent the attack (default: :block)
|
14
14
|
class XSSHeader < Base
|
15
|
-
default_options :xss_mode => :block
|
16
|
-
|
17
|
-
def header
|
18
|
-
{ 'X-XSS-Protection' => "1; mode=#{options[:xss_mode]}" }
|
19
|
-
end
|
15
|
+
default_options :xss_mode => :block, :nosniff => true
|
20
16
|
|
21
17
|
def call(env)
|
22
18
|
status, headers, body = @app.call(env)
|
23
|
-
[
|
19
|
+
headers['X-XSS-Protection'] ||= "1; mode=#{options[:xss_mode]}" if html? headers
|
20
|
+
headers['X-Content-Type-Options'] ||= 'nosniff' if options[:nosniff]
|
21
|
+
[status, headers, body]
|
24
22
|
end
|
25
23
|
end
|
26
24
|
end
|
data/lib/rack/protection.rb
CHANGED
@@ -8,6 +8,7 @@ module Rack
|
|
8
8
|
autoload :EscapedParams, 'rack/protection/escaped_params'
|
9
9
|
autoload :FormToken, 'rack/protection/form_token'
|
10
10
|
autoload :FrameOptions, 'rack/protection/frame_options'
|
11
|
+
autoload :HttpOrigin, 'rack/protection/http_origin'
|
11
12
|
autoload :IPSpoofing, 'rack/protection/ip_spoofing'
|
12
13
|
autoload :JsonCsrf, 'rack/protection/json_csrf'
|
13
14
|
autoload :PathTraversal, 'rack/protection/path_traversal'
|
@@ -19,15 +20,19 @@ module Rack
|
|
19
20
|
def self.new(app, options = {})
|
20
21
|
# does not include: RemoteReferrer, AuthenticityToken and FormToken
|
21
22
|
except = Array options[:except]
|
23
|
+
use_these = Array options[:use]
|
22
24
|
Rack::Builder.new do
|
23
|
-
use
|
24
|
-
use
|
25
|
-
use
|
26
|
-
use
|
27
|
-
use
|
28
|
-
use
|
29
|
-
use
|
30
|
-
use
|
25
|
+
use ::Rack::Protection::RemoteReferrer, options if use_these.include? :remote_referrer
|
26
|
+
use ::Rack::Protection::AuthenticityToken,options if use_these.include? :authenticity_token
|
27
|
+
use ::Rack::Protection::FormToken, options if use_these.include? :form_token
|
28
|
+
use ::Rack::Protection::FrameOptions, options unless except.include? :frame_options
|
29
|
+
use ::Rack::Protection::HttpOrigin, options unless except.include? :http_origin
|
30
|
+
use ::Rack::Protection::IPSpoofing, options unless except.include? :ip_spoofing
|
31
|
+
use ::Rack::Protection::JsonCsrf, options unless except.include? :json_csrf
|
32
|
+
use ::Rack::Protection::PathTraversal, options unless except.include? :path_traversal
|
33
|
+
use ::Rack::Protection::RemoteToken, options unless except.include? :remote_token
|
34
|
+
use ::Rack::Protection::SessionHijacking, options unless except.include? :session_hijacking
|
35
|
+
use ::Rack::Protection::XSSHeader, options unless except.include? :xss_header
|
31
36
|
run app
|
32
37
|
end.to_app
|
33
38
|
end
|