cult3

Dealing with brute force attacks in Laravel

Dec 28, 2015

Table of contents:

  1. An overview of the solution
  2. Creating the database table
  3. Creating the Throttle Model
  4. Adding Throttling to your Authentication
  5. Conclusion

As soon as your web application gains any kind of traction you will become the target for brute force attacks.

Whilst an all out attack is very difficult to fight against, there are certain things you can you put in place to try to thwart brute force attacks.

In today’s tutorial I will walk you through setting up my preferred method for throttling brute force attacks in Laravel applications.

An overview of the solution

Before we get into the code, first we will look at the problem we face and what we can do to mitigate against it.

A brute force attack is where your application will receive a flood of authentication requests to try and gain access.

These requests will usually span across multiple credentials and multiple ip addresses.

If you don’t take measures to stop these brute force attempts, your application is open to attack.

In order to protect ourselves we need to record the failed authentication attempts for all user credentials.

If the number of failed authentication attempts is higher than it should be (based on historical data for this particular application) we can take measures to slow the requests down.

This might involve introducing a delay so the requests are reduced, or requiring the requester to provide the answer to a captcha form or similar test.

So now that we have that clear, lets look at what we need to do to add this protection to our application.

Creating the database table

We are going to need to record all of the failed authentication attempts for the application and so we will need a database table to store the data.

Here is the migration I’m going to be using:

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateThrottlesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create("throttles", function (Blueprint $table) {
            $table->increments("id");
            $table->string("identifier");
            $table->timestamp("attempted_at")->index();
        });

        DB::statement("ALTER TABLE `throttles` ADD `ip_address` VARBINARY(16)");
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop("throttles");
    }
}

I’m storing the identifier the user used (usually username or email address) as well as the ip address of the request and the time in which it occurred.

You will also notice that I’m storing the IP address as a VARBINARY.

Creating the Throttle Model

Next I will create the Throttle Model that will be used to store the failed authentication requests and to determine if the number of requests has exceeded the limit:

class Throttle extends Model
{
    /**
     * @var bool
     */
    public $timestamps = false;

    /**
     * @var array
     */
    protected $fillable = ["identifier", "ip_address", "attempted_at"];
}

As you can see this is a fairly typical model. We don’t need timestamps so we can just turn them off.

In order to transform the IP address to and from the binary field we can use these two mutator methods:

/**
 * Get the IP address
 *
 * @param string $value
 * @return string
 */
public function getIpAddressAttribute($value)
{
    return inet_ntop($value);
}

/**
 * Set the IP address
 *
 * @param string $value
 * @return void
 */
public function setIpAddressAttribute($value)
{
    $this->attributes['ip_address'] = inet_pton($value);
}

And finally I will add a throttle method that will check to see if the request should be delayed:

/**
 * Check to see if the Throttle threshold has been exceeded
 *
 * @return bool
 */
public static function throttle()
{
    $threshold = 50;
    $period = Carbon::now()->subMinutes(15);

    $count = (new static)->where('attempted_at', '>', $period)->count();

    if ($count > $threshold) sleep(2);

    return false;
}

In this example I’m simply going to sleep the request, but you might want to take increasingly more severe measures like sleeping for longer or requiring some form of test to prove the request is coming from a human.

I’m also hard coding the threshold and the interval period, but these are things you will want to adjust once you know the average failed requests for your application.

Adding Throttling to your Authentication

Finally we can add throttling to the authentication by creating a new row in the database whenever a request fails:

Throttle::create([
    "identifier" => $identifier,
    "ip_address" => Request::ip(),
    "attempted_at" => Carbon::now(),
]);

We can then check to see if the request should be throttled:

Throttle::throttle();

Conclusion

This is a fairly simple solution to try and prevent brute force attacks.

Whilst there isn’t really a silver bullet for protecting yourself, there are a lot of things you can do to make it harder for your attackers.

A lot of applications don’t even need this kind of protection because they are probably too small to even be targeted.

But the measures to protect yourself are fairly simple, so it’s usually a good practice to implement them.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.