rack-protection 0.1.0 → 1.5.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +13 -3
- 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 +15 -5
- 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 +61 -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 +35 -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 +84 -30
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,12 +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`
|
47
|
+
|
46
48
|
## Cross Site Scripting
|
47
49
|
|
48
50
|
Prevented by:
|
49
51
|
|
50
|
-
* `Rack::Protection::EscapedParams`
|
51
|
-
* `Rack::Protection::
|
52
|
+
* `Rack::Protection::EscapedParams` (not included by `use Rack::Protection`)
|
53
|
+
* `Rack::Protection::XSSHeader` (Internet Explorer only)
|
52
54
|
|
53
55
|
## Clickjacking
|
54
56
|
|
@@ -70,7 +72,6 @@ Prevented by:
|
|
70
72
|
|
71
73
|
## IP Spoofing
|
72
74
|
|
73
|
-
|
74
75
|
Prevented by:
|
75
76
|
|
76
77
|
* `Rack::Protection::IPSpoofing`
|
@@ -78,3 +79,12 @@ Prevented by:
|
|
78
79
|
# Installation
|
79
80
|
|
80
81
|
gem install rack-protection
|
82
|
+
|
83
|
+
# Instrumentation
|
84
|
+
|
85
|
+
Instrumentation is enabled by passing in an instrumenter as an option.
|
86
|
+
```
|
87
|
+
use Rack::Protection, instrumenter: ActiveSupport::Notifications
|
88
|
+
```
|
89
|
+
|
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
|
@@ -7,18 +7,28 @@ module Rack
|
|
7
7
|
# Supported browsers:: all
|
8
8
|
# More infos:: http://flask.pocoo.org/docs/security/#json-security
|
9
9
|
#
|
10
|
-
# JSON GET APIs are
|
10
|
+
# JSON GET APIs are vulnerable to being embedded as JavaScript while the
|
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
|