Prevent Post Data From Different Domains & Hosts (Brute Force Protection)

Yesterday, one of my servers began getting hit by what I thought was a DDOS attack. Well, it kinda worked at a DDOS attack, to be honest, but after taking a closer look into it the true attack seemed to be a brute force attack.

My setup is a login form on index.php which then posts to login-functions.php… Pretty standard.

Anyway, what was happening was that the attackers were just firing post data straight at login-functions.php in an attempt to get correct combinations.

Luckily I’d already put a brute force prevention measure in place (saving IPs in database & limiting attempts per 15 minutes)… But the problem was that these bogus requests were being redirected to an error page, thus pulling resources & ultimately resulting in the server taking a beating from the load.

So I needed to figure out a way to prevent third parties from being able to shoot post data straight at the form handler & then ideally kill those requests with a simple 403 error to stop the load.

Naturally, I figured it out… 😉 And now I’m posting it here for future reference & for anyone else who might want something similar.

Step 1 – Stop Non-Server Requests

The first bit of code is pretty self-explanatory. Just get the host from the server & make sure that it is actually our server that’s submitting the request.

This should go in your form handler file:

1
2
3
4
5
// If REFERRER is empty, or if it's NOT THIS HOST, then STOP it
if(!isset($_SERVER['HTTP_REFERER']) || parse_url($_SERVER['HTTP_REFERER'])['host'] != $_SERVER['HTTP_HOST']){
header('HTTP/1.0 403 Forbidden');
die("Forbidden");
}

The problem is, whilst this will stop most attacks, it appears that this can be spoofed… Or at least that’s what seemed to happen on my server, so I had to go one step further.

Step 2 – Disabling Framing & Passing a Hidden Value

The first thing I did was disable X-Frames on the page with the form via PHP in order to prevent another host simply framing the form & submitting fake requests through it.

Here’s the code for that:

1
2
// Disable X-frames for security
header('X-Frame-Options: Deny');

And then, using the my_simple_crypt functions, I passed an encrypted time-sensitive value through to the handler.

Here’s the code for creating the value on the form page:

1
2
3
4
5
6
// Protect forms from brute force etc by passing time sensitive value
 
$config_formprotection_secret_key = "jkdsf45689hjhj45" // change this
$formprotection_time = time();
$formprotection_value = "".$formprotection_time.",".$_SERVER['REMOTE_ADDR'].",".$config_formprotection_secret_key."";
$formprotection_encrypted_value = my_simple_crypt($formprotection_value, 'e');

Then I simply pass $formprotection_encrypted_value through to the handler then decrypt & check it on the other side using this code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Check temporary brute force key is valid
if ($_POST['login_temp'] != "") {
$formprotection_decrypted_value = my_simple_crypt($_POST['login_temp'], 'd');
$formprotection_array = explode(',', $formprotection_decrypted_value);
// Check time key isn't older than 15 minutes & that values match
$new_time = (time() - 60*15);
if (($formprotection_array['0'] < $new_time) || ($formprotection_array['1'] != $_SERVER['REMOTE_ADDR']) || ($formprotection_array['2'] != $config_formprotection_secret_key)) {
header('HTTP/1.0 403 Forbidden');
die("Document expired, please try again");
}
} else {
header('HTTP/1.0 403 Forbidden');
die("Forbidden");
}

This way, if the value is missing or invalid the form will simply present a 403 error… So if somebody wanted to carry out a brute force attack, they would have to figure out the correct code (but its tagged with your hidden ID) and they’d also have to recreate it every 15 minutes.

Job done, hopefully… Well at least it’s better protected than it was, anyway.

Leave a Comment