rack-protection 2.0.8.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ca62cac87c00c55967df5d002e0244e1eabddcdf973ea007556b102760c66a74
4
+ data.tar.gz: ee1ed6aed9320fee7d1e85fdb708bf34226cfb18815220f8f8ab0bafe87f4629
5
+ SHA512:
6
+ metadata.gz: c2493f8c141991bbba4697f60f3a84b361a5472d4fc8f370974eba485f2f910aaf51ab6b3a9246479a41ffc72f619d2587e0f987fdfb75536f0cb41115bc2b95
7
+ data.tar.gz: ddb4160be67e1f13f16514d0a3959c39e417232cb708a84c9be99985c3502178fb718d4218a3b651c54d932582fb370628f0a5ed5815aa7ed39765cbf24c8de1
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "https://rubygems.org"
2
+ # encoding: utf-8
3
+
4
+ gem 'rake'
5
+
6
+ rack_version = ENV['rack'].to_s
7
+ rack_version = nil if rack_version.empty? or rack_version == 'stable'
8
+ rack_version = {:github => 'rack/rack'} if rack_version == 'master'
9
+ gem 'rack', rack_version
10
+
11
+ gem 'sinatra', path: '..'
12
+
13
+ gemspec
data/License ADDED
@@ -0,0 +1,23 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2011-2017 Konstantin Haase
4
+ Copyright (c) 2015-2017 Zachary Scott
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ 'Software'), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,118 @@
1
+ # Rack::Protection
2
+
3
+ This gem protects against typical web attacks.
4
+ Should work for all Rack apps, including Rails.
5
+
6
+ # Usage
7
+
8
+ Use all protections you probably want to use:
9
+
10
+ ``` ruby
11
+ # config.ru
12
+ require 'rack/protection'
13
+ use Rack::Protection
14
+ run MyApp
15
+ ```
16
+
17
+ Skip a single protection middleware:
18
+
19
+ ``` ruby
20
+ # config.ru
21
+ require 'rack/protection'
22
+ use Rack::Protection, :except => :path_traversal
23
+ run MyApp
24
+ ```
25
+
26
+ Use a single protection middleware:
27
+
28
+ ``` ruby
29
+ # config.ru
30
+ require 'rack/protection'
31
+ use Rack::Protection::AuthenticityToken
32
+ run MyApp
33
+ ```
34
+
35
+ # Prevented Attacks
36
+
37
+ ## Cross Site Request Forgery
38
+
39
+ Prevented by:
40
+
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]
47
+
48
+ ## Cross Site Scripting
49
+
50
+ Prevented by:
51
+
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]
55
+
56
+ ## Clickjacking
57
+
58
+ Prevented by:
59
+
60
+ * [`Rack::Protection::FrameOptions`][frame-options]
61
+
62
+ ## Directory Traversal
63
+
64
+ Prevented by:
65
+
66
+ * [`Rack::Protection::PathTraversal`][path-traversal]
67
+
68
+ ## Session Hijacking
69
+
70
+ Prevented by:
71
+
72
+ * [`Rack::Protection::SessionHijacking`][session-hijacking]
73
+
74
+ ## Cookie Tossing
75
+
76
+ Prevented by:
77
+ * [`Rack::Protection::CookieTossing`][cookie-tossing] (not included by `use Rack::Protection`)
78
+
79
+ ## IP Spoofing
80
+
81
+ Prevented by:
82
+
83
+ * [`Rack::Protection::IPSpoofing`][ip-spoofing]
84
+
85
+ ## Helps to protect against protocol downgrade attacks and cookie hijacking
86
+
87
+ Prevented by:
88
+
89
+ * [`Rack::Protection::StrictTransport`][strict-transport] (not included by `use Rack::Protection`)
90
+
91
+ # Installation
92
+
93
+ gem install rack-protection
94
+
95
+ # Instrumentation
96
+
97
+ Instrumentation is enabled by passing in an instrumenter as an option.
98
+ ```
99
+ use Rack::Protection, instrumenter: ActiveSupport::Notifications
100
+ ```
101
+
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
@@ -0,0 +1,72 @@
1
+ # encoding: utf-8
2
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
3
+
4
+ begin
5
+ require 'bundler'
6
+ Bundler::GemHelper.install_tasks
7
+ rescue LoadError => e
8
+ $stderr.puts e
9
+ end
10
+
11
+ desc "run specs"
12
+ task(:spec) { ruby '-S rspec spec' }
13
+
14
+ namespace :doc do
15
+ task :readmes do
16
+ Dir.glob 'lib/rack/protection/*.rb' do |file|
17
+ excluded_files = %w[lib/rack/protection/base.rb lib/rack/protection/version.rb]
18
+ next if excluded_files.include?(file)
19
+ doc = File.read(file)[/^ module Protection(\n)+( #[^\n]*\n)*/m].scan(/^ *#(?!#) ?(.*)\n/).join("\n")
20
+ file = "doc/#{file[4..-4].tr("/_", "-")}.rdoc"
21
+ Dir.mkdir "doc" unless File.directory? "doc"
22
+ puts "writing #{file}"
23
+ File.open(file, "w") { |f| f << doc }
24
+ end
25
+ end
26
+
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]
36
+ end
37
+
38
+ desc "generate documentation"
39
+ task :doc => 'doc:all'
40
+
41
+ desc "generate gemspec"
42
+ task 'rack-protection.gemspec' do
43
+ require 'rack/protection/version'
44
+ content = File.binread 'rack-protection.gemspec'
45
+
46
+ # fetch data
47
+ fields = {
48
+ :authors => `git shortlog -sn`.force_encoding('utf-8').scan(/[^\d\s].*/),
49
+ :email => ["mail@zzak.io", "konstantin.haase@gmail.com"],
50
+ :files => %w(License README.md Rakefile Gemfile rack-protection.gemspec) + Dir['lib/**/*']
51
+ }
52
+
53
+ # insert data
54
+ fields.each do |field, values|
55
+ updated = " s.#{field} = ["
56
+ updated << values.map { |v| "\n %p" % v }.join(',')
57
+ updated << "\n ]"
58
+ content.sub!(/ s\.#{field} = \[\n( .*\n)* \]/, updated)
59
+ end
60
+
61
+ # set version
62
+ content.sub! /(s\.version.*=\s+).*/, "\\1\"#{Rack::Protection::VERSION}\""
63
+
64
+ # escape unicode
65
+ content.gsub!(/./) { |c| c.bytesize > 1 ? "\\u{#{c.codepoints.first.to_s(16)}}" : c }
66
+
67
+ File.open('rack-protection.gemspec', 'w') { |f| f << content }
68
+ end
69
+
70
+ task :gemspec => 'rack-protection.gemspec'
71
+ task :default => :spec
72
+ task :test => :spec
@@ -0,0 +1 @@
1
+ require "rack/protection"
@@ -0,0 +1,54 @@
1
+ require 'rack/protection/version'
2
+ require 'rack'
3
+
4
+ module Rack
5
+ module Protection
6
+ autoload :AuthenticityToken, 'rack/protection/authenticity_token'
7
+ autoload :Base, 'rack/protection/base'
8
+ autoload :CookieTossing, 'rack/protection/cookie_tossing'
9
+ autoload :ContentSecurityPolicy, 'rack/protection/content_security_policy'
10
+ autoload :EscapedParams, 'rack/protection/escaped_params'
11
+ autoload :FormToken, 'rack/protection/form_token'
12
+ autoload :FrameOptions, 'rack/protection/frame_options'
13
+ autoload :HttpOrigin, 'rack/protection/http_origin'
14
+ autoload :IPSpoofing, 'rack/protection/ip_spoofing'
15
+ autoload :JsonCsrf, 'rack/protection/json_csrf'
16
+ autoload :PathTraversal, 'rack/protection/path_traversal'
17
+ autoload :RemoteReferrer, 'rack/protection/remote_referrer'
18
+ autoload :RemoteToken, 'rack/protection/remote_token'
19
+ autoload :SessionHijacking, 'rack/protection/session_hijacking'
20
+ autoload :StrictTransport, 'rack/protection/strict_transport'
21
+ autoload :XSSHeader, 'rack/protection/xss_header'
22
+
23
+ def self.new(app, options = {})
24
+ # does not include: RemoteReferrer, AuthenticityToken and FormToken
25
+ except = Array options[:except]
26
+ use_these = Array options[:use]
27
+
28
+ if options.fetch(:without_session, false)
29
+ except += [:session_hijacking, :remote_token]
30
+ end
31
+
32
+ Rack::Builder.new do
33
+ # Off by default, unless added
34
+ use ::Rack::Protection::AuthenticityToken, options if use_these.include? :authenticity_token
35
+ use ::Rack::Protection::CookieTossing, options if use_these.include? :cookie_tossing
36
+ use ::Rack::Protection::ContentSecurityPolicy, options if use_these.include? :content_security_policy
37
+ use ::Rack::Protection::FormToken, options if use_these.include? :form_token
38
+ use ::Rack::Protection::RemoteReferrer, options if use_these.include? :remote_referrer
39
+ use ::Rack::Protection::StrictTransport, options if use_these.include? :strict_transport
40
+
41
+ # On by default, unless skipped
42
+ use ::Rack::Protection::FrameOptions, options unless except.include? :frame_options
43
+ use ::Rack::Protection::HttpOrigin, options unless except.include? :http_origin
44
+ use ::Rack::Protection::IPSpoofing, options unless except.include? :ip_spoofing
45
+ use ::Rack::Protection::JsonCsrf, options unless except.include? :json_csrf
46
+ use ::Rack::Protection::PathTraversal, options unless except.include? :path_traversal
47
+ use ::Rack::Protection::RemoteToken, options unless except.include? :remote_token
48
+ use ::Rack::Protection::SessionHijacking, options unless except.include? :session_hijacking
49
+ use ::Rack::Protection::XSSHeader, options unless except.include? :xss_header
50
+ run app
51
+ end.to_app
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,196 @@
1
+ require 'rack/protection'
2
+ require 'securerandom'
3
+ require 'base64'
4
+
5
+ module Rack
6
+ module Protection
7
+ ##
8
+ # Prevented attack:: CSRF
9
+ # Supported browsers:: all
10
+ # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery
11
+ #
12
+ # This middleware only accepts requests other than <tt>GET</tt>,
13
+ # <tt>HEAD</tt>, <tt>OPTIONS</tt>, <tt>TRACE</tt> if their given access
14
+ # token matches the token included in the session.
15
+ #
16
+ # It checks the <tt>X-CSRF-Token</tt> header and the <tt>POST</tt> form
17
+ # data.
18
+ #
19
+ # Compatible with the {rack-csrf}[https://rubygems.org/gems/rack_csrf] gem.
20
+ #
21
+ # == Options
22
+ #
23
+ # [<tt>:authenticity_param</tt>] the name of the param that should contain
24
+ # the token on a request. Default value:
25
+ # <tt>"authenticity_token"</tt>
26
+ #
27
+ # == Example: Forms application
28
+ #
29
+ # To show what the AuthenticityToken does, this section includes a sample
30
+ # program which shows two forms. One with, and one without a CSRF token
31
+ # The one without CSRF token field will get a 403 Forbidden response.
32
+ #
33
+ # Install the gem, then run the program:
34
+ #
35
+ # gem install 'rack-protection'
36
+ # ruby server.rb
37
+ #
38
+ # Here is <tt>server.rb</tt>:
39
+ #
40
+ # require 'rack/protection'
41
+ #
42
+ # app = Rack::Builder.app do
43
+ # use Rack::Session::Cookie, secret: 'secret'
44
+ # use Rack::Protection::AuthenticityToken
45
+ #
46
+ # run -> (env) do
47
+ # [200, {}, [
48
+ # <<~EOS
49
+ # <!DOCTYPE html>
50
+ # <html lang="en">
51
+ # <head>
52
+ # <meta charset="UTF-8" />
53
+ # <title>rack-protection minimal example</title>
54
+ # </head>
55
+ # <body>
56
+ # <h1>Without Authenticity Token</h1>
57
+ # <p>This takes you to <tt>Forbidden</tt></p>
58
+ # <form action="" method="post">
59
+ # <input type="text" name="foo" />
60
+ # <input type="submit" />
61
+ # </form>
62
+ #
63
+ # <h1>With Authenticity Token</h1>
64
+ # <p>This successfully takes you to back to this form.</p>
65
+ # <form action="" method="post">
66
+ # <input type="hidden" name="authenticity_token" value="#{env['rack.session'][:csrf]}" />
67
+ # <input type="text" name="foo" />
68
+ # <input type="submit" />
69
+ # </form>
70
+ # </body>
71
+ # </html>
72
+ # EOS
73
+ # ]]
74
+ # end
75
+ # end
76
+ #
77
+ # Rack::Handler::WEBrick.run app
78
+ #
79
+ # == Example: Customize which POST parameter holds the token
80
+ #
81
+ # To customize the authenticity parameter for form data, use the
82
+ # <tt>:authenticity_param</tt> option:
83
+ # use Rack::Protection::AuthenticityToken, authenticity_param: 'your_token_param_name'
84
+ class AuthenticityToken < Base
85
+ TOKEN_LENGTH = 32
86
+
87
+ default_options :authenticity_param => 'authenticity_token',
88
+ :allow_if => nil
89
+
90
+ def self.token(session)
91
+ self.new(nil).mask_authenticity_token(session)
92
+ end
93
+
94
+ def self.random_token
95
+ SecureRandom.base64(TOKEN_LENGTH)
96
+ end
97
+
98
+ def accepts?(env)
99
+ session = session env
100
+ set_token(session)
101
+
102
+ safe?(env) ||
103
+ valid_token?(session, env['HTTP_X_CSRF_TOKEN']) ||
104
+ valid_token?(session, Request.new(env).params[options[:authenticity_param]]) ||
105
+ ( options[:allow_if] && options[:allow_if].call(env) )
106
+ end
107
+
108
+ def mask_authenticity_token(session)
109
+ token = set_token(session)
110
+ mask_token(token)
111
+ end
112
+
113
+ private
114
+
115
+ def set_token(session)
116
+ session[:csrf] ||= self.class.random_token
117
+ end
118
+
119
+ # Checks the client's masked token to see if it matches the
120
+ # session token.
121
+ def valid_token?(session, token)
122
+ return false if token.nil? || token.empty?
123
+
124
+ begin
125
+ token = decode_token(token)
126
+ rescue ArgumentError # encoded_masked_token is invalid Base64
127
+ return false
128
+ end
129
+
130
+ # See if it's actually a masked token or not. We should be able
131
+ # to handle any unmasked tokens that we've issued without error.
132
+
133
+ if unmasked_token?(token)
134
+ compare_with_real_token token, session
135
+
136
+ elsif masked_token?(token)
137
+ token = unmask_token(token)
138
+
139
+ compare_with_real_token token, session
140
+
141
+ else
142
+ false # Token is malformed
143
+ end
144
+ end
145
+
146
+ # Creates a masked version of the authenticity token that varies
147
+ # on each request. The masking is used to mitigate SSL attacks
148
+ # like BREACH.
149
+ def mask_token(token)
150
+ token = decode_token(token)
151
+ one_time_pad = SecureRandom.random_bytes(token.length)
152
+ encrypted_token = xor_byte_strings(one_time_pad, token)
153
+ masked_token = one_time_pad + encrypted_token
154
+ encode_token(masked_token)
155
+ end
156
+
157
+ # Essentially the inverse of +mask_token+.
158
+ def unmask_token(masked_token)
159
+ # Split the token into the one-time pad and the encrypted
160
+ # value and decrypt it
161
+ token_length = masked_token.length / 2
162
+ one_time_pad = masked_token[0...token_length]
163
+ encrypted_token = masked_token[token_length..-1]
164
+ xor_byte_strings(one_time_pad, encrypted_token)
165
+ end
166
+
167
+ def unmasked_token?(token)
168
+ token.length == TOKEN_LENGTH
169
+ end
170
+
171
+ def masked_token?(token)
172
+ token.length == TOKEN_LENGTH * 2
173
+ end
174
+
175
+ def compare_with_real_token(token, session)
176
+ secure_compare(token, real_token(session))
177
+ end
178
+
179
+ def real_token(session)
180
+ decode_token(session[:csrf])
181
+ end
182
+
183
+ def encode_token(token)
184
+ Base64.strict_encode64(token)
185
+ end
186
+
187
+ def decode_token(token)
188
+ Base64.strict_decode64(token)
189
+ end
190
+
191
+ def xor_byte_strings(s1, s2)
192
+ s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*')
193
+ end
194
+ end
195
+ end
196
+ end