Администраторы больших сетей регулярно получают жалобы на исходящий из них спам. Получив жалобу,
можно найти виновного и устранить проблему. Но нехорошо узнавать о таких делах только от чужих людей.
Спамеров вполне можно вычислить самому, и принять меры раньше, чем кто-то пострадает.Чисто интуитивно достаточно запустить tcpdump на исходящем канале, и посмотреть, какие IP-адреса больше в
сего посылают SYN-пакетов на 25 порт.tcpdump -ni bridge0 'tcp[tcpflags] & tcp-syn != 0 and dst port 25'
В идеале это был бы ваш SMTP-сервер. В действительности вы увидите в основном IP-адреса простых пользователей,
"одержимых бесами". Дело в том, что обычный спамбот создает SMTP-соединений во много раз больше
вашего почтового сервера. Причем с разными IP-адресами.В наши дни все нормальные почтовые сервера имеют PTR-запись DNS. Простые клиенты, напротив,
такой записи обычно не имеют. Если почтовый сервер может устанавливать SMTP-соединения
со множеством IP-адресов, то для простого клиента эта величина обычно не более 1-2 (например,
пользование удаленными SMTP-серверами с лаптопа).Исходя из этого, можно автоматически определять спамботов в нашей сети, и блокировать их (например,
средствами IPFW). В качестве критерия оценки мы установили максимальное количество IP-адресов,
с которыми каждый хост может общаться по SMTP в течение 5 минут. Для хостов с реверсной записью DNS это 200,
для хостов без реверсной записи - 20. Если хост превышает этот лимит, он попадает в черный список на сутки.Нами написан Perl-скрипт, запускающий tcpdump на 5 минут, и анализирующий результаты.
Хосты-нарушители заносятся в таблицу PostgreSQL для удобства персонала, и затем помещаются
в таблицу IPFW. Правила файрвола блокируют все соединения на 25 порт с хостов, находящихся в данной таблице.Текст скрипта:
#!/usr/local/bin/perl
# dimss@stalin.lv 2009-08-20my $colltime = 300; # How long to collect data
my $rev_limit = 200; # Remote IP limit for hosts with PTR record
my $norev_limit = 20; # Remote IP limit for hosts without PTR record
my $table_no = 1; # Number of IPFW tableuse strict;
use warnings;use POSIX ':signal_h';
use Socket;
use DBI;#
# Collect data
# Run tcpdump for some time
#my $conns = {};
my $mask = POSIX::SigSet->new( SIGALRM );
my $action = POSIX::SigAction->new(
sub {
system("killall tcpdump");
},
$mask
);
my $oldaction = POSIX::SigAction->new();
sigaction(14, $action, $oldaction);alarm($colltime); # Run tcpdump for this amount of time
open(T, "/usr/sbin/tcpdump -ni bridge0 'tcp[tcpflags] & tcp-syn != 0 " .
"and dst port 25' " .
"2>/dev/null |") or die;while(<T>){
/\ ((\d+\.){3}\d+).+?((\d+\.){3}\d+)/;
my $locip = $1;
my $remip = $3;
#print "$locip $remip\n";
$conns->{$locip} ||= {};
$conns->{$locip}->{$remip} ||= 1;
}
alarm(0);
sleep(1);#
# Analyze connections
#
$mask = POSIX::SigSet->new( SIGALRM );
$action = POSIX::SigAction->new(
sub {
die("Reverse lookup took too long");
},
$mask
);
$oldaction = POSIX::SigAction->new();
sigaction(14, $action, $oldaction);alarm(60); # Reverse DNS lookups must complete within this period
my %concount;
my %bots;for my $locip (keys(%$conns)){
#print("Loc IP: $locip\n");
my $cnt = 0;
for my $remip (keys(%{$conns->{$locip}})){
#print(" Rem IP: $remip\n");
$cnt++;
}
$concount{$locip} = $cnt;
}for my $locip (sort {$concount{$b} <=> $concount{$a}} keys(%concount)){
if($concount{$locip} > $norev_limit){
my $ip = inet_aton($locip);
my $name = gethostbyaddr($ip, AF_INET);
if($concount{$locip} > ($name ? $rev_limit : $norev_limit)){
#print("$locip: $concount{$locip} (" . ($name || '') . ")\n");
$bots{$locip} = $name;
}
}
}alarm(0);
#
# Update database
#$mask = POSIX::SigSet->new( SIGALRM );
$action = POSIX::SigAction->new(
sub {
die("Database timeout");
},
$mask
);
$oldaction = POSIX::SigAction->new();
sigaction(14, $action, $oldaction);alarm(15);
my $dbh = DBI->connect("dbi:Pg:host=db.host.tld;dbname=spambot",
"spambot", "passwd");
if(!$dbh) {
die("Cannot connect to DB");
}my $query;
my $sth;
my $rv;
for my $botip (keys(%bots)){
#print "$botip\n";
my $qname = $dbh->quote($bots{$botip});
$query = "
update spambot_hosts set
active_till = now() + '1 days',
hostname = $qname
where ip = '$botip'
";
$sth = $dbh->prepare($query);
$rv = $sth->execute();
if($rv != 1){
$query = "
insert into spambot_hosts
(ip, hostname, active_till)
values
('$botip', $qname, now() + '1 days')
";
$sth = $dbh->prepare($query);
$sth->execute();
}
}
$query = "
delete from spambot_hosts
where active_till < now()
";
$sth = $dbh->prepare($query);
$sth->execute();#
# Get full list of banned hosts from DB
#$query = "
select ip from spambot_hosts
";
$sth = $dbh->prepare($query);
$sth->execute();my %dlist;
while(my $row = $sth->fetchrow_hashref){
$dlist{$row->{ip}} = 1;
}undef $dbh;
alarm(0);#
# Read list of banned hosts from kernel (ipfw) table
#my %klist;
if(open(KTABLE, "/sbin/ipfw table $table_no list|")){
while(<KTABLE>){
chomp();
s/\/32//;
my ($prefix, $queue) = split();
$klist{$prefix} = 1;
}
close(KTABLE);
}#
# Update kernel table
#for my $host (keys(%dlist)){
unless($klist{$host}){
#print "Add $host\n";
system("/sbin/ipfw table $table_no add $host");
}
}for my $host (keys(%klist)){
unless($dlist{$host}){
#print "Remove $host\n";
system("/sbin/ipfw table $table_no delete $host");
}
}exit;
Из /etc/ipfw.rules:
#....
ipfw -q add deny tcp from "table(1)" to any 25
#....У нас скрипт работает на шейпере, обслуживающем только заграничный канал. Местный трафик им не анализируется
и не блокируется. При этом большая часть "хорошей" почты ходит именно по местным каналам, но спамеры
спамят в основном по заграничному каналу. Так что лимиты вам наверняка придется подстроить под себя.
Возможно, вам придется также реализовать "белый список", если у вас есть очень активные SMTP-сервера.На момент написания статьи в "расстрельном списке" несколько десятков хостов. Жалоб за истекшие сутки
не поступало, хотя раньше их было множество.URL:
Обсуждается: http://www.opennet.me/tips/info/2146.shtml
фильтр PF в FreeBSD\OpenBSD умеет самостоятельно вылавливать адреса, превышающие порог на частоту соединений и заносить их в свою внутреннюю таблицу. По этой таблице можно и блокировать трафик, и вылавливать нарушителей. Все гораздо проще и изящнее ...
>фильтр PF в FreeBSD\OpenBSD умеет самостоятельно вылавливать адреса, превышающие порог на частоту
>соединений и заносить их в свою внутреннюю таблицу. По этой таблице
>можно и блокировать трафик, и вылавливать нарушителей. Все гораздо проще и
>изящнее ...Не умеет.
Умеет, конечно.
>Умеет, конечно.Попробуйте и доложите.
Желательно с дешифрованными tpcdump'ом логами, иллюструющими ваш набор правил.
Где-то так:
table <smtp_bruteforce> persist
block drop in quick from <smtp_bruteforce>
pass in quick proto tcp from <local_net> to any port smtp flags S/S keep state \
(max-src-conn-rate 20/300, overload <smtp_bruteforce> flush global)Теперь в таблице <smtp_bruteforce> будет содержаться забаненые ip.
Очистку этой таблицы можно производить через запуск по крону программы, очищающая устаревшие записи на сутки:
/usr/local/sbin/expiretable -t 79200 smtp_bruteforce
А где ключевые слова "умеет самостоятельно"?
"Где-то" или "есть логи, иллюстрирующие гипотезу"?
Можно фрагмент лога?
У меня большой сети зомбируемых машин нет. Поэтому оставлю вам это на самопроработку.В этом коде, конечно не учтено:
>>Здесь дело не в количестве соединений, а в количестве IP-адресов, с которыми устанавливается соединение.но тем немение, работает с частотой создаваемых соединений.
ps: такая связка успешно функционирует у меня на блокирование попыток подключения по ssh.
>ps: такая связка успешно функционирует у меня на блокирование попыток подключения по ssh.Мой друг, это же классика :)
Про max-src-conn-rate(pf)/hashlimit(iptables) знает любой начинающий админ. Если же вас собеседник не знает таких очевидных вещей - бессмысленно пытаться ему что-либо объяснить.Кстати, в отношении таймаутов ipset немного удобнее таблиц pf - не нужно прикручивать всякие expiretable, механизм таймаутов реализован нативно:
ipset -N spambots iptree --timeout 79200
Так записи будут автоматически удаляться через 22 часа.
>Так записи будут автоматически удаляться через 22 часа.Причем таймаут хранится для каждой записи отдельно.
>Мой друг, это же классика :)Естественно. :)
И применимая в текущей задаче.>Кстати, в отношении таймаутов ipset немного удобнее таблиц pf - не нужно
>прикручивать всякие expiretable, механизм таймаутов реализован нативно:
>ipset -N spambots iptree --timeout 79200
>Так записи будут автоматически удаляться через 22 часа.
>Причем таймаут хранится для каждой записи отдельно.Так и в pf таймауты в таблице выставляются для каждой записи индивидуально. expiretable нужно вызывать только для очистки устаревших записей. Конечно, хотелось бы обходиться без него.
Скажим так expiretable - это расширение к pf, как ipset расширение к iptables. :)
>Скажим так expiretable - это расширение к pf, как ipset расширение к iptables. :)Если уж быть строгим и занудным до конца, то и iptables, и ipset - это всего лишь интерфейсы к netfilter, как pfctl - интерфейс к pf :)
Здесь дело не в количестве соединений, а в количестве IP-адресов, с которыми устанавливается соединение. К тому же, нужна гибкая политика, кого и как банить.
Это, конечно, от ситуации зависит, но проще просто ЗАКРЫТЬ весь исходящий траффик на 25й порт, и проковырять его только для реальных почтовых серваков.Логика очень простая: кому действительно надо иметь выход на 25й порт, осознанно попросят его открыть, но их будет очень мало. Всем остальным это не надо, и они будут закрыты => боты обломаются. При этом надо, разумеется, _документировать_ эту политику.
Да, так было бы лучше всего. Но пользователям этого не объяснить. Т.е. увеличится число тупых кейсов у саппорта. А так заодно выявляем сифозные хосты в сети.
>Да, так было бы лучше всего. Но пользователям этого не объяснить. Т.е.
>увеличится число тупых кейсов у саппорта. А так заодно выявляем сифозные
>хосты в сети.На трипаки в сетке похер, а закрытый на выход 25й порт - это айс. парсить maillog - изящнее и информативнее.
А есть чтото подобное для сервиса icq?
Это же надо так мучаться... В iptables это делается несколькими строчками:
# Создаем список спамеров
ipset -N spambots iphash
# Блокируем спамеров
iptables -I FORWARD -m set --set spambots src -j DROP
# Если кто-то превышает лимит подключений - заносим в "черный список"
iptables -A FORWARD -p tcp --dport 25 -m state --state NEW -m hashlimit --hashlimit-name spamcheck --hashlimit-mode srcip --hashlimit-above 20/min -j SET --add-set spambots src
# Еще можно добавить проверку на количество одновременных соединений с одного адреса
iptables -A FORWARD -p tcp --dport 25 -m state --state NEW -m connlimit --connlimit-above 10 -j SET --add-set spambots srcВуаля! Не более 20 новых соединений в минуту, не более 10 соединений одновременно.
Правда, hashlimit жестко задает контролируемые периоды времени (минута, час, сутки). Если хотите задавать их произвольно - см. recent.И не жалуйтесь, что "для этого надо патчить ядро и т.п.". Это всего лишь следствие неправильного выбора дистрибутива =) В debian stable (с iptables и xtables-addons из testing) все работает "искаропки".
Нда. Скрипт на двести строчек... и три-четыре строчки на iptables и pf.
Наглядно понимаешь разницу между сисадмином и велосипедистом.
>Нда. Скрипт на двести строчек... и три-четыре строчки на iptables и pf.
>
>Наглядно понимаешь разницу между сисадмином и велосипедистом.НЕ работают "три-четыре строчки на iptables и pf". НЕ работают.
Бабушку свою жить поучите.
А у меня - работают. Причем далеко не на одном сервере.
Вопрос: у кого из нас двоих руки из плеч, а у кого из ...?
>НЕ работают "три-четыре строчки на iptables и pf". НЕ работают.Полагаю, проблема не у "iptables и pf", а у вас. Где-то в области /dev/hands либо /dev/brain. Возможно, обусловлено ошибками при сборке ДНК :)
Самое простое
-A FORWARD -p tcp --dport 25 -j LOG --log-prefix smtpport:
включить logwatch и получать почту с логом ежедневно, не гнушаясь просматривать.
-A FORWARD -s 192.168.0.0/16 -p tcp -m tcp --dport 25 -m state --state NEW -m recent --set --name SMTP
-A FORWARD -s 192.168.0.0/16 -p tcp -m tcp --dport 25 -m state --state NEW -m recent --update --seconds 60 --hitcount 5 --name SMTP -j REJECT --reject-with icmp-port-unreachableАвтоматически блокирует рассылку спама от клиентов. Как только спам прекращается почта начинает работать :)
>-A FORWARD -s 192.168.0.0/16 -p tcp -m tcp --dport 25 -m state
>--state NEW -m recent --set --name SMTP
>-A FORWARD -s 192.168.0.0/16 -p tcp -m tcp --dport 25 -m state
>--state NEW -m recent --update --seconds 60 --hitcount 5 --name SMTP
>-j REJECT --reject-with icmp-port-unreachable
>
>Автоматически блокирует рассылку спама от клиентов. Как только спам прекращается почта начинает
>работать :)очень интересно, из-за кого-то одного будут страдать все - это во первых.
Во вторых, при /16 вполне возможно одновременное подключение более 5 клиентов в минуту, так что, думаю , не лучший вариант.
Страдать будет только тот кто спамит, проверено.
Префикс /16 указан для примера, роли это не влияет
>очень интересно, из-за кого-то одного будут страдать все - это во первых.
>Во вторых, при /16 вполне возможно одновременное подключение более 5 клиентов в минуту, так что, думаю , не лучший вариант.Мсье самый умный? Мсье никогда не читает документацию?
Шагом марш читать man iptables!
recent хранит информацию по каждому айпишнику отдельно. /16 задает только "область наблюдения".
>>очень интересно, из-за кого-то одного будут страдать все - это во первых.
>>Во вторых, при /16 вполне возможно одновременное подключение более 5 клиентов в минуту, так что, думаю , не лучший вариант.
>
>Мсье самый умный? Мсье никогда не читает документацию?
>Шагом марш читать man iptables!
>recent хранит информацию по каждому айпишнику отдельно. /16 задает только "область наблюдения".
>Мсье не страдает такой хренью, ибо.....
>Мсье не страдает такой хренью, ибо.....Ибо считает, что лучше написать двадцать тормозных велосипедов по сто килобайт каждый, чем один раз прочитать документацию по штатным возможностям своей системы.
Я правильно вас понял?
совершенно неправильно....
сплошной, блин, офф топ.... (лучше по русски - не по теме)
далек от таких ньюансов.... мелко.
ну судите строго.
не видел такого в 1.2
а дальше уже, как то, не очень вставляет - железо лучше, да и оно уже прошлое.
за прогу - зачёт. за ночь наловила 87 хостов. фикусы с iptables стоят на релее. но вот на исходящий поток от клиентов надо ставить чтото заносящее инфу в биллинг. чтоб тп по ночам спокойно могло удалить/приостановить бан клиенту, а не ждать админа чтобы он посмотрел в табличку.
>надо ставить чтото заносящее инфу в биллинг. чтоб тп по ночам спокойно могло >удалить/приостановить бан клиенту, а не ждать админа чтобы он посмотрел в табличку.Именно!!! Наконец кто-то понял мою задумку :)
Я бы предложил анализировать коннтраки с ASSURED на 25 порту.
>Я бы предложил анализировать коннтраки с ASSURED на 25 порту.Кстати вопрос: ctstatus анализирует все подключения или только related по conntrack?
многабукаф в перле. может и красиво, но оч тяжело.
у меня на шеле в 5 строк влезло.
>многабукаф в перле. может и красиво, но оч тяжело.
>у меня на шеле в 5 строк влезло.И не слабО их здесь привести?
Что-то мешает? Шарики?
>у меня на шеле в 5 строк влезло.У меня на iptables 3-4. У товарища выше на pf - 3. Что вы там так длинно пишете? ;)
Скрипт, думаю, будет полезен.
Он хорошо подходит, когда нужно оповещать пользователя о том, что с него сыплется спам.Могу посоветовать прикрутить к нему отправлялку писем пользователям.
Типа "Это автоматическое сообщение - у вас на компе вирус. Можете скачать антивирус с нашего фтп (ссылка), обновить его бесплатно с нашего фтп (ссылка)."
Ещё можно ссылку на пошаговое описание, как что делать.
А описание в картинках)
Думаю, это может ещё больше уменьшить звонки в суппорт.Ещё могу посоветовать анализировать dns запросы типа MX - они тоже обычно генерятся именно спамописателями.
Вылавливать такие запросы тоже можно с помощью tcpdump.Ещё в свое время заметил, что размер "окна" (параметр такой в ip или tcp заголовке) у спамописем равен 24000.
Помнится, даже после долгого наблюдения не заметил ничего другого, кроме спама, с таким окном.
Но это уже не так однозначно.А ещё могу посоветовать:
ipfw add 1 skipto 4 tcp from table(x) to not me 25
ipfw add 2 skipto 4 tcp from any to not me 25 limit src-addr 10
ipfw add 3 deny tcp from any to not me 25Просто не дает устанавливать больше 10 одновременных соединений)
Правило с limit - динамическое.
Параметры динамических правил можно настроить тут:
sysctl net.inet.ip.fwА в table(x) кидать "официальных спаммеров")
Т.е. тех, кого не надо блочить.Можно пойти более хитро)
Можно воспользоваться подсистемой netgraph - связать модуль для bpf и модуль для netflow.
И настроить так, чтобы получать статистику netflow о спаммерах.
А netflow уже хоть куда - хоть в БД, хоть в html)
То же самое, только чуть короче :)
http://sources.homelink.ru/spamblock/
#Время записи в журнал
client_connection_status_update_time = 5m
# Задаём промежуток времени, на который будем опираться, тут - 10 минут
anvil_rate_time_unit =600s
# Кол-во получателей для одного авторизованного пользователя в установленный промежуток времени
#(или сколько можно указать RCPT TO для одного авторизованного клиента вне зависимости от кол-ва соединений/сессий).
smtpd_client_recipient_rate_limit=200
# Кол-во соединений для одного ip адреса. При переборе соединений, пользователь получит отказ в соединении с
# ошибкой "Too many connections from ip". Параметр может иметь исключения, задаваемые параметром smtpd_client_event_limit_exceptions (по умолчанию равен $mynetworks)
smtpd_client_connection_rate_limit=200
# Кол-во отправленных писем авторизованным клиентом вне зависимости от кол-ва сессий (были ли отправки за одну сессию или за несколько).
#При переборе параметра, клиент получит ошибку "Too many messages".
smtpd_client_message_rate_limit=200
# Исключения
smtpd_client_event_limit_exceptions = $mynetworks
#Интервал времени для подсчета количества соединений
client_connection_rate_time_unit = 60s+ faill2ban
[Definition]
# Option: failregex
# Notes.: regex to match the password failures messages in the logfile. The
# host must be matched by a group named "host". The tag "<HOST>" can
# be used for standard IP/hostname matching and is only an alias for
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values: TEXT
#
failregex = reject: RCPT from (.*)\[<HOST>\]: 504 5.5.2
# reject: RCPT from (.*)\[<HOST>\]: 450 4.1.1
# reject: RCPT from (.*)\[<HOST>\]: 450 4.7.1
# reject: RCPT from (.*)\[<HOST>\]: 554 5.7.1
reject: RCPT from (.*)\[<HOST>\]: 553 5.7.1
Connection rate limit exceeded: (.*) from unknown\[<HOST>\] for service smtp
Message delivery request rate limit exceeded: (.*) from unknown\[<HOST>\] for service smtp
Recipient address rate limit exceeded: (.*) from unknown\[<HOST>\] for service smtp
too many errors after RCPT from (.*)\[<HOST>\]
cannot find your hostname\S \S<HOST>\S
# RCPT from .+\[<HOST>\]: .+ Recipient address rejected: User unknown
Client host \[<HOST>\] blocked using [\.\w]+;
RCPT from .+\[<HOST>\]: .+: Sender address rejected: Domain not found
RCPT from .+\[<HOST>\]: .+: Helo command rejected:
Illegal address syntax from unknown\[<HOST>\]
# warning: unknown\[<HOST>\]: SASL PLAIN authentication failed:
warning: .+\[<HOST>\]: SASL LOGIN authentication failed:
Жалоб не было