Brute force attacks are very common on the Internet. They usually involve automatic scripts that attempt to log in to a specific service by trying the maximum number of logins per minute possible. These logins can cause degradation of the service or even a denial of service. In this article, we will see how to prevent this type of attack, either on a final server or by protecting an entire network behind PF, using overload tables.
First, we prepare the testing scenario. In my case, it is an Nginx with basic user/password authentication.
We install Nginx:
We generate the credentials file:
openssl passwd -apr1 >> /usr/local/etc/nginx/.htpasswd
We configure Nginx to request access credentials:
...
location / {
auth_basic "Login Required";
auth_basic_user_file /usr/local/etc/nginx/.htpasswd;
root /usr/local/www/nginx;
index index.html index.htm;
}
We enable and start the service:
service nginx restart
We access to check that everything works normally:
http://192.168.69.55
On the attacking host, we install
Hydra
, a well-known online cracker.
We launch the attack:
[STATUS] 8615.00 tries/min, 8615 tries in 00:01h, 14335784 to do in 27:45h, 16 active
On the server/router, we configure the overloaded table:
table <bruteforce> persist
block quick from <bruteforce>
pass quick proto tcp from any to any port http flags S/SA keep state (max-src-conn 15, max-src-conn-rate 5/3, overload <bruteforce> flush global)
The parameters of the overloaded table are:
- max-src-conn is the number of simultaneous connections allowed from one host.
- max-src-conn-rate is the rate of new connections allowed from any single host (15) per number of seconds (5).
- overload
means that any host which exceeds these limits gets its address added to the bruteforce table. The ruleset blocks all traffic from addresses in the bruteforce table. - Finally, flush global says that when a host reaches the limit, that all (global) of that hostβs connections will be terminated (flush).
The complete script would look like this:
ext_if = "em0"
table <bruteforce> persist
set block-policy return
scrub in on $ext_if all fragment reassemble
set skip on lo
antispoof for $ext_if inet
block log all
block quick from <bruteforce>
pass quick proto tcp from any to any port http flags S/SA keep state (max-src-conn 15, max-src-conn-rate 5/3, overload <bruteforce> flush global)
pass out
pass in proto tcp to any port 22
pass in proto tcp to any port 80
Apply the rules:
We will see that Hydra starts giving connection errors:
[ERROR] Child with pid 55961 terminating, can not connect
If we check the content of the bruteforce table, we will see the attacker’s IP:
192.168.69.4
In most cases, we only need a temporary ban, so we can schedule a crontab entry like this to clean up the tables by removing entries that have not been referenced in the last X seconds.
Crontab:
00 11 * * * pfctl -t bruteforce -T expire 86400
One of the most common attacks is on SSH, with PF we could prevent such attacks in the same way we did for port 80:
pass quick proto tcp from any to any port ssh flags S/SA keep state (max-src-conn 15, max-src-conn-rate 5/3, overload <bruteforce> flush global)