How to Prevent Discovery Attacks

Sanket Patel | Last updated on October 6, 2022 | 5 minute read

When something goes wrong with your data, Rewind Backups is there to help you restore it back to the way it was. This requires us to securely store a great deal of customer data. In this post, we’re going to look at one solution we’re using to prevent a specific type of targeted attack. Keep in mind that security is multi-faceted and we’re following the defence in depth concept but wanted to highlight one library we’re using.

What is a Discovery Attack ?

A Discovery attack is the method or process in which a hacker tries to bombard your system with thousands of requests in an hope to identify some information about your system.

Let’s take an example of a simple registration page and assume that there is no limit as to how many times a user can try to register on your application. Now, a hacker can try to bombard this registration page with thousands of requests per minute without fear of getting blocked by the system. He should be able to try 1000 different email addresses in this one minute time frame and quickly be able to know which emails exist in our system.

Another example of such an attack would be a hacker hitting your login page with different passwords in order to discover the correct one.

Similarly, there could be several such scenarios when a malicious user could discover private information that we don’t want them to exploit.

How to mitigate these Attacks?

This is where the Rack::Attack gem comes to rescue. Rack::Attack lies at the application layer right before any of your code is executed. It is a middleware through which we can throttle/limit requests coming from a particular IP address or any other parameters such as Email Address.

Adding Rack::Attack to your Rails Application

Adding Rack::Attack to secure your Rails Application is pretty simple. Here’s a simple example that blocks by unique IP address:

  1. Add it to the Gemfile
    gem 'rack-attack'
  2. Add it as middleware in your config/application.rb
    config.middleware.use Rack::Attack

     

  3. Now add the rack configuration file to config/initializers/ with the name rack_attack.rb
    class Rack::Attack
    
    
               ### Configure Cache ###
               Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
    
               ### Configure Throttle Configs ###
               throttle('registration/ip', limit: 5, period: 1.minutes) do |req|
                   req.ip if req.path == ‘/register’ && req.post?
               end
    
                ### Configure throttle response ###
                self.throttled_response = lambda do |_env|
                   [429, # status
                     { 'Content-Type' => 'text/html' }, # headers
                     [ActionView::Base.new.render(file: 'public/429.html')]] # body
                end
    
                ### Configure Rack attack detection logging ###
                ActiveSupport::Notifications.subscribe('rack.attack') do |_name, _start,      _completed, _request_id, payload|
                     req = payload[:request]
    
                     Logger.error("===ABUSIVE REQUESTS RECEIVED=== IP:  #{req.ip} URL: #{req.path} throttle_config: #{req.env['rack.attack.matched']} method:  #{req.env['REQUEST_METHOD']}") if req.env['rack.attack.match_type'] == :throttle
                end
             end
    

Let’s try to understand the configuration file that we just added to our Rails application.

The first step in the configuration file is setting up the Cache. This is actually an important step as all the throttle configuration states will be stored in this cache that we configure.

There are two cache options that you get for using RackAttack:

  • In-Memory Cache: This could be implemented using ActiveSupport::Cache::MemoryStore. This is a cache store implementation which stores everything into memory in the same process. That said, you should be careful when you have multiple Ruby processes or multiple application Servers. Since each process will have its own in-memory cache. For Example, If you are running a PUMA server in clustered mode with two processes, then, your throttle limit would be doubled. In simpler terms, It could take up to (number_of_ruby_processes * limit) number of requests to block a hacker. For this reason, the in-memory cache should really only be used for development or simple applications.
  • Shared Cache: You can also configure a Shared Cache using Redis or Memcached. `increment` and `write` methods must be implemented for any shared cache similar to ActiveSupport::Cache::Store as stated in the documentation.

The next step is the fun part. This is where you will add your configurations for blocking malicious users. The `throttle` function is pretty simple. The first argument is the name of the throttle. This name should always be unique to avoid multiple throttle conflicts. The next two arguments are limit and period. For Example, limit: 5 and period: 1.minutes, will block a hacker if they make more than 5 requests in 1 minute. Finally, you pass in a ruby-block-argument that will return the property you wish to block requests for (source IP address, user’s email address, etc.).

At this point, you are ready to block the malicious users. but, Rack::Attack also gives you an ability to return a custom status code or a response page to the hacker. It is simple to configure as shown in the code above where a simple 429 error page is returned.

Congratulations!!! You have successfully blocked the malicious users. But, you will still want to know when and who tried to bombard your website with 1000s of requests or tried breaking into your system. In the example above, we log a message which is sent to a log aggregation system where we can create metrics and alarms.

Some Quick Tips:

What if there is a load balancer in front of the application?

All the traffic from load balancer may be blocked by Rack Attack if all your traffic appears to come from the load balancer. If that happens you might want to check how your load balancer is forwarding the IP Address to your application. You can configure your load balancer to forward the actual user’s ip address through the “X-Forwarded-For” header. Then when you do `req.ip` it will automatically get the correct IP address for you. To understand how the code for IP fetching works, Look here.

What if you have a trusted application that continuously hits your application?

In some cases, It is possible that an internal/trusted application is hitting your application continuously to accomplish some purpose. You don’t want to block that application from doing its job. In this case, Rack Attack provides a way to safelist IP. This means that an IP address added to this list will not be blocked. Take a look here on how to do that.

Want to work with the people who wrote this?

We’re always hiring problem solvers: check out our Open Positions or join our Talent Network!


Profile picture of <a class=Sanket Patel">
Sanket Patel
Sanket Patel is a passionate software developer with 3+ years of experience in developing large-scale web applications in various languages, frameworks, and platforms. Sanket has a curious mind and enjoys exploring new ideas and technologies in the software industry.