rack-protection 4.0.0 → 4.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8762f8b7bc260c94e353c6c9eb9a24d3a0efe695cb5904f526a5a84094f567db
4
- data.tar.gz: d3c7e05bf8bfea7c6b077025f330429f386b416bccce5ebfab2d91d0513e437b
3
+ metadata.gz: 127cb2d5f6bf0c9efee234cdf611e9f8760bd97a7933436f0df194ac6d46773e
4
+ data.tar.gz: ee71fb517c167af11a5d53ead6acb59a4a488b53baa11178b336ff584c8de65c
5
5
  SHA512:
6
- metadata.gz: 48b9fffade45349249d1122c3acc8618f2cb8ca05cde4ce1959b063899faad7c5c433bf6481518beedd0a0dd2ff28c878b1f5898fc3f21629ea39233fa747dc6
7
- data.tar.gz: ec3176d39b37dfdd97b4d230f98251681a93c6dddcb17db4659c998d8f296dc9fc97808a9b732bbf055d0d2b0504f9696f541b6d6864983b214df94ceff636a5
6
+ metadata.gz: 689e749c54382ad1b574a1feb6cccb0944e86136869809670a346534088ccfe1d2322ac835cd036d1a0747f4e6bfc8d2b245cb99cc9963971393f9bb0495fde2
7
+ data.tar.gz: b22f5e1659e9271e0b159a7de8f6dd3744befff2a4a06c1f20567ab63fe5c73e953077de77f86a66ce76ba867d62d629abf3c24d8031405a78215a01676b0c30
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.2.1'
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,13 @@
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.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - https://github.com/sinatra/sinatra/graphs/contributors
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-01-19 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: base64
@@ -24,6 +23,20 @@ dependencies:
24
23
  - - ">="
25
24
  - !ruby/object:Gem::Version
26
25
  version: 0.1.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: logger
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.6.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 1.6.0
27
40
  - !ruby/object:Gem::Dependency
28
41
  name: rack
29
42
  requirement: !ruby/object:Gem::Requirement
@@ -64,6 +77,7 @@ files:
64
77
  - lib/rack/protection/escaped_params.rb
65
78
  - lib/rack/protection/form_token.rb
66
79
  - lib/rack/protection/frame_options.rb
80
+ - lib/rack/protection/host_authorization.rb
67
81
  - lib/rack/protection/http_origin.rb
68
82
  - lib/rack/protection/ip_spoofing.rb
69
83
  - lib/rack/protection/json_csrf.rb
@@ -85,7 +99,6 @@ metadata:
85
99
  homepage_uri: http://sinatrarb.com/protection/
86
100
  documentation_uri: https://www.rubydoc.info/gems/rack-protection
87
101
  rubygems_mfa_required: 'true'
88
- post_install_message:
89
102
  rdoc_options: []
90
103
  require_paths:
91
104
  - lib
@@ -100,8 +113,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
100
113
  - !ruby/object:Gem::Version
101
114
  version: '0'
102
115
  requirements: []
103
- rubygems_version: 3.5.3
104
- signing_key:
116
+ rubygems_version: 3.6.9
105
117
  specification_version: 4
106
118
  summary: Protect against typical web attacks, works with all Rack apps, including
107
119
  Rails.