sauviq
Last Updated: November 28, 2016
·
221
· jasny
Arnold

Brute force protection in PHP

If you have a system where users can subscribe and choose there own passwords, you are probably a target for brute force attacks like a dictionary attack. You can limit this problem by showing user how strong their password is. However forcing users to enter a really strong password will annoy them, since they like something they can remember.

Another way wall you can put up is blocking an IP address for a few minutes upon a number of login failures. This is not a waterproof protection, but the hacker now requires a botnet to perform the brute force attack. This is a hurdle that should keep at the casual hackers and script kiddies out. So based on the data you’re protecting this should be a decent defense.

Setting up this defense isn’t difficult. I’ll show an example how to do this with APC user cache.

<?php
  $apc_key = "{$_SERVER['SERVER_NAME']}~login:{$_SERVER['REMOTE_ADDR']}";
  $tries = (int)apc_fetch($apc_key);
  if ($tries >= 10) {
    header("HTTP/1.1 429 Too Many Requests");
    echo "You've exceeded the number of login attempts. We've blocked IP address {$_SERVER['REMOTE_ADDR']} for a few minutes.";
    exit();
  }

  $success = login($_POST['username'], $_POST['password']);
  if (!$success) {
    apcu_inc($apc_key, $tries+1, 600);  # store tries for 10 minutes
  } else {
    apc_delete($apc_key);
  }

Increasing the block time

Blocking an IP might effect a whole office which can be annoying. Starting with a low timeout and increasing it each time an IP is blocked will help against it.

<?php
  $apc_key = "{$_SERVER['SERVER_NAME']}~login:{$_SERVER['REMOTE_ADDR']}";
  $apc_blocked_key = "{$_SERVER['SERVER_NAME']}~login-blocked:{$_SERVER['REMOTE_ADDR']}";

  $tries = (int)apc_fetch($apc_key);
  if ($tries >= 10) {
    header("HTTP/1.1 429 Too Many Requests");
    echo "You've exceeded the number of login attempts. We've blocked IP address {$_SERVER['REMOTE_ADDR']} for a few minutes.";
    exit();
  }

  $success = login($_POST['username'], $_POST['password']);
  if (!$success) {
    $blocked = (int)apc_fetch($apc_blocked_key);

    apc_store($apc_key, $tries+1, pow(2, $blocked+1)*60);  # store tries for 2^(x+1) minutes: 2, 4, 8, 16, ...
    apc_store($apc_blocked_key, $blocked+1, 86400);  # store number of times blocked for 24 hours
  } else {
    apc_delete($apc_key);
    apc_delete($apc_blocked_key);
  }