Skip to content

Commit e36e66c

Browse files
committed
Adds rack-attack throttles
Why are these changes being introduced: * Bots and pentesters are using a significant amount of our resources Relevant ticket(s): * https://mitlibraries.atlassian.net/browse/TIMX-480 How does this address that need: * Adds a rack-attack configuration for a few scenarios * Overall throttle for extremely high volume (not including assets) * Lower throttle for redirect detected requests (these are bots behaving badly) * a few pentester bans * campus IPs are safelisted, which should include ezproxy and vpn Document any side effects to this change: It's difficult to test to ensure the best tweaks to this type of work until it is in production.
1 parent 5babd3f commit e36e66c

File tree

1 file changed

+121
-0
lines changed

1 file changed

+121
-0
lines changed

config/initializers/rack_attack.rb

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
class Rack::Attack
2+
3+
### Configure Cache ###
4+
5+
# If you don't want to use Rails.cache (Rack::Attack's default), then
6+
# configure it here.
7+
#
8+
# Note: The store is only used for throttling (not blocklisting and
9+
# safelisting). It must implement .increment and .write like
10+
# ActiveSupport::Cache::Store
11+
12+
# Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
13+
14+
### Safelist MIT IP addresses
15+
# http://kb.mit.edu/confluence/x/F4DCAg
16+
# Main IP range (includes campus, NAT pool, and VPNs)
17+
# This also affects bot_challenge_page logic which uses rack_attack under the hood
18+
Rack::Attack.safelist_ip("18.0.0.0/11")
19+
20+
### Throttle Spammy Clients ###
21+
22+
# If any single client IP is making tons of requests, then they're
23+
# probably malicious or a poorly-configured scraper. Either way, they
24+
# don't deserve to hog all of the app server's CPU. Cut them off!
25+
#
26+
# Note: If you're serving assets through rack, those requests may be
27+
# counted by rack-attack and this throttle may be activated too
28+
# quickly. If so, enable the condition to exclude them from tracking.
29+
30+
# Throttle all requests by IP (default is 100 requests per 10 minutes)
31+
#
32+
# Key: "rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}"
33+
throttle('req/ip',
34+
limit: (ENV.fetch('REQUESTS_PER_PERIOD') { 100 }).to_i,
35+
period: (ENV.fetch('REQUEST_PERIOD') { 10 }).to_i.minutes) do |req|
36+
# don't include assets as requests
37+
req.ip unless req.path.start_with?('/assets')
38+
end
39+
40+
# Throttle redirects by IP (default is 5 per 10 minutes)
41+
#
42+
# Key: "rack::attack:#{Time.now.to_i/:period}:req/ip/redirects:#{req.ip}"
43+
throttle('req/ip/redirects',
44+
limit: (ENV.fetch('REDIRECT_REQUESTS_PER_PERIOD') { 5 }).to_i,
45+
period: (ENV.fetch('REDIRECT_REQUEST_PERIOD') { 10 }).to_i.minutes) do |req|
46+
req.ip if req.query_string.start_with?('geoweb-redirect')
47+
end
48+
49+
### Prevent Brute-Force Login Attacks ###
50+
51+
# The most common brute-force login attack is a brute-force password
52+
# attack where an attacker simply tries a large number of emails and
53+
# passwords to see if any credentials match.
54+
#
55+
# Another common method of attack is to use a swarm of computers with
56+
# different IPs to try brute-forcing a password for a specific account.
57+
58+
# Throttle POST requests to /login by IP address
59+
#
60+
# Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}"
61+
# throttle('logins/ip', limit: 5, period: 20.seconds) do |req|
62+
# if req.path == '/login' && req.post?
63+
# req.ip
64+
# end
65+
# end
66+
67+
# Throttle POST requests to /login by email param
68+
#
69+
# Key: "rack::attack:#{Time.now.to_i/:period}:logins/email:#{req.email}"
70+
#
71+
# Note: This creates a problem where a malicious user could intentionally
72+
# throttle logins for another user and force their login requests to be
73+
# denied, but that's not very common and shouldn't happen to you. (Knock
74+
# on wood!)
75+
# throttle("logins/email", limit: 5, period: 20.seconds) do |req|
76+
# if req.path == '/login' && req.post?
77+
# # return the email if present, nil otherwise
78+
# req.params['email'].presence
79+
# end
80+
# end
81+
82+
### Custom Throttle Response ###
83+
84+
# By default, Rack::Attack returns an HTTP 429 for throttled responses,
85+
# which is just fine.
86+
#
87+
# If you want to return 503 so that the attacker might be fooled into
88+
# believing that they've successfully broken your app (or you just want to
89+
# customize the response), then uncomment these lines.
90+
# self.throttled_response = lambda do |env|
91+
# [ 503, # status
92+
# {}, # headers
93+
# ['']] # body
94+
# end
95+
96+
# Block suspicious requests for '/etc/password' or wordpress specific paths.
97+
# After 3 blocked requests in 10 minutes, block all requests from that IP for 5 minutes.
98+
# Note: these will not show up in the throttle logs as they are blocks and not throttles
99+
Rack::Attack.blocklist('fail2ban pentesters') do |req|
100+
# `filter` returns truthy value if request fails, or if it's from a previously banned IP
101+
# so the request is blocked
102+
Rack::Attack::Fail2Ban.filter("pentesters-#{req.ip}", maxretry: 3, findtime: 10.minutes, bantime: 5.minutes) do
103+
# The count for the IP is incremented if the return value is truthy
104+
CGI.unescape(req.query_string) =~ %r{/etc/passwd} ||
105+
req.path.include?('/etc/passwd') ||
106+
req.path.include?('wp-admin') ||
107+
req.path.include?('wp-login')
108+
end
109+
end
110+
111+
# Log when throttles are triggered
112+
ActiveSupport::Notifications.subscribe("throttle.rack_attack") do |name, start, finish, request_id, payload|
113+
@@rack_logger ||= ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
114+
@@rack_logger.info{[
115+
"[#{payload[:request].env['rack.attack.match_type']}]",
116+
"[#{payload[:request].env['rack.attack.matched']}]",
117+
"[#{payload[:request].env['rack.attack.match_discriminator']}]",
118+
"[#{payload[:request].env['rack.attack.throttle_data']}]",
119+
].join(' ') }
120+
end
121+
end

0 commit comments

Comments
 (0)