rack-protection 4.0.0 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8762f8b7bc260c94e353c6c9eb9a24d3a0efe695cb5904f526a5a84094f567db
4
- data.tar.gz: d3c7e05bf8bfea7c6b077025f330429f386b416bccce5ebfab2d91d0513e437b
3
+ metadata.gz: 4c04192c3b3ef137443ac72495e4d948a544c1f7c7fd514f422045b870071b54
4
+ data.tar.gz: 5fc1d9022ef2d26e1c18dd56752883d3d99c6293fbd2a67708628bd414ab2f3f
5
5
  SHA512:
6
- metadata.gz: 48b9fffade45349249d1122c3acc8618f2cb8ca05cde4ce1959b063899faad7c5c433bf6481518beedd0a0dd2ff28c878b1f5898fc3f21629ea39233fa747dc6
7
- data.tar.gz: ec3176d39b37dfdd97b4d230f98251681a93c6dddcb17db4659c998d8f296dc9fc97808a9b732bbf055d0d2b0504f9696f541b6d6864983b214df94ceff636a5
6
+ metadata.gz: 26d822dde064625a3520d8bd95b0b2499e4d94d081e8943bfeb4283cab18fd0f70d98b7e5e3cdea5298a75edc70a0a5fc44444e5563fab90db179136901758b6
7
+ data.tar.gz: d9618b6af6ffc69006e8404a1dc18dc4bd082a516042f25d804613e67438a27830f81b3fc87f2f82d2c7ff5e6607d461e30117515bf2f0be9ea36a0ff094f28d
data/README.md CHANGED
@@ -34,6 +34,10 @@ run MyApp
34
34
 
35
35
  # Prevented Attacks
36
36
 
37
+ ## DNS rebinding and other Host header attacks
38
+
39
+ * [`Rack::Protection::HostAuthorization`][host-authorization] (not included by `use Rack::Protection`)
40
+
37
41
  ## Cross Site Request Forgery
38
42
 
39
43
  Prevented by:
@@ -109,6 +113,7 @@ The instrumenter is passed a namespace (String) and environment (Hash). The name
109
113
  [escaped-params]: http://www.sinatrarb.com/protection/escaped_params
110
114
  [form-token]: http://www.sinatrarb.com/protection/form_token
111
115
  [frame-options]: http://www.sinatrarb.com/protection/frame_options
116
+ [host-authorization]: https://github.com/sinatra/sinatra/blob/main/rack-protection/lib/rack/protection/host_authorization.rb
112
117
  [http-origin]: http://www.sinatrarb.com/protection/http_origin
113
118
  [ip-spoofing]: http://www.sinatrarb.com/protection/ip_spoofing
114
119
  [json-csrf]: http://www.sinatrarb.com/protection/json_csrf
@@ -46,15 +46,15 @@ module Rack
46
46
  # Install the gem, then run the program:
47
47
  #
48
48
  # gem install 'rack-protection'
49
- # ruby server.rb
49
+ # puma server.ru
50
50
  #
51
- # Here is <tt>server.rb</tt>:
51
+ # Here is <tt>server.ru</tt>:
52
52
  #
53
53
  # require 'rack/protection'
54
54
  # require 'rack/session'
55
55
  #
56
56
  # app = Rack::Builder.app do
57
- # use Rack::Session::Cookie, secret: 'secret'
57
+ # use Rack::Session::Cookie, secret: 'CHANGEMEaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
58
58
  # use Rack::Protection::AuthenticityToken
59
59
  #
60
60
  # run -> (env) do
@@ -88,7 +88,7 @@ module Rack
88
88
  # end
89
89
  # end
90
90
  #
91
- # Rack::Handler::WEBrick.run app
91
+ # run app
92
92
  #
93
93
  # == Example: Customize which POST parameter holds the token
94
94
  #
@@ -58,6 +58,13 @@ module Rack
58
58
  result if (Array === result) && (result.size == 3)
59
59
  end
60
60
 
61
+ def debug(env, message)
62
+ return unless options[:logging]
63
+
64
+ l = options[:logger] || env['rack.logger'] || ::Logger.new(env['rack.errors'])
65
+ l.debug(message)
66
+ end
67
+
61
68
  def warn(env, message)
62
69
  return unless options[:logging]
63
70
 
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/protection'
4
+ require 'ipaddr'
5
+
6
+ module Rack
7
+ module Protection
8
+ ##
9
+ # Prevented attack:: DNS rebinding and other Host header attacks
10
+ # Supported browsers:: all
11
+ # More infos:: https://en.wikipedia.org/wiki/DNS_rebinding
12
+ # https://portswigger.net/web-security/host-header
13
+ #
14
+ # Blocks HTTP requests with an unrecognized hostname in any of the following
15
+ # HTTP headers: Host, X-Forwarded-Host, Forwarded
16
+ #
17
+ # If you want to permit a specific hostname, you can pass in as the `:permitted_hosts` option:
18
+ #
19
+ # use Rack::Protection::HostAuthorization, permitted_hosts: ["www.example.org", "sinatrarb.com"]
20
+ #
21
+ # The `:allow_if` option can also be set to a proc to use custom allow/deny logic.
22
+ class HostAuthorization < Base
23
+ DOT = '.'
24
+ PORT_REGEXP = /:\d+\z/.freeze
25
+ SUBDOMAINS = /[a-z0-9\-.]+/.freeze
26
+ private_constant :DOT,
27
+ :PORT_REGEXP,
28
+ :SUBDOMAINS
29
+ default_reaction :deny
30
+ default_options allow_if: nil,
31
+ message: 'Host not permitted'
32
+
33
+ def initialize(*)
34
+ super
35
+ @permitted_hosts = []
36
+ @domain_hosts = []
37
+ @ip_hosts = []
38
+ @all_permitted_hosts = Array(options[:permitted_hosts])
39
+
40
+ @all_permitted_hosts.each do |host|
41
+ case host
42
+ when String
43
+ if host.start_with?(DOT)
44
+ domain = host[1..-1]
45
+ @permitted_hosts << domain.downcase
46
+ @domain_hosts << /\A#{SUBDOMAINS}#{Regexp.escape(domain)}\z/i
47
+ else
48
+ @permitted_hosts << host.downcase
49
+ end
50
+ when IPAddr then @ip_hosts << host
51
+ end
52
+ end
53
+ end
54
+
55
+ def accepts?(env)
56
+ return true if options[:allow_if]&.call(env)
57
+ return true if @all_permitted_hosts.empty?
58
+
59
+ request = Request.new(env)
60
+ origin_host = extract_host(request.host_authority)
61
+ forwarded_host = extract_host(request.forwarded_authority)
62
+
63
+ debug env, "#{self.class} " \
64
+ "@all_permitted_hosts=#{@all_permitted_hosts.inspect} " \
65
+ "@permitted_hosts=#{@permitted_hosts.inspect} " \
66
+ "@domain_hosts=#{@domain_hosts.inspect} " \
67
+ "@ip_hosts=#{@ip_hosts.inspect} " \
68
+ "origin_host=#{origin_host.inspect} " \
69
+ "forwarded_host=#{forwarded_host.inspect}"
70
+
71
+ if host_permitted?(origin_host)
72
+ if forwarded_host.nil?
73
+ true
74
+ else
75
+ host_permitted?(forwarded_host)
76
+ end
77
+ else
78
+ false
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def extract_host(authority)
85
+ authority.to_s.split(PORT_REGEXP).first&.downcase
86
+ end
87
+
88
+ def host_permitted?(host)
89
+ exact_match?(host) || domain_match?(host) || ip_match?(host)
90
+ end
91
+
92
+ def exact_match?(host)
93
+ @permitted_hosts.include?(host)
94
+ end
95
+
96
+ def domain_match?(host)
97
+ return false if host.nil?
98
+ return false if host.start_with?(DOT)
99
+
100
+ @domain_hosts.any? { |domain_host| host.match?(domain_host) }
101
+ end
102
+
103
+ def ip_match?(host)
104
+ @ip_hosts.any? { |ip_host| ip_host.include?(host) }
105
+ rescue IPAddr::InvalidAddressError
106
+ false
107
+ end
108
+ end
109
+ end
110
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rack
4
4
  module Protection
5
- VERSION = '4.0.0'
5
+ VERSION = '4.1.0'
6
6
  end
7
7
  end
@@ -12,6 +12,7 @@ module Rack
12
12
  autoload :EscapedParams, 'rack/protection/escaped_params'
13
13
  autoload :FormToken, 'rack/protection/form_token'
14
14
  autoload :FrameOptions, 'rack/protection/frame_options'
15
+ autoload :HostAuthorization, 'rack/protection/host_authorization'
15
16
  autoload :HttpOrigin, 'rack/protection/http_origin'
16
17
  autoload :IPSpoofing, 'rack/protection/ip_spoofing'
17
18
  autoload :JsonCsrf, 'rack/protection/json_csrf'
@@ -40,5 +40,6 @@ RubyGems 2.0 or newer is required to protect against public gem pushes. You can
40
40
 
41
41
  # dependencies
42
42
  s.add_dependency 'base64', '>= 0.1.0'
43
+ s.add_dependency 'logger', '>= 1.6.0'
43
44
  s.add_dependency 'rack', '>= 3.0.0', '< 4'
44
45
  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: 4.0.0
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - https://github.com/sinatra/sinatra/graphs/contributors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-19 00:00:00.000000000 Z
11
+ date: 2024-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.1.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: logger
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.6.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 1.6.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rack
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -64,6 +78,7 @@ files:
64
78
  - lib/rack/protection/escaped_params.rb
65
79
  - lib/rack/protection/form_token.rb
66
80
  - lib/rack/protection/frame_options.rb
81
+ - lib/rack/protection/host_authorization.rb
67
82
  - lib/rack/protection/http_origin.rb
68
83
  - lib/rack/protection/ip_spoofing.rb
69
84
  - lib/rack/protection/json_csrf.rb
@@ -100,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
100
115
  - !ruby/object:Gem::Version
101
116
  version: '0'
102
117
  requirements: []
103
- rubygems_version: 3.5.3
118
+ rubygems_version: 3.5.22
104
119
  signing_key:
105
120
  specification_version: 4
106
121
  summary: Protect against typical web attacks, works with all Rack apps, including