Here I present a script which works like a simple intrusion detections system.
I have made the script a long ago and used it for a long time.
Now I have decided to share the script with the community.
If this Forum is not a right place for such publications, I hope, the Community can suggest the right place.
The script uses the IPFW firewall and blocks connections and/or datagrams from hosts wich expose extremal traffic statistics. The script initialy was created to block password guessing attacks on ssh, ftp and similar services. However later I have used it successfully to resist to DNS DDOS attack.
The corresponding IPFW configuration:
Here rule numbers 4000 and 4499 (-1) corresponds with script variables
and 4001 - 4498 is a place for rules generated by the script.
The $fwcmd and $oif shell variables contain the path to ipfw command and the name of the WAN network interface respectively.
These variables should be defined somewhere above, this is a standard practice for IPFW configuration scripts.
The corresponding syslog.conf configuration:
Here the 1st line was in the syslog.conf file from the beginning (from the installation), and the second line is added especially to activate the script.
Note that the syslog facility which is accepting IPFW log messages (the "security" in the example) differs from version to version of FreeBSD and in your system in could be the other. For instance, on some older system
I have to provide the same with different lines:
]
(As in previous example, the 1st line was here from the beginning and the second was added especialy to activate the script.)
The sample parameter block to resist DNS DDOS attack:
I keep this parameter block in a separate file and the parameters replace the original ones when the script is called with the path to the file as command line parameter.
Corresponding syslog.conf line is:
(Or similar on the base of the "!ipfw" trick, see above.)
Corresponding IPFW configuration is:
However I found that is useful also to slow down the speed of incoming DNS datagrams by means of IPFW pipe facility.
That's all. Hope, this can help anybody.
I have made the script a long ago and used it for a long time.
Now I have decided to share the script with the community.
If this Forum is not a right place for such publications, I hope, the Community can suggest the right place.
The script uses the IPFW firewall and blocks connections and/or datagrams from hosts wich expose extremal traffic statistics. The script initialy was created to block password guessing attacks on ssh, ftp and similar services. However later I have used it successfully to resist to DNS DDOS attack.
Code:
#!/usr/bin/perl
$PROTO = 'tcp';
$SETUP = 'setup';
$MIN_MEAS_TIME = 120; #sec.
$MIN_MEAN_INT = 10; #sec
$MAX_MEAN_INT = 60;
$MAX_PEAK_COUNT = 10;
$PEAK_TIME = 0;
$CLEARING_PERIOD = 180; #sec
$ANL_RULE_NO = 4000;
$MIN_RULE_NO = 4001;
$MAX_RULE_NO = 4498;
$RULES_FILE = '/var/tmp/detect_rules';
if (-f $ARGV[0])
{
do $ARGV[0];
}
if (0)
{
print "PROTO=$PROTO, MIN_MEAS_TIME=$MIN_MEAS_TIME, MIN_MEAN_INT=$MIN_MEAN_INT, ".
"MAX_MEAN_INT=$MAX_MEAN_INT, MAX_PEAK_FREQ=$MAX_PEAK_FREQ, PEAK_TIME=$PEAK_TIME, ".
"CLEARING_PERIOD=$CLEARING_PERIOD, ".
"ANL_RULE_NO=$ANL_RULE_NO, MIN_RULE_NO=$MIN_RULE_NO, MAX_RULE_NO=$MAX_RULE_NO, ".
"RULES_FILE=$RULES_FILE\n";
exit 0;
}
sub print_time()
{
my $strnow = localtime();
print "$strnow : ";
}
#sub save_pid
#{
# open(PID, ">$PID_FILE");
# print PID "$$\n";
# close(PID);
#}
sub install_rule
{
my ($rule, $host, $port) = @_;
my $command = "/sbin/ipfw add $rule deny $PROTO from $host to any $port $SETUP";
print_time();
print "$command\n";
system($command);
}
sub uninstall_rule
{
my $rule = $_[0];
my $command = "/sbin/ipfw delete $rule";
print_time();
print "$command\n";
system($command);
}
@numbers = ();
for ($i = $MIN_RULE_NO; $i <= $MAX_RULE_NO; $i++)
{
$numbers[$i] = 0;
}
sub get_number
{
my $i;
for ($i = $MIN_RULE_NO; $i <= $MAX_RULE_NO; $i++)
{
if (!$numbers[$i])
{
$numbers[$i] = 1;
return $i;
}
}
return $MAX_RULE_NO;
}
sub save_numbers
{
open(NUMBERS, ">$RULES_FILE");
my $i;
for ($i = $MIN_RULE_NO; $i <= $MAX_RULE_NO; $i++)
{
if ($numbers[$i])
{
print NUMBERS "$i\n";
}
}
close(NUMBERS);
}
sub delete_old_rules
{
open(NUMBERS, "<$RULES_FILE");
while($str = <numbers>)
{
chomp $str;
uninstall_rule $str;
}
close(NUMBERS);
save_numbers();
}
%table = ();
sub analyze
{
my $key = $_[0];
my $stime = $table{$key}{stime};
my $time = time() - $stime;
my $count = $table{$key}{count};
#print "$key $count $time ($stime)\n";
if (!$count) { return; }
my $meanint = $time / $count;
if ($time > 0 && $time <= $PEAK_TIME)
{
if ($count > $MAX_PEAK_COUNT)
{
if (!$table{$key}{rule})
{
$table{$key}{rule} = get_number();
install_rule($table{$key}{rule}, $table{$key}{host}, $table{$key}{port});
save_numbers();
}
}
}
elsif ($time > $MIN_MEAS_TIME)
{
if ($meanint < $MIN_MEAN_INT)
{
if (!$table{$key}{rule})
{
$table{$key}{rule} = get_number();
install_rule($table{$key}{rule}, $table{$key}{host}, $table{$key}{port});
save_numbers();
}
}
elsif ($meanint > $MAX_MEAN_INT)
{
if ($table{$key}{rule})
{
uninstall_rule($table{$key}{rule});
$numbers[$table{$key}{rule}] = 0;
save_numbers();
}
delete $table{$key};
}
}
}
sub clear
{
for my $key (keys %table)
{
analyze($key);
}
}
sub count
{
my $host = $_[0];
my $port = $_[1];
my $str = $_[2];
#print "$host $port from $str\n";
my $key = $host.'-'.$port;
if (exists $table{$key})
{
$table{$key}{count}++;
analyze($key);
}
else
{
$table{$key} = { host => $host, port => $port, stime => time(), count => 1, rule => 0 };
#print "+++",$key,"+++",%{$table{$key}}, "+++\n";
}
}
$| = 1;
print_time();
print "$0 started\n";
delete_old_rules();
#save_pid();
$ctime = time();
while ($str = <stdin>)
{
#print "$str";
#exit 0;
if ($str =~ /ipfw: ${ANL_RULE_NO} [A-z, ]+([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\:[0-9]* [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\:[0-9]*)/)
{
print "$str";
count($1, $2, $str);
}
if (time() - $ctime > $CLEARING_PERIOD)
{
clear();
$ctime = time();
}
}
Code:
# make a hole for password guess blocking
idsports="21,22,23,53,110,143,3389"
$fwcmd add 4000 count log tcp from any to any $idsports setup in via $oif
$fwcmd add 4499 count tcp from any to any $idsports setup in via $oif
Here rule numbers 4000 and 4499 (-1) corresponds with script variables
Code:
]
$ANL_RULE_NO = 4000;
$MIN_RULE_NO = 4001;
$MAX_RULE_NO = 4498; # note
The $fwcmd and $oif shell variables contain the path to ipfw command and the name of the WAN network interface respectively.
These variables should be defined somewhere above, this is a standard practice for IPFW configuration scripts.
The corresponding syslog.conf configuration:
Code:
security.* /var/log/security
security.* |exec /path/to/the/script >>/path/to/script's/log/file 2>&1
Here the 1st line was in the syslog.conf file from the beginning (from the installation), and the second line is added especially to activate the script.
Note that the syslog facility which is accepting IPFW log messages (the "security" in the example) differs from version to version of FreeBSD and in your system in could be the other. For instance, on some older system
I have to provide the same with different lines:
Code:
!ipfw
*.* /var/log/ipfw.log
*.* |exec /path/to/the/script >>/path/to/script's/log/file 2>&1
(As in previous example, the 1st line was here from the beginning and the second was added especialy to activate the script.)
The sample parameter block to resist DNS DDOS attack:
Code:
$PROTO = 'udp';
$SETUP = '';
$MIN_MEAS_TIME = 120; #sec.
$MIN_MEAN_INT = 5; #sec
$MAX_MEAN_INT = 30;
$MAX_PEAK_COUNT = 30;
$PEAK_TIME = 5;
$ANL_RULE_NO = 4501;
$MIN_RULE_NO = 4502;
$MAX_RULE_NO = 4999;
$RULES_FILE = '/var/tmp/detect_udp_rules';
Corresponding syslog.conf line is:
Code:
security.* |exec /path/to/the/script /path/to/the/parameter/file >>/path/to/script's/log/file 2>&1
(Or similar on the base of the "!ipfw" trick, see above.)
Corresponding IPFW configuration is:
Code:
idsuports="53"
$fwcmd add 4501 count log udp from any to any $idsuports in via $oif
$fwcmd add 5000 count udp from any to any $idsuports in via $oif
That's all. Hope, this can help anybody.
Last edited: