| Accueil | FAQ | Statistiques | Divers | Contact |
Securité : Linux : Jamd
Jamd est un petit daemon qui permet de tarpiter/engluer les scanners de port, script-kiddies, spammers et dans certains cas, certaines attaques DoS (slowloris).
La méthode du tarpit est connue depuis longtemps grâce notamment à l'utilitaire LaBrea mais aussi au module tarpit d'iptables. Jamd reprend le même concept mais en simplifiant son utilisation :
Le lancement nécessite comme paramètres au moins l'interface ainsi qu'un port ou une IP (source ou destination).
Les paramètres '--dport' et '--sport' acceptent un seul port (single port), une combinaison (multiports) ou éventail (port range) de ports.
Les modules suivants sont nécessaire pour lancer jamd :
Dans tous les cas de figure, vous devez vous assurer que votre serveur ne réponde pas aux requêtes que vous souhaitez tarpiter. Cela implique généralement l'ajout d'une règle au firewall pour ignorer ces paquets. Avec iptables sous linux, utilisez l'action '-j DROP'.
Voici une liste non exhaustive d'exemples d'utilisation de jamd.
1) bloquez ces ports avec iptables (sous Linux) :
2) lancez jamd (port range):
1) bloquez tout accès à cette IP :
2) lancez jamd :
1) bloquez tout accès à ces 2 ports :
2) lancez jamd (multiports) :
1) bloquez l'accès de l'attaquant à votre port 80 :
2) lancez jamd :
3) relancez Apache pour qu'il se remette de ses émotions et ferme toutes ces sockets.
En quelques secondes, sans le savoir ni même s'en apercevoir, l'attaquant va se transformer en victime. Les 2000 sockets établies seront maintenues sur son serveur uniquement, pas sur le votre. Le résultat est à peu près équivalent à une attaque DoS "netkill" inversée.
1) créez un sous domaine (ex : jamd.domaine.com), faitez-le pointer sur cette IP et ajoutez-lui un champ MX :
3) bloquez l'accès à votre port 25 :
4) lancez jamd :
Les spammers se feront un plaisir de voler ces adresses email et de venir s'engluer sur votre port 25.
Dernière version : v1.0.1 (05-Mai-2010)
I - Présentation :
- portabilité : il fonctionne sous toute distribution Linux, FreeBSD et probablement autres *BSD et peut-être même sous Mac.
- simplicité : on peut le lancer et l'arrêter quand on veut, il n'y a rien à compiler et/ou patcher ni même à configurer.
II - Paramètres :
options :
--stop : arrête daemon.
--help : affiche ce menu.
--debug : tourne en mode debug (ne lance pas le daemon).
--test : simulation (comme debug mais ne répond pas aux paquets).
--nopromisc : ne met pas l'interface en mode promiscuous.
--interface : interface réseau.
--stats : affiche les stats.
TCP parameters :
--source <IP> : IP du client (la victime).
--sport <src_port> : port du client (single port '--sport 25', multiports
'--sport 25,80,81' ou port range '--sport 25-81').
--destination <IP> : IP de destination (votre serveur).
--dport <dest_port> : port de destination (single port '--dport 25', multiports
'--dport 25,80,81' ou port range '--dport 25-81').
III - Module Perl nécessaires :
IV - Utilisation :
# iptables -I INPUT -i eth0 -d 1.2.3.4 -p tcp --dport 137:139 -j DROP
# jamd --interface eth0 --destination 1.2.3.4 --dport 137-139
# iptables -I INPUT -i eth0 -d 1.2.3.4 -j DROP
# jamd --interface eth0 --destination 1.2.3.4
# iptables -I INPUT -i eth0 -d 1.2.3.4 -p tcp -m multiport --dports 21,110
# jamd --interface eth0 --destination 1.2.3.4 --dport 21,110
# iptables -I INPUT -i eth0 -s 2.2.2.2 -p tcp --dport 80 -j DROP
# jamd --interface eth0 --destination 1.2.3.4 --dport 80 --source 2.2.2.2
jamd.domaine.com. IN MX 10 jamd.domaine.com.
2) insérez/cachez des dizaines d'adresses email dans le code HTML de votre site (ex: hello@jamd.domaine.com, eatme@jamd.domaine.com, spamsucks@jamd.domaine.com etc).
# iptables -I INPUT -i eth0 -d 1.2.3.4 -p tcp --dport 25 -j DROP
# jamd --interface eth0 --destination 1.2.3.4 --dport 25
V - Source :
#!/usr/bin/perl
######################################################################
# jamd :
#
# tarpit daemon to jam ports scanners, script-kiddies,
# spammers and some DoS attacks (eg: slowloris)
#
# (c) Jerome Bruandet - http://spamcleaner.org/
#
# version 1.0.1 - 05-Mai-2010
#
# doc : http://spamcleaner.org/en/misc/jamd.html
#
######################################################################
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
######################################################################
use strict;
use Socket;
use Net::RawIP;
use Net::Pcap;
use NetPacket::Ethernet qw(:strip);
use NetPacket::IP;
use NetPacket::TCP;
use POSIX qw(setsid);
use Getopt::Long;
# signal handlers :
$SIG{INT} = \&stop;
$SIG{QUIT} = \&stop;
$SIG{TERM} = \&stop;
my $appname = 'jamd';
my $version = '1.01';
my $logfile = '/var/log/jamd.log';
my $pidfile = '/var/run/jamd.pid';
my $copyright = '(c) 2010 Jerome Bruandet - http://spamcleaner.org/';
if ( $> ) {
print "$appname $version - $copyright\n\n";
print "\t[ERROR] : you must be root\n\n";
exit 1;
}
my ( $pid, $res, $date, $error );
my ( $err, $addr, $mask, $filter_t, $pcap, $test, $filter );
my ( $debug, $stop, $help, $interface, $nopromisc,
$source, $sport, $destination, $dport );
my $promisc = 1;
# gestion des paramètres :
GetOptions (
'debug' => \$debug,
'stop' => \&stop,
'test' => \$test,
'help' => \&help,
'nopromisc' => \$nopromisc,
'interface=s' => \$interface,
'source=s' => \$source,
'sport=s' => \$sport,
'destination=s' => \$destination,
'dport=s' => \$dport,
'stats' => \&show_stats,
);
my @tmp_filter;
$promisc = 0 if ( $nopromisc );
# récupère les paramètres port/port range/multiport :
if ( $sport =~ /-/ ) {
push @tmp_filter, 'tcp src portrange ' . $sport . ' ';
} elsif ( $sport =~ /,/ ) {
my @tmp = split( /,/, $sport );
$sport = '';
foreach ( @tmp ) {
goto sp if (! $sport );
$sport.= 'and ';
sp: $sport.= "tcp src port $_ ";
}
push @tmp_filter, $sport;
} elsif ( $sport =~ /^\d+$/ ) {
push @tmp_filter, 'tcp src port ' . $sport . ' ';
} else {
$sport = '';
}
if ( $dport =~ /-/ ) {
push @tmp_filter, 'tcp dst portrange ' . $dport . ' ';
} elsif ( $dport =~ /,/ ) {
my @tmp = split( /,/, $dport );
$dport = '';
foreach ( @tmp ) {
goto dp if (! $dport );
$dport.= 'and ';
dp: $dport.= "tcp dst port $_ ";
}
push @tmp_filter, $dport;
} elsif ( $dport =~ /^\d+$/ ) {
push @tmp_filter, 'tcp dst port ' . $dport . ' ';
} else {
$dport = '';
}
# paramètres des IPs :
if ( $source ) {
if ( $source !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ ) {
print "$appname $version : $copyright\n\n";
print "\t[ERROR] : source IP ($source) is not correct\n\n";
exit 1;
} else {
push @tmp_filter, 'src host ' . $source . ' ';
}
}
if ( $destination ) {
if ( $destination !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ ) {
print "$appname $version : $copyright\n\n";
print "\t[ERROR] : destination IP ($destination) is not correct\n\n";
exit 1;
} else {
push @tmp_filter, 'dst host ' . $destination . ' ';
}
}
if ( ( ! $source ) && ( ! $sport ) && ( ! $destination ) && ( ! $dport ) ) {
print "$appname $version : $copyright\n\n";
print "\t[ERROR] : you must select at least a port (or an IP)\n";
print "\tand the network interface\n\nrun jamd --help\n\n";
exit 1;
}
# interface :
if ( ! $interface ) {
print "$appname $version : $copyright\n\n";
print "\t[ERROR] : interface is missing\n\n";
exit 1;
}
`ifconfig | grep '^$interface' > /dev/null`;
if ( $? >> 8) {
print "$appname $version : $copyright\n\n";
print "\t[ERROR] : cannot find interface ($interface)\n\n";
exit 1;
}
$debug = 1 if $test;
# préparation du filtre :
foreach ( @tmp_filter ) {
goto filter if (! $filter);
$filter.= 'and ';
filter:
$filter.= $_;
}
chop $filter if ( $filter =~ /\s$/ );
&run;
exit;
######################################################################
sub run {
my $res = &is_running;
if (! $res ) {
print "$appname $version : $copyright\n\n";
print "\t[ERROR] : daemon is already running (PID : $pid)\n\n";
exit 1;
}
if ( ! $debug ) {
print "$appname $version : $copyright\n\n" .
"\t- starting daemon on interface [$interface]\n" .
"\t- filter : $filter\n" .
"\t- DON'T FORGET to drop those packets with your firewall !\n\n";
}
# lance le daemon :
&daemonize if ( ! $debug );
# sauvegarde son PID :
open PID, ">$pidfile";
print PID $$;
close PID;
# fichier log :
open LOG, ">>$logfile";
select((select(LOG), $|=1)[0]);
$date = `date '+%d/%b/%Y %T'`;
chomp $date;
my $temp;
if ( $test ) { $temp = ' in ** test mode **' }
elsif ( $debug ) { $temp = ' in ** debug mode **' }
print LOG "$date [$appname $version] : starting ".
"daemon (PID : $$)$temp\n";
print "$appname $version : $copyright\n\n";
print "\t[OK] : starting daemon (PID : $$)$temp\n\n";
print "- setting up packet sniffer :\n" .
"\tinterface : $interface\n" if $debug;
print LOG "$date [$appname $version] : interface : $interface\n";
if ( Net::Pcap::lookupnet( $interface, \$addr, \$mask, \$err) == -1 ) {
print LOG "$date [$appname $version] : ERROR ".
"Net::Pcap::lookupnet failed ($err)\n";
print "- ERROR : check $logfile\n" if ( $debug );
$error = 1;
&stop;
}
print "\tpromiscuous : $promisc\n" if $debug;
print LOG "$date [$appname $version] : promiscuous mode : $promisc\n";
# on capture 96 octets maxi pour chaque paquet TCP :
my $pcap = Net::Pcap::open_live( $interface, 96, $promisc, 0, \$err);
if (! $pcap ) {
print LOG "$date [$appname $version] : ERROR ".
"Net::Pcap::open_live failed ($err)\n";
print "- ERROR : check $logfile\n" if ( $debug );
$error = 1;
&stop;
}
print "\tfilter : $filter\n" if $debug;
print LOG "$date [$appname $version] : filter : '$filter'\n";
if ( Net::Pcap::compile( $pcap, \$filter_t, $filter, 0, $mask ) == -1 ) {
print LOG "$date [$appname $version] : ERROR ".
"Net::Pcap::compile failed\n";
print "- ERROR : check $logfile\n" if ( $debug );
$error = 1;
&stop;
}
Net::Pcap::setfilter( $pcap, $filter_t);
print "- listening...\n" if $debug;
print LOG "$date [$appname $version] : listening....\n";
# loop infinie :
Net::Pcap::loop($pcap, -1, \&process_packet, 0);
}
######################################################################
sub process_packet {
my( $user_data, $header, $packet ) = @_;
# on vérifie s'il s'agit d'un paquet SOCK_RAW
# ou bien d'un paquet SOCK_DGRAM (linux cooked) :
if ( $packet =~ /^.{14}\x08\x00/ ) {
print "- linux cooked detected, removing 2 bytes\n" if $debug;
# suppression des 2 premiers octets si SOCK_DGRAM :
$packet = substr( $packet, 2);
}
# decode le paquet :
my $ether_data = NetPacket::Ethernet::strip($packet);
my $ip = NetPacket::IP->decode($ether_data);
my $tcp = NetPacket::TCP->decode($ip->{'data'});
# vérifie ses flags :
if ( $tcp->{flags} == SYN ) {
print "- SYN from [$ip->{src_ip}:$tcp->{src_port}]" .
" to [$ip->{dest_ip}:$tcp->{dest_port}]" .
" => sending SYN/ACK\n" if $debug;
$date = `date '+%d/%b/%Y %T'`;
chomp $date;
print LOG "$date [$appname $version] : connection from " .
"$ip->{src_ip}:$tcp->{src_port} to ".
"$ip->{dest_ip}:$tcp->{dest_port}\n";
# on ne fait rien de plus si on est en mode 'test' :
return if $test;
# on repond au SYN avec un SYN/ACK pour 'établir' la connexion :
my $seq = int( rand( 5000000 ) );
my $packet = Net::RawIP->new( {
ip => {
frag_off => 0, tos => 0,
saddr => $ip->{dest_ip},
daddr => $ip->{src_ip}
},
tcp =>{
dest => $tcp->{src_port},
source => $tcp->{dest_port},
seq => $seq,
ack => 1,
syn => 1,
ack_seq => $tcp->{seqnum}+1,
window => 0
}
} );
$packet->send;
} elsif ( $tcp->{flags} == ( SYN | ACK ) ) {
print "- SYN/ACK from [$ip->{src_ip}:$tcp->{src_port}]" .
" to [$ip->{dest_ip}:$tcp->{dest_port}]" .
" => sending RST\n" if $debug;
return if $test;
# on n'envoie pas de SYN, on n'a donc pas de raison
# de recevoir un SYN/ACK => on reset :
my $packet = Net::RawIP->new( {
ip => {
frag_off => 0,
tos => 0,
saddr => $ip->{dest_ip},
daddr => $ip->{src_ip}
},
tcp =>{
dest => $tcp->{src_port},
source => $tcp->{dest_port},
seq => $tcp->{acknum},
rst => 1
}
} );
$packet->send;
} elsif (( $tcp->{flags} == ACK ) || ( $tcp->{flags} == (PSH | ACK) )) {
print "- ACK from [$ip->{src_ip}:$tcp->{src_port}]" .
" to [$ip->{dest_ip}:$tcp->{dest_port}]" .
" => jamming with 0-byte window !\n" if $debug;
return $test;
# notre victime pense que la connexion est établie,
# on continue et on le ralenti en envoyant des ACKs,
# avec une fenêtre initialisée à 0 :
my $packet = Net::RawIP->new( {
ip => {
frag_off => 0,
tos => 0,
saddr => $ip->{dest_ip},
daddr => $ip->{src_ip}
},
tcp => {
dest => $tcp->{src_port},
source => $tcp->{dest_port},
seq => $tcp->{acknum},
ack => 1,
syn => 0,
ack_seq => $tcp->{seqnum}+1,
window => 0
}
} );
$packet->send;
} else {
# ce paquet ne nous interesse pas, on l'ignore :
print "- ignoring TCP flag #" . $tcp->{flags} .
" from [$ip->{src_ip}:$tcp->{src_port}]" .
" to [$ip->{dest_ip}:$tcp->{dest_port}]\n" if $debug;
}
}
######################################################################
sub stop {
my $res = &is_running;
print "$appname $version : $copyright\n\n";
if ( $res ) {
print "\t[ERROR] : daemon is not running\n\n";
exit 1;
}
# on n'a pas beaucoup de choix : on le tue violemment sinon
# la loop de Net::Pcap pourrait ne pas être interrompue :
if ( kill 9, $pid ) {
unlink $pidfile;
$date = `date '+%d/%b/%Y %T'`;
chomp $date;
open LOG, ">>$logfile";
print LOG "$date [$appname $version] : stopping daemon\n";
close LOG;
print "\t[OK] : stopping daemon\n\n";
exit;
# unlikely... :
} else {
print "\t[ERROR] : cannot stop the daemon\n\n";
}
}
######################################################################
sub daemonize {
chdir '/' or die "- [ERROR] : cannot chdir to [/] : $!\n";
umask 0;
open STDIN, '/dev/null' or
die "- [ERROR] : cannot read [/dev/null] : $!\n";
open STDOUT, '>/dev/null' or
die "- [ERROR] : cannot write to [/dev/null] : $!\n";
open STDERR, '>/dev/null'
or die "- [ERROR] : cannot write to [/dev/null] : $!\n";
defined(my $pid = fork) or die "- [ERROR] : cannot fork : $!\n";
exit if $pid;
setsid or die "- [ERROR] : cannot start a session : $!\n";
}
######################################################################
sub is_running{
if ( -e $pidfile ){
open IN, "<$pidfile";
while (<IN>){
chomp;
$pid = $_;
last;
}
close IN;
`ps p $pid >/dev/null`;
$res = ( $? >> 8 );
return $res;
}
return 1;
}
######################################################################
sub help {
print "\n$appname $version - $copyright
options :
--stop : stop the daemon.
--help : display this menu.
--debug : run in debug mode (verbose, no daemon).
--test : simulation (same as debug but doesn't send packets).
--nopromisc : do not use promiscuous mode.
--interface : network interface.
--stats : display stats
TCP parameters :
--source <IP> : source IP (the victim).
--sport <src_port> : source port (single port '--sport 25', multiports
'--sport 25,80,81' or port range '--sport 25-81').
--destination <IP> : destination IP (your server).
--dport <dest_port> : destination port (single port '--dport 25', multiports
'--dport 25,80,81' or port range '--dport 25-81').
";
exit;
}
######################################################################
sub show_stats {
print "\n$appname $version - $copyright\n\n";
if (! -e $logfile ) {
print "\t[ERROR] : you do not have any log yet !\n\n";
exit 1;
}
my ( $start_date, %jammed );
my $today = 0; my $tot = 0;
$date = `date '+%d/%b/%Y'`;
chomp $date;
open IN, "<$logfile";
while ( <IN> ) {
if ( (/^(\d{2}\/.+?\/\d{4})\s+/) && ( ! $start_date ) ){
$start_date = $1;
} else {
if (/^(\d{2}\/.+?\/\d{4})\s+.+?connection from ([\d.]+):/ ) {
if ( $1 eq $date ) {
$today++;
}
$jammed{$2}++;
$tot++;
}
}
}
close IN;
if ( ! $tot ) {
print "- no jammed IPs yet!\n\n";
exit;
}
my $logsize = (stat( $logfile ))[7];
print "- logfile size : $logsize bytes\n";
print "- total jammed connections since $start_date : $tot\n";
print "- total jammed connections today $date : $today\n";
print "- top 15 script-kiddies :\n\t IPs connections\n";
my $count;
foreach my $jamip ( sort { $jammed{$b} <=> $jammed{$a} } keys %jammed ) {
$count++;
print "\t$jamip";
for ( length( $jamip ) .. 15 ) { print " " };
print " $jammed{$jamip}\n";
last if $count == 15;
}
exit;
}
######################################################################
# EOF
VI - Téléchargement :
![]()