App-BlockWebFlooders

 view release on metacpan or  search on metacpan

script/block-web-flooders  view on Meta::CPAN

if ($action eq 'block') {
    action_block();
} elsif ($action eq 'unblock') {
    action_unblock();
} elsif ($action eq 'list_blocked') {
    action_list_blocked();
} elsif ($action eq 'unblock_all') {
    action_unblock_all();
} elsif ($action eq 'run') {
    require Sys::RunAlone::Flexible;
    Sys::RunAlone::Flexible::lock();
    action_run();
} else {
    die "$PROG: Unknown action '$action'\n";
}

# PODNAME: block-web-flooders
# ABSTRACT: Block IP addresses of web flooders using iptables

__END__

=pod

=encoding UTF-8

=head1 NAME

block-web-flooders - Block IP addresses of web flooders using iptables

=head1 VERSION

This document describes version 0.010 of block-web-flooders (from Perl distribution App-BlockWebFlooders), released on 2019-01-29.

=head1 SYNOPSIS

This script should be run as root/sudo root, because it needs to call the
L<iptables> command to add block rules to the firewall.

First of all, create F</etc/block-web-flooders.conf> that contains something
like this:

 whitelist_ip = 1.2.3.4
 whitelist_ip = ...

Where C<1.2.3.4> is the IP address(es) that you are connecting from (you can see
this from output of L<w> command), to make sure you yourself don't get blocked.
Add more lines/IP address as necessary.

When a flood is happening, try to tail your web access log file:

 # tail -f /s/example.com/syslog/https_access.2017-06-07.log

and see the patterns that you can use to discriminate the requests coming from
the flooder. Since the IP address is usually random/many, you can see from other
patterns e.g. requested URI, user agent. For example, if the suspicious log
lines are something like this:

 93.186.253.79 - - [07/Jun/2017:00:54:23 +0000] "GET /heavy1.php HTTP/1.0" 200 20633 "-" "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.0 Version/10.00"
 51.15.41.74 - - [07/Jun/2017:00:54:25 +0000] "POST /heavy2.php HTTP/1.1" 302 - "-" "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.0 Version/10.00"
 89.38.149.5 - - [07/Jun/2017:00:54:24 +0000] "GET /heavy1.php HTTP/1.0" 200 20633 "-" "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.0 Version/10.00"
 93.186.253.79 - - [07/Jun/2017:00:54:24 +0000] "GET /heavy3.php HTTP/1.0" 200 20524 "-" "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.0 Version/10.00"
 51.15.41.74 - - [07/Jun/2017:00:54:25 +0000] "GET /heavy1.php HTTP/1.0" 200 20633 "-" "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.0 Version/10.00"
 89.38.149.5 - - [07/Jun/2017:00:54:25 +0000] "GET /heavy3.php HTTP/1.0" 200 20524 "-" "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.0 Version/10.00"
 89.38.149.5 - - [07/Jun/2017:00:54:25 +0000] "GET /heavy3.php HTTP/1.0" 200 20524 "-" "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.0 Version/10.00"
 93.186.253.79 - - [07/Jun/2017:00:54:26 +0000] "POST /heavy2.php HTTP/1.1" 302 - "-" "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.0 Version/10.00"
 51.15.41.74 - - [07/Jun/2017:00:54:25 +0000] "GET /heavy1.php HTTP/1.0" 200 20633 "-" "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.0 Version/10.00"
 89.36.213.37 - - [07/Jun/2017:00:54:26 +0000] "GET /heavy3.php HTTP/1.0" 200 20524 "-" "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.0 Version/10.00"
 89.36.213.37 - - [07/Jun/2017:00:54:27 +0000] "POST /heavy2.php HTTP/1.1" 302 - "-" "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.0 Version/10.00"
 89.38.149.5 - - [07/Jun/2017:00:54:26 +0000] "GET /heavy1.php HTTP/1.0" 200 20633 "-" "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.0 Version/10.00"
 89.36.213.37 - - [07/Jun/2017:00:54:26 +0000] "GET /heavy1.php HTTP/1.0" 200 20633 "-" "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.0 Version/10.00"

you can add C<--has Presto/2.2.0> and C<--has /heavy> since these quite
accurately selects the flood requests. If you can add strings which pretty
accurately single out the flood requests, you can use a lower threshold speed,
e.g. C<--limit 5> to block IPs which has requested 5 or more in the last 5
minutes. Otherwise, if you do not have any specific C<--has> to single out the
flood, you might need to set a higher limit, e.g. C<--has html --limit 30
--period 60> to block IPs which have requested 30 or more requests in the last
minute, or C<--limit 200 --period 120> to block IPs which have requested 200 or
more requests in the last 2 minutes.

Feed the output of the C<tail> command to this script:

 # tail -f /s/example.com/syslog/https_access.2017-06-07.log | block-web-flooders \
   --has Presto/2.2.0 --has-pattern '/heavy|/baz' --limit 5

or perhaps:

 # tail -f /s/example.com/syslog/https_access.2017-06-07.log | block-web-flooders \
   --limit 200 --period 120

The script will display the top IP addresses and whether an IP is being blocked,
along with some statistics:

 Blocked IPs this session:  12 | Log lines:  198 | Running for: 2m13s
 Top IPs:
   89.36.213.37    (  4)
   89.38.149.5     (  2)
   93.186.253.79   (  2)
   ...
 Last messages:
   51.15.41.74 BLOCKED

While this script is running, you might also want to open something like this in
another terminal (monitor incoming web requests):

 # tail -f /s/example.com/syslog/https_access.2017-06-07.log | grep /heavy

and somethins like this in yet another terminal (monitor system load and number
of web server processes, this depends on the web server you use):

 # watch 'w | head -n1; echo -n "Blocked IPs total: "; iptables -nL INPUT | wc -l; echo -n "Apache processes: "; ps ax | grep apache | wc -l'

If your webserver is still maxed out by requests, you might want to tweak
C<--limit> and C<--period> options and restart the web server.

To see the blocked IP addresses:

 # iptables -nL INPUT

As long as the script runs, IP addresses are blocked by default temporarily for
86400 seconds (or, according to the --block-period command-line option or
block_period configuration). After that block period is exceeded, the IP is
unblocked.

To immediately clear/unblock all the IPs:



( run in 1.510 second using v1.01-cache-2.11-cpan-39bf76dae61 )