wkimeria-rack-attack 4.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ODBkODliMDY4NmQ1MzAyZmNlMWZjOWU2ZWQ2YmQwOGU3MjY0MzY4NA==
5
+ data.tar.gz: !binary |-
6
+ YTIxNWEyMzk4MTE0MDEwODQyMTdlYjY4YTc1M2FiZDhiYjQ3YmFjOQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MjUzMTMyMWJiOWM5MWYzMTI0Zjg3NzYyMDM4M2FhM2RmNjkxMjJjMjViMGRi
10
+ ZWJjMjNjYzc4MDBjZjY1NzkzOWM5ZDA3NWNlYjQ0NThiMTA1OTg0OWE5ZmFh
11
+ N2RmYjdmMDJlYjVhYjMyMDdjN2M3MmRkOGIzYTYzYTVhMTJiMGY=
12
+ data.tar.gz: !binary |-
13
+ NzdmNmE4NDhlNGJkYjgzNTMwYjQzYjlkMzM1YTQ4NjJhYzllZGJkZDE0YTM5
14
+ YWVlYjFhNDBkYjFhZjRiOGYwNjM4YzA1MWQ3MjlhYmUyYzEzZjIzN2EwM2U5
15
+ OGMyMjIwMTdlZGI4OWE3Zjc0ZTdjOTFkZTIyZmIyYTM3ZTc0OGE=
@@ -0,0 +1,308 @@
1
+ # Rack::Attack!!!
2
+ *Rack middleware for blocking & throttling abusive requests*
3
+
4
+ Rack::Attack is a rack middleware to protect your web app from bad clients.
5
+ It allows *whitelisting*, *blacklisting*, *throttling*, and *tracking* based on arbitrary properties of the request.
6
+
7
+ Throttle state is stored in a configurable cache (e.g. `Rails.cache`), presumably backed by memcached or redis ([at least gem v3.0.0](https://rubygems.org/gems/redis)).
8
+
9
+ See the [Backing & Hacking blog post](http://www.kickstarter.com/backing-and-hacking/rack-attack-protection-from-abusive-clients) introducing Rack::Attack.
10
+
11
+ [![Gem Version](https://badge.fury.io/rb/rack-attack.png)](http://badge.fury.io/rb/rack-attack)
12
+ [![Build Status](https://travis-ci.org/kickstarter/rack-attack.png?branch=master)](https://travis-ci.org/kickstarter/rack-attack)
13
+ [![Code Climate](https://codeclimate.com/github/kickstarter/rack-attack.png)](https://codeclimate.com/github/kickstarter/rack-attack)
14
+
15
+
16
+ ## Getting started
17
+
18
+ Install the [rack-attack](http://rubygems.org/gems/rack-attack) gem; or add it to you Gemfile with bundler:
19
+
20
+ ```ruby
21
+ # In your Gemfile
22
+ gem 'rack-attack'
23
+ ```
24
+ Tell your app to use the Rack::Attack middleware.
25
+ For Rails 3+ apps:
26
+
27
+ ```ruby
28
+ # In config/application.rb
29
+ config.middleware.use Rack::Attack
30
+ ```
31
+
32
+ Or for Rackup files:
33
+
34
+ ```ruby
35
+ # In config.ru
36
+ use Rack::Attack
37
+ ```
38
+
39
+ Add a `rack-attack.rb` file to `config/initalizers/`:
40
+ ```ruby
41
+ # In config/initializers/rack-attack.rb
42
+ class Rack::Attack
43
+ # your custom configuration...
44
+ end
45
+ ```
46
+
47
+ *Tip:* The example in the wiki is a great way to get started:
48
+ [Example Configuration](https://github.com/kickstarter/rack-attack/wiki/Example-Configuration)
49
+
50
+ Optionally configure the cache store for throttling:
51
+
52
+ ```ruby
53
+ Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # defaults to Rails.cache
54
+ ```
55
+
56
+ Note that `Rack::Attack.cache` is only used for throttling; not blacklisting & whitelisting. Your cache store must implement `increment` and `write` like [ActiveSupport::Cache::Store](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html).
57
+
58
+ ## How it works
59
+
60
+ The Rack::Attack middleware compares each request against *whitelists*, *blacklists*, *throttles*, and *tracks* that you define. There are none by default.
61
+
62
+ * If the request matches any **whitelist**, it is allowed.
63
+ * Otherwise, if the request matches any **blacklist**, it is blocked.
64
+ * Otherwise, if the request matches any **throttle**, a counter is incremented in the Rack::Attack.cache. If any throttle's limit is exceeded, the request is blocked.
65
+ * Otherwise, all **tracks** are checked, and the request is allowed.
66
+
67
+ The algorithm is actually more concise in code: See [Rack::Attack.call](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack.rb):
68
+
69
+ ```ruby
70
+ def call(env)
71
+ req = Rack::Attack::Request.new(env)
72
+
73
+ if whitelisted?(req)
74
+ @app.call(env)
75
+ elsif blacklisted?(req)
76
+ blacklisted_response[env]
77
+ elsif throttled?(req)
78
+ throttled_response[env]
79
+ else
80
+ tracked?(req)
81
+ @app.call(env)
82
+ end
83
+ end
84
+ ```
85
+
86
+ Note: `Rack::Attack::Request` is just a subclass of `Rack::Attack` so that you
87
+ can cleanly monkey patch helper methods onto the
88
+ [request object](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack/request.rb).
89
+
90
+ ## About Tracks
91
+
92
+ `Rack::Attack.track` doesn't affect request processing. Tracks are an easy way to log and measure requests matching arbitrary attributes.
93
+
94
+ ## Usage
95
+
96
+ Define whitelists, blacklists, throttles, and tracks as blocks that return truthy values if matched, falsy otherwise. In a Rails app
97
+ these go in an initializer in `config/initializers/`.
98
+ A [Rack::Request](http://rack.rubyforge.org/doc/classes/Rack/Request.html) object is passed to the block (named 'req' in the examples).
99
+
100
+ ### Whitelists
101
+
102
+ ```ruby
103
+ # Always allow requests from localhost
104
+ # (blacklist & throttles are skipped)
105
+ Rack::Attack.whitelist('allow from localhost') do |req|
106
+ # Requests are allowed if the return value is truthy
107
+ '127.0.0.1' == req.ip
108
+ end
109
+ ```
110
+
111
+ ### Blacklists
112
+
113
+ ```ruby
114
+ # Block requests from 1.2.3.4
115
+ Rack::Attack.blacklist('block 1.2.3.4') do |req|
116
+ # Requests are blocked if the return value is truthy
117
+ '1.2.3.4' == req.ip
118
+ end
119
+
120
+ # Block logins from a bad user agent
121
+ Rack::Attack.blacklist('block bad UA logins') do |req|
122
+ req.path == '/login' && req.post? && req.user_agent == 'BadUA'
123
+ end
124
+ ```
125
+
126
+ #### Fail2Ban
127
+
128
+ `Fail2Ban.filter` can be used within a blacklist to block all requests from misbehaving clients.
129
+ This pattern is inspired by [fail2ban](http://www.fail2ban.org/wiki/index.php/Main_Page).
130
+ See the [fail2ban documentation](http://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Jail_Options) for more details on
131
+ how the parameters work.
132
+
133
+ ```ruby
134
+ # Block requests containing '/etc/password' in the params.
135
+ # After 3 blocked requests in 10 minutes, block all requests from that IP for 5 minutes.
136
+ Rack::Attack.blacklist('fail2ban pentesters') do |req|
137
+ # `filter` returns truthy value if request fails, or if it's from a previously banned IP
138
+ # so the request is blocked
139
+ Rack::Attack::Fail2Ban.filter(req.ip, :maxretry => 3, :findtime => 10.minutes, :bantime => 5.minutes) do
140
+ # The count for the IP is incremented if the return value is truthy.
141
+ CGI.unescape(req.query_string) =~ %r{/etc/passwd}
142
+ end
143
+ end
144
+ ```
145
+
146
+ #### Allow2Ban
147
+ `Allow2Ban.filter` works the same way as the `Fail2Ban.filter` except that it *allows* requests from misbehaving
148
+ clients until such time as they reach maxretry at which they are cut off as per normal.
149
+ ```ruby
150
+ # Lockout IP addresses that are hammering your login page.
151
+ # After 20 requests in 1 minute, block all requests from that IP for 1 hour.
152
+ Rack::Attack.blacklist('allow2ban login scrapers') do |req|
153
+ # `filter` returns false value if request is to your login page (but still
154
+ # increments the count) so request below the limit are not blocked until
155
+ # they hit the limit. At that point, filter will return true and block.
156
+ Rack::Attack::Allow2Ban.filter(req.ip, :maxretry => 20, :findtime => 1.minute, :bantime => 1.hour) do
157
+ # The count for the IP is incremented if the return value is truthy.
158
+ req.path == '/login' and req.post?
159
+ end
160
+ end
161
+ ```
162
+
163
+
164
+ ### Throttles
165
+
166
+ ```ruby
167
+ # Throttle requests to 5 requests per second per ip
168
+ Rack::Attack.throttle('req/ip', :limit => 5, :period => 1.second) do |req|
169
+ # If the return value is truthy, the cache key for the return value
170
+ # is incremented and compared with the limit. In this case:
171
+ # "rack::attack:#{Time.now.to_i/1.second}:req/ip:#{req.ip}"
172
+ #
173
+ # If falsy, the cache key is neither incremented nor checked.
174
+
175
+ req.ip
176
+ end
177
+
178
+ # Throttle login attempts for a given email parameter to 6 reqs/minute
179
+ # Return the email as a discriminator on POST /login requests
180
+ Rack::Attack.throttle('logins/email', :limit => 6, :period => 60.seconds) do |req|
181
+ req.params['email'] if req.path == '/login' && req.post?
182
+ end
183
+
184
+ # You can also set a limit using a proc instead of a number. For
185
+ # instance, after Rack::Auth::Basic has authenticated the user:
186
+ limit_based_on_proc = proc {|req| req.env["REMOTE_USER"] == "admin" ? 100 : 1}
187
+ Rack::Attack.throttle('req/ip', :limit => limit_based_on_proc, :period => 1.second) do |req|
188
+ req.ip
189
+ end
190
+ ```
191
+
192
+ ### Conditional Throttles
193
+ # Throttle failed logins
194
+ Rack::Attack.conditional_throttle('login', :limit =>5, :period => 1.hour) do |req|
195
+ # If the return value is truthy, the cache key for the return value is checked to determine
196
+ # whether to throttle. The value for the key is not incremented
197
+
198
+ # To increment the value returned by key
199
+ To increment counter
200
+ if(failed_login)
201
+ Rack::Attack.increment_throttle_counter('login', request.params['...
202
+ end
203
+
204
+ ### Tracks
205
+
206
+ ```ruby
207
+ # Track requests from a special user agent.
208
+ Rack::Attack.track("special_agent") do |req|
209
+ req.user_agent == "SpecialAgent"
210
+ end
211
+
212
+ # Supports optional limit and period, triggers the notification only when the limit is reached.
213
+ Rack::Attack.track("special_agent", :limit 6, :period => 60.seconds) do |req|
214
+ req.user_agent == "SpecialAgent"
215
+ end
216
+
217
+ # Track it using ActiveSupport::Notification
218
+ ActiveSupport::Notifications.subscribe("rack.attack") do |name, start, finish, request_id, req|
219
+ if req.env['rack.attack.matched'] == "special_agent" && req.env['rack.attack.match_type'] == :track
220
+ Rails.logger.info "special_agent: #{req.path}"
221
+ STATSD.increment("special_agent")
222
+ end
223
+ end
224
+ ```
225
+
226
+ ## Responses
227
+
228
+ Customize the response of blacklisted and throttled requests using an object that adheres to the [Rack app interface](http://rack.rubyforge.org/doc/SPEC.html).
229
+
230
+ ```ruby
231
+ Rack::Attack.blacklisted_response = lambda do |env|
232
+ # Using 503 because it may make attacker think that they have successfully
233
+ # DOSed the site. Rack::Attack returns 403 for blacklists by default
234
+ [ 503, {}, ['Blocked']]
235
+ end
236
+
237
+ Rack::Attack.throttled_response = lambda do |env|
238
+ # name and other data about the matched throttle
239
+ body = [
240
+ env['rack.attack.matched'],
241
+ env['rack.attack.match_type'],
242
+ env['rack.attack.match_data']
243
+ ].inspect
244
+
245
+ # Using 503 because it may make attacker think that they have successfully
246
+ # DOSed the site. Rack::Attack returns 429 for throttling by default
247
+ [ 503, {}, [body]]
248
+ end
249
+ ```
250
+
251
+ For responses that did not exceed a throttle limit, Rack::Attack annotates the env with match data:
252
+
253
+ ```ruby
254
+ request.env['rack.attack.throttle_data'][name] # => { :count => n, :period => p, :limit => l }
255
+ ```
256
+
257
+ ## Logging & Instrumentation
258
+
259
+ Rack::Attack uses the [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) API if available.
260
+
261
+ You can subscribe to 'rack.attack' events and log it, graph it, etc:
262
+
263
+ ```ruby
264
+ ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, req|
265
+ puts req.inspect
266
+ end
267
+ ```
268
+
269
+ ## Testing
270
+
271
+ A note on developing and testing apps using Rack::Attack - if you are using throttling in particular, you will
272
+ need to enable the cache in your development environment. See [Caching with Rails](http://guides.rubyonrails.org/caching_with_rails.html)
273
+ for more on how to do this.
274
+
275
+ ## Performance
276
+
277
+ The overhead of running Rack::Attack is typically negligible (a few milliseconds per request),
278
+ but it depends on how many checks you've configured, and how long they take.
279
+ Throttles usually require a network roundtrip to your cache server(s),
280
+ so try to keep the number of throttle checks per request low.
281
+
282
+ If a request is blacklisted or throttled, the response is a very simple Rack response.
283
+ A single typical ruby web server thread can block several hundred requests per second.
284
+
285
+ Rack::Attack complements tools like `iptables` and nginx's [limit_conn_zone module](http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html#limit_conn_zone).
286
+
287
+ ## Motivation
288
+
289
+ Abusive clients range from malicious login crackers to naively-written scrapers.
290
+ They hinder the security, performance, & availability of web applications.
291
+
292
+ It is impractical if not impossible to block abusive clients completely.
293
+
294
+ Rack::Attack aims to let developers quickly mitigate abusive requests and rely
295
+ less on short-term, one-off hacks to block a particular attack.
296
+
297
+ ## Mailing list
298
+
299
+ New releases of Rack::Attack are announced on
300
+ <rack.attack.announce@librelist.com>. To subscribe, just send an email to
301
+ <rack.attack.announce@librelist.com>. See the
302
+ [archives](http://librelist.com/browser/rack.attack.announce/).
303
+
304
+ ## License
305
+
306
+ Copyright Kickstarter, Inc.
307
+
308
+ Released under an [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,18 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+ require 'rake/testtask'
4
+
5
+ namespace :test do
6
+ Rake::TestTask.new(:units) do |t|
7
+ t.pattern = "spec/*_spec.rb"
8
+ end
9
+
10
+ Rake::TestTask.new(:integration) do |t|
11
+ t.pattern = "spec/integration/*_spec.rb"
12
+ end
13
+ end
14
+
15
+ desc 'Run tests'
16
+ task :test => %w[test:units test:integration]
17
+
18
+ task :default => :test
@@ -0,0 +1,122 @@
1
+ require 'rack'
2
+ require 'forwardable'
3
+
4
+ class Rack::Attack
5
+ autoload :Cache, 'rack/attack/cache'
6
+ autoload :Check, 'rack/attack/check'
7
+ autoload :Throttle, 'rack/attack/throttle'
8
+ autoload :ConditionalThrottle, 'rack/attack/conditional_throttle'
9
+ autoload :Whitelist, 'rack/attack/whitelist'
10
+ autoload :Blacklist, 'rack/attack/blacklist'
11
+ autoload :Track, 'rack/attack/track'
12
+ autoload :StoreProxy, 'rack/attack/store_proxy'
13
+ autoload :DalliProxy, 'rack/attack/store_proxy/dalli_proxy'
14
+ autoload :RedisStoreProxy, 'rack/attack/store_proxy/redis_store_proxy'
15
+ autoload :Fail2Ban, 'rack/attack/fail2ban'
16
+ autoload :Allow2Ban, 'rack/attack/allow2ban'
17
+ autoload :Request, 'rack/attack/request'
18
+
19
+ class << self
20
+
21
+ attr_accessor :notifier, :blacklisted_response, :throttled_response
22
+
23
+ def increment_throttle_counter(name, discriminator)
24
+ self.throttles[name].increment_counter(discriminator)
25
+ end
26
+
27
+ def whitelist(name, &block)
28
+ self.whitelists[name] = Whitelist.new(name, block)
29
+ end
30
+
31
+ def blacklist(name, &block)
32
+ self.blacklists[name] = Blacklist.new(name, block)
33
+ end
34
+
35
+ def throttle(name, options, &block)
36
+ self.throttles[name] = Throttle.new(name, options, block)
37
+ end
38
+
39
+ def conditional_throttle(name, options, &block)
40
+ self.throttles[name] = ConditionalThrottle.new(name, options, block)
41
+ end
42
+
43
+ def track(name, options = {}, &block)
44
+ self.tracks[name] = Track.new(name, options, block)
45
+ end
46
+
47
+ def whitelists; @whitelists ||= {}; end
48
+ def blacklists; @blacklists ||= {}; end
49
+ def throttles; @throttles ||= {}; end
50
+ def tracks; @tracks ||= {}; end
51
+
52
+ def whitelisted?(req)
53
+ whitelists.any? do |name, whitelist|
54
+ whitelist[req]
55
+ end
56
+ end
57
+
58
+ def blacklisted?(req)
59
+ blacklists.any? do |name, blacklist|
60
+ blacklist[req]
61
+ end
62
+ end
63
+
64
+ def throttled?(req)
65
+ throttles.any? do |name, throttle|
66
+ throttle[req]
67
+ end
68
+ end
69
+
70
+ def tracked?(req)
71
+ tracks.each_value do |tracker|
72
+ tracker[req]
73
+ end
74
+ end
75
+
76
+ def instrument(req)
77
+ notifier.instrument('rack.attack', req) if notifier
78
+ end
79
+
80
+ def cache
81
+ @cache ||= Cache.new
82
+ end
83
+
84
+ def clear!
85
+ @whitelists, @blacklists, @throttles, @tracks = {}, {}, {}, {}
86
+ end
87
+
88
+ end
89
+
90
+ # Set defaults
91
+ @notifier = ActiveSupport::Notifications if defined?(ActiveSupport::Notifications)
92
+ @blacklisted_response = lambda {|env| [403, {'Content-Type' => 'text/plain'}, ["Forbidden\n"]] }
93
+ @throttled_response = lambda {|env|
94
+ retry_after = env['rack.attack.match_data'][:period] rescue nil
95
+ [429, {'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s}, ["Retry later\n"]]
96
+ }
97
+
98
+ def initialize(app)
99
+ @app = app
100
+ end
101
+
102
+ def call(env)
103
+ req = Rack::Attack::Request.new(env)
104
+
105
+ if whitelisted?(req)
106
+ @app.call(env)
107
+ elsif blacklisted?(req)
108
+ self.class.blacklisted_response[env]
109
+ elsif throttled?(req)
110
+ self.class.throttled_response[env]
111
+ else
112
+ tracked?(req)
113
+ @app.call(env)
114
+ end
115
+ end
116
+
117
+ extend Forwardable
118
+ def_delegators self, :whitelisted?,
119
+ :blacklisted?,
120
+ :throttled?,
121
+ :tracked?
122
+ end