Mailinglist Archive: opensuse-security (232 mails)

< Previous Next >
Re: [suse-security] Under DDoS Attack [160 line perl proxy inside]
  • From: Lars Ellenberg <devnull@xxxxxxxxxx>
  • Date: Fri, 28 Oct 2005 00:46:34 +0200
  • Message-id: <20051027224634.GD20440@xxxxxxxxxxxxxxxxx>
/ 2005-10-27 23:59:54 +0200
\ b@rry:
> >As I said - its a root server. Nothing in front but the pure internet...
>
>
>
> Why not have a firewall in front of it? Root server or no, something that
> can manage the connections to the box with relatively low connection
> timeouts?

as mentioned before,
you could try to proxy...
somewhen, Apache TimeOut directive will be able to configure
initial request timeout, inter-packet timeout and total timeout
independently...

until then,
try attached script.
quick hack in the last half our, so beware.
adjust the timeouts.
consider security implications.
maybe you want to do more verbose, or near to no logging.
or to feed "timed out" ips into some iptables script (after sanitizing)

feel free to reimplement in C,
use select loops instead of many processes,
add command line parameters to adjust the various timeouts,
or just ignore it altogether.

or course, you want to change your apache to listen on the loopback
only, and some different port, and change $apache_port accordingly.
then change $P_port to where your apache typically listens.
maybe you want to bind() more specifically, not to ANY.

run it like "perl -wT myproxy.pl > myproxy.log"

if this script breaks terribly in the real world, so what:
anyways, has been a nice excercise...

cheers,

Lars Ellenberg
#!/usr/bin/perl -Tw
#
# largely from the perlipc man page
#
# intended to be used in front of an apache, that is under flood attack,
# but the attackers don't actually send data...
# btw, you may want to turn off KeepAlive in your Apache for the duration of the attack.
#
# this could use select/poll/whatever constructs and avoid a couple of forks...
# prove of concept only, anyways.
# use at your own risk...
#
# 2005-10-27, Lars Ellenberg
#
use strict;
use Socket;
use Carp;

sub spawn; # forward declaration
sub logmsg { print "$0 $$: @_ at ", scalar localtime, "\n" }

my $apache_ip = '127.0.0.1';
my $apache_port = 80; # <<<=== MOVE IT to where you moved your apache

my $apache_addr = sockaddr_in($apache_port, inet_aton($apache_ip));

my $P_port = 8070; # <<<=== MOVE IT to where apache normally listens
my $proto = getprotobyname('tcp');

socket(Server, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
setsockopt(Server, SOL_SOCKET, SO_REUSEADDR,
pack("l", 1)) || die "setsockopt: $!";
bind(Server, sockaddr_in($P_port, INADDR_ANY)) || die "bind: $!";
listen(Server,SOMAXCONN) || die "listen: $!";

logmsg "server started on port $P_port";

my $waitedpid = 0;
my $paddr;

use POSIX ":sys_wait_h";
sub REAPER {
my $child;
while (($waitedpid = waitpid(-1,WNOHANG)) > 0) {
logmsg "reaped $waitedpid" . ($? ? " with exit $?" : '');
}
$SIG{CHLD} = \&REAPER; # loathe sysV
}

$SIG{CHLD} = \&REAPER;

my ($iaddr,$c_port);
for ( $waitedpid = 0;
($paddr = accept(Client,Server)) || $waitedpid;
$waitedpid = 0, close Client # still open in forked kids...
)
{
next if $waitedpid and not $paddr;
($c_port,$iaddr) = sockaddr_in($paddr);

#my $name = gethostbyaddr($iaddr,AF_INET);
#logmsg "connection from $name [",
# inet_ntoa($iaddr), "]
# at port $c_port";
logmsg "connect ",inet_ntoa($iaddr),":$c_port";

spawn \&proxy
}

sub spawn {
my $coderef = shift;

unless (@_ == 0 && $coderef && ref($coderef) eq 'CODE') {
confess "usage: spawn CODEREF";
}

my $pid;
if (!defined($pid = fork)) {
logmsg "cannot fork: $!";
return;
} elsif ($pid) {
logmsg "begat $pid";
return; # I'm the parent
}
# else I'm the child -- go spawn

exit (&$coderef() || 0);
}


sub proxy() {
# could use some select()
#my ($rout,$rin,$wout,$win,$eout,$ein);
#vec($rin,fileno(Client),1) = 1;
#vec($win,fileno(Client),1) = 1;
#$ein = $rin | $win;
# select(..., timeout)
# but don't bother...

# wait for initial request data to be sent
eval {
local $SIG{ALRM} = sub { die "timed out\n"; };
alarm 3;
sysread Client,$_,10;
alarm 0;
};
if (length($_) < 5 or $@ eq "timed out\n")
{
# think about the condition in the if above...
# try to avoid false positives
# well. handle it somehow, log it, iptable it
# don't forget to whitelist yourself, if you iptable it
# don't log the received data directly!
die(sprintf "timed out: %s, %u byte read\n", inet_ntoa($iaddr), length($_));
}

# connect to real apache
socket(APACHE, PF_INET, SOCK_STREAM, $proto) || die "socket APACHE: $!";
connect(APACHE, $apache_addr) || die "connect APACHE: $!";

my $pid;
if (!defined($pid = fork)) {
logmsg "cannot fork: $!";
shutdown APACHE,2;
return 0;
} elsif ($pid) {
# reader, forward to apache
# maybe some timeout again?
select APACHE; $| = 1; select STDOUT;
eval {
local $SIG{ALRM} = sub { die "timed out\n" if length($_) <= 200; };
alarm 5;
print APACHE $_;
while (sysread Client,$_,4096) {
alarm 5; # extend period
print APACHE $_ or last;
}
};
alarm 0;
shutdown APACHE,1;
# shutdown Client,0; # XXX really? now?
# if he is an attacker, we don't care,
# and if he is legit, this might confuse a client that expects
# a "keepalive" type connection.
# will close when the last of read/writer exits...
return 1; # reader done
}
# writer, forward to client
# maybe some timeout again?
select Client; $| = 1; select STDOUT;
while (defined($_ = <APACHE>)) {
print Client $_ or last;
}
shutdown APACHE,0;
shutdown Client,1;
return 2;
}
< Previous Next >
List Navigation
References