rack-protection-monkey 1.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/License +20 -0
  3. data/README.md +90 -0
  4. data/Rakefile +48 -0
  5. data/lib/rack-protection.rb +1 -0
  6. data/lib/rack/protection.rb +40 -0
  7. data/lib/rack/protection/authenticity_token.rb +31 -0
  8. data/lib/rack/protection/base.rb +121 -0
  9. data/lib/rack/protection/escaped_params.rb +87 -0
  10. data/lib/rack/protection/form_token.rb +23 -0
  11. data/lib/rack/protection/frame_options.rb +37 -0
  12. data/lib/rack/protection/http_origin.rb +34 -0
  13. data/lib/rack/protection/ip_spoofing.rb +23 -0
  14. data/lib/rack/protection/json_csrf.rb +35 -0
  15. data/lib/rack/protection/path_traversal.rb +47 -0
  16. data/lib/rack/protection/remote_referrer.rb +20 -0
  17. data/lib/rack/protection/remote_token.rb +22 -0
  18. data/lib/rack/protection/session_hijacking.rb +36 -0
  19. data/lib/rack/protection/version.rb +16 -0
  20. data/lib/rack/protection/xss_header.rb +25 -0
  21. data/rack-protection.gemspec +123 -0
  22. data/spec/lib/rack/protection/authenticity_token_spec.rb +46 -0
  23. data/spec/lib/rack/protection/base_spec.rb +38 -0
  24. data/spec/lib/rack/protection/escaped_params_spec.rb +41 -0
  25. data/spec/lib/rack/protection/form_token_spec.rb +31 -0
  26. data/spec/lib/rack/protection/frame_options_spec.rb +37 -0
  27. data/spec/lib/rack/protection/http_origin_spec.rb +40 -0
  28. data/spec/lib/rack/protection/ip_spoofing_spec.rb +33 -0
  29. data/spec/lib/rack/protection/json_csrf_spec.rb +56 -0
  30. data/spec/lib/rack/protection/path_traversal_spec.rb +39 -0
  31. data/spec/lib/rack/protection/protection_spec.rb +103 -0
  32. data/spec/lib/rack/protection/remote_referrer_spec.rb +29 -0
  33. data/spec/lib/rack/protection/remote_token_spec.rb +40 -0
  34. data/spec/lib/rack/protection/session_hijacking_spec.rb +53 -0
  35. data/spec/lib/rack/protection/xss_header_spec.rb +54 -0
  36. data/spec/spec_helper.rb +86 -0
  37. data/spec/support/dummy_app.rb +7 -0
  38. data/spec/support/not_implemented_as_pending.rb +23 -0
  39. data/spec/support/rack_monkey_patches.rb +21 -0
  40. data/spec/support/shared_examples.rb +65 -0
  41. data/spec/support/spec_helpers.rb +36 -0
  42. metadata +180 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f75c546a977915d22bdaf2e32eb1d903dc5e9f08
4
+ data.tar.gz: 617f770ae255145766f78710f51fce0994965b84
5
+ SHA512:
6
+ metadata.gz: 6673074dd7b647fc40803fb2cc409097205857576dbb2456c3d3fe06a676926432816c268f1dea8d10e094b81fedba213608bd1f9eed05a926d6b6ad81f236d5
7
+ data.tar.gz: 2106e5478e00ac4b20451a820327721630a18b285efe69f0ea6c39c8b6c8c0494aaffc3802984b8e6ffebd0e222dd0352403e3a9390f627cb287cc1605098fe9
data/License ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Konstantin Haase
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ 'Software'), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,90 @@
1
+ You should use 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` (not included by `use Rack::Protection`)
42
+ * `Rack::Protection::FormToken` (not included by `use Rack::Protection`)
43
+ * `Rack::Protection::JsonCsrf`
44
+ * `Rack::Protection::RemoteReferrer` (not included by `use Rack::Protection`)
45
+ * `Rack::Protection::RemoteToken`
46
+ * `Rack::Protection::HttpOrigin`
47
+
48
+ ## Cross Site Scripting
49
+
50
+ Prevented by:
51
+
52
+ * `Rack::Protection::EscapedParams` (not included by `use Rack::Protection`)
53
+ * `Rack::Protection::XSSHeader` (Internet Explorer only)
54
+
55
+ ## Clickjacking
56
+
57
+ Prevented by:
58
+
59
+ * `Rack::Protection::FrameOptions`
60
+
61
+ ## Directory Traversal
62
+
63
+ Prevented by:
64
+
65
+ * `Rack::Protection::PathTraversal`
66
+
67
+ ## Session Hijacking
68
+
69
+ Prevented by:
70
+
71
+ * `Rack::Protection::SessionHijacking`
72
+
73
+ ## IP Spoofing
74
+
75
+ Prevented by:
76
+
77
+ * `Rack::Protection::IPSpoofing`
78
+
79
+ # Installation
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'.
@@ -0,0 +1,48 @@
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
+ desc "generate gemspec"
15
+ task 'rack-protection.gemspec' do
16
+ require 'rack/protection/version'
17
+ content = File.binread 'rack-protection.gemspec'
18
+
19
+ # fetch data
20
+ fields = {
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)/ }
24
+ }
25
+
26
+ # double email :(
27
+ fields[:email].delete("konstantin.haase@gmail.com")
28
+
29
+ # insert data
30
+ fields.each do |field, values|
31
+ updated = " s.#{field} = ["
32
+ updated << values.map { |v| "\n %p" % v }.join(',')
33
+ updated << "\n ]"
34
+ content.sub!(/ s\.#{field} = \[\n( .*\n)* \]/, updated)
35
+ end
36
+
37
+ # set version
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
+
43
+ File.open('rack-protection.gemspec', 'w') { |f| f << content }
44
+ end
45
+
46
+ task :gemspec => 'rack-protection.gemspec'
47
+ task :default => :spec
48
+ task :test => :spec
@@ -0,0 +1 @@
1
+ require "rack/protection"
@@ -0,0 +1,40 @@
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 :EscapedParams, 'rack/protection/escaped_params'
9
+ autoload :FormToken, 'rack/protection/form_token'
10
+ autoload :FrameOptions, 'rack/protection/frame_options'
11
+ autoload :HttpOrigin, 'rack/protection/http_origin'
12
+ autoload :IPSpoofing, 'rack/protection/ip_spoofing'
13
+ autoload :JsonCsrf, 'rack/protection/json_csrf'
14
+ autoload :PathTraversal, 'rack/protection/path_traversal'
15
+ autoload :RemoteReferrer, 'rack/protection/remote_referrer'
16
+ autoload :RemoteToken, 'rack/protection/remote_token'
17
+ autoload :SessionHijacking, 'rack/protection/session_hijacking'
18
+ autoload :XSSHeader, 'rack/protection/xss_header'
19
+
20
+ def self.new(app, options = {})
21
+ # does not include: RemoteReferrer, AuthenticityToken and FormToken
22
+ except = Array options[:except]
23
+ use_these = Array options[:use]
24
+ Rack::Builder.new do
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
36
+ run app
37
+ end.to_app
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,31 @@
1
+ require 'rack/protection'
2
+
3
+ module Rack
4
+ module Protection
5
+ ##
6
+ # Prevented attack:: CSRF
7
+ # Supported browsers:: all
8
+ # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery
9
+ #
10
+ # Only accepts unsafe HTTP requests if a given access token matches the token
11
+ # included in the session.
12
+ #
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
+ #
19
+ class AuthenticityToken < Base
20
+ default_options :authenticity_param => 'authenticity_token'
21
+
22
+ def accepts?(env)
23
+ session = session env
24
+ token = session[:csrf] ||= session['_csrf_token'] || random_string
25
+ safe?(env) ||
26
+ env['HTTP_X_CSRF_TOKEN'] == token ||
27
+ Request.new(env).params[options[:authenticity_param]] == token
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,121 @@
1
+ require 'rack/protection'
2
+ require 'digest'
3
+ require 'logger'
4
+ require 'uri'
5
+
6
+ module Rack
7
+ module Protection
8
+ class Base
9
+ DEFAULT_OPTIONS = {
10
+ :reaction => :default_reaction, :logging => true,
11
+ :message => 'Forbidden', :encryptor => Digest::SHA1,
12
+ :session_key => 'rack.session', :status => 403,
13
+ :allow_empty_referrer => true,
14
+ :report_key => "protection.failed",
15
+ :html_types => %w[text/html application/xhtml]
16
+ }
17
+
18
+ attr_reader :app, :options
19
+
20
+ def self.default_options(options)
21
+ define_method(:default_options) { super().merge(options) }
22
+ end
23
+
24
+ def self.default_reaction(reaction)
25
+ alias_method(:default_reaction, reaction)
26
+ end
27
+
28
+ def default_options
29
+ DEFAULT_OPTIONS
30
+ end
31
+
32
+ def initialize(app, options = {})
33
+ @app, @options = app, default_options.merge(options)
34
+ end
35
+
36
+ def safe?(env)
37
+ %w[GET HEAD OPTIONS TRACE].include? env['REQUEST_METHOD']
38
+ end
39
+
40
+ def accepts?(env)
41
+ raise NotImplementedError, "#{self.class} implementation pending"
42
+ end
43
+
44
+ def call(env)
45
+ unless accepts? env
46
+ instrument env
47
+ result = react env
48
+ end
49
+ result or app.call(env)
50
+ end
51
+
52
+ def react(env)
53
+ result = send(options[:reaction], env)
54
+ result if Array === result and result.size == 3
55
+ end
56
+
57
+ def warn(env, message)
58
+ return unless options[:logging]
59
+ l = options[:logger] || env['rack.logger'] || ::Logger.new(env['rack.errors'])
60
+ l.warn(message)
61
+ end
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
+
69
+ def deny(env)
70
+ warn env, "attack prevented by #{self.class}"
71
+ [options[:status], {'Content-Type' => 'text/plain'}, [options[:message]]]
72
+ end
73
+
74
+ def report(env)
75
+ warn env, "attack reported by #{self.class}"
76
+ env[options[:report_key]] = true
77
+ end
78
+
79
+ def session?(env)
80
+ env.include? options[:session_key]
81
+ end
82
+
83
+ def session(env)
84
+ return env[options[:session_key]] if session? env
85
+ fail "you need to set up a session middleware *before* #{self.class}"
86
+ end
87
+
88
+ def drop_session(env)
89
+ session(env).clear if session? env
90
+ end
91
+
92
+ def referrer(env)
93
+ ref = env['HTTP_REFERER'].to_s
94
+ return if !options[:allow_empty_referrer] and ref.empty?
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']
101
+ end
102
+
103
+ def random_string(secure = defined? SecureRandom)
104
+ secure ? SecureRandom.hex(16) : "%032x" % rand(2**128-1)
105
+ rescue NotImplementedError
106
+ random_string false
107
+ end
108
+
109
+ def encrypt(value)
110
+ options[:encryptor].hexdigest value.to_s
111
+ end
112
+
113
+ alias default_reaction deny
114
+
115
+ def html?(headers)
116
+ return false unless header = headers.detect { |k,v| k.downcase == 'content-type' }
117
+ options[:html_types].include? header.last[/^\w+\/\w+/]
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,87 @@
1
+ require 'rack/protection'
2
+ require 'rack/utils'
3
+
4
+ begin
5
+ require 'escape_utils'
6
+ rescue LoadError
7
+ end
8
+
9
+ module Rack
10
+ module Protection
11
+ ##
12
+ # Prevented attack:: XSS
13
+ # Supported browsers:: all
14
+ # More infos:: http://en.wikipedia.org/wiki/Cross-site_scripting
15
+ #
16
+ # Automatically escapes Rack::Request#params so they can be embedded in HTML
17
+ # or JavaScript without any further issues. Calls +html_safe+ on the escaped
18
+ # strings if defined, to avoid double-escaping in Rails.
19
+ #
20
+ # Options:
21
+ # escape:: What escaping modes to use, should be Symbol or Array of Symbols.
22
+ # Available: :html (default), :javascript, :url
23
+ class EscapedParams < Base
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
33
+
34
+ def initialize(*)
35
+ super
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
46
+ end
47
+
48
+ def call(env)
49
+ request = Request.new(env)
50
+ get_was = handle(request.GET)
51
+ post_was = handle(request.POST) rescue nil
52
+ app.call env
53
+ ensure
54
+ request.GET.replace get_was if get_was
55
+ request.POST.replace post_was if post_was
56
+ end
57
+
58
+ def handle(hash)
59
+ was = hash.dup
60
+ hash.replace escape(hash)
61
+ was
62
+ end
63
+
64
+ def escape(object)
65
+ case object
66
+ when Hash then escape_hash(object)
67
+ when Array then object.map { |o| escape(o) }
68
+ when String then escape_string(object)
69
+ else nil
70
+ end
71
+ end
72
+
73
+ def escape_hash(hash)
74
+ hash = hash.dup
75
+ hash.each { |k,v| hash[k] = escape(v) }
76
+ hash
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
85
+ end
86
+ end
87
+ end