Як захистити себе від спамерів що атакують поштовий сервер Postfix.
Визначаємо активних користувачів що відсилають пошту понад дозволенного ліміту за певний час, блокуємо доступ з IP адреси до сервера на певний час. Також блокуємо користувачів що ввели забагато неправильних паролів: authentication failed та супер активних користувачів що хочуть надіслати дуже швидко і багато : Connection rate limit exceeded.Компоненти:
Policyd2
/usr/ports/mail/policyd2policyd2-2.0.12 - Policyd v2 is a multi-platform policy server for popular MTA ...
/usr/local/etc/cluebringer.conf:
[server]
# Protocols to load
protocols=<<EOT
Postfix
#Bizanga
EOT
# Modules to load
modules=<<EOT
Core
AccessControl
#CheckHelo
#CheckSPF
#Greylisting
Quotas
EOT
[database]
DSN=DBI:Pg:database=policyd;host=localhost
Username=somedbuser
Password=somedbuserpwd
# Access Control module
[AccessControl]
enable=1
# Greylisting module
[Greylisting]
enable=0
# CheckHelo module
[CheckHelo]
enable=0
# CheckSPF module
[CheckSPF]
enable=0
# Quotas module
[Quotas]
enable=1
Postfix & Policyd2
postfix/main.cf:
smtpd_recipient_restrictions =
check_policy_service inet:127.0.0.1:10031,
permit_mynetworks,
reject_non_fqdn_sender,
reject_sender_login_mismatch,
permit_sasl_authenticated,
reject_unauth_destination
smtpd_end_of_data_restrictions = check_policy_service inet:127.0.0.1:10031
anvil_rate_time_unit = 240s
smtpd_client_connection_rate_limit = 10
smtpd_client_event_limit_exceptions = $mynetworks
далі налаштування policyd2 тільки через Web interface.
cluebringer-2.0.12/INSTALL:
5. Install the webui/* into your apache directory, check out includes/config.php and adjust the MySQL server details.
Таким чином усі користувачі що знаходяться у таблиці Member "newuser" попадають під правила Policy List: "newusers to not internal"
А модуль QUOTAS та POLICY: "newusers to not internal" підраховує скільки відіслано повідомлень за кількістю (MessageCount) , або скільки повідомлень за розміром (MessageCumulativeSize). Якщо ліміт перевищено, то виконується дія за POLICY: "newusers to not internal" - REJECT "LIMIT AT QUOTAUSE" - відкинути, і це буде записано до postfix лог файлу.
Fail2ban
/usr/ports/security/py-fail2banpy27-fail2ban-0.8.6 - Scans log files and bans IP that makes too many password ...
/usr/local/etc/fail2ban/filter.d/postfix-sasl.conf:
failregex = (?i): warning: [-._\w]+\[<HOST>\]: SASL (?:LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed(: [ A-Za-z0-9+/]*={0,2})?\s*$
/usr/local/etc/fail2ban/filter.d/postfix-conlim.conf:
failregex = Connection rate limit exceeded: .* from (.*)\[<HOST>\] for service smtp
/usr/local/etc/fail2ban/filter.d/postfix-overlim.conf:
failregex = reject: RCPT from (.*)\[<HOST>\]: .*: LIMIT AT QUOTAUSE;
/usr/local/etc/fail2ban/jail.conf:
[postfix-sasl]
enabled = true
filter = postfix-sasl
action = pf
logpath = /var/log/maillog
bantime = 360000
maxretry = 4
[postfix-conlim]
enabled = true
filter = postfix-conlim
action = pf
logpath = /var/log/maillog
bantime = 360000
maxretry = 4
[postfix-overlim]
enabled = true
filter = postfix-overlim
action = pf
logpath = /var/log/maillog
bantime = 360000
maxretry = 4
Описую як треба передати інформацію до таблиці файровола pf, також завершення усіх поточних з'єднань за визначеною адресою та портами SMTP, SMTPS сервера.
Додатково для спрощення аналізу - оновлю файл з переліком блокованих адрес - fail2ban.txt.
/usr/local/etc/fail2ban/action.d/pf.conf:
actionban = /sbin/pfctl -t fail2ban -T add <ip>/32
/usr/sbin/tcpdrop -la | /usr/bin/egrep ' (25|465|110|995) <ip>' |/bin/sh
/sbin/pfctl -t fail2ban -T show -q > /var/db/filter/fail2ban.txt
actionunban = /sbin/pfctl -t fail2ban -T delete <ip>/32
pf.conf:
# block banned SMTP's IP
table <fail2ban> persist file "/var/db/filter/fail2ban.txt"
block in on $ext_if proto tcp from <fail2ban> to any port {25, 465, 110, 995}
Надалі до таблиці policyd2 - Member "newuser", додаємо користувачів за котрими треба "наглядати" у ручному або автоматичному режимі.
Автоматичний режим додавання користувачів до "нагляду"
Базується на аналізі лог файлу на предмет підрахувань спроб авторизації користувачів з різних країн та різних інтернет провайдерів під одним обліковим записом.У мене в наявності є рішення з описаним класом на PHP, а також спрощену версію з використанням шел скрипту.
Ось скрипт що додає до бази даних спроби авторизації користувачів та їх адреси та код інтернет провайдеру що видав цю адресу.
store-postfix-sasl.sh:
#!/bin/sh
echo "DELETE FROM xm_sasl_user_activity;"| psql policyd2 somedbuser
( zcat /var/log/maillog.0.bz2 ; cat /var/log/maillog ) | grep "sasl_username=" | cut -d " " -f 1,3,4,10,8 | \
while read str; do \
#echo $str;
date=`echo $str | sed "s/^\(.*\) client.*/\1/g"`
ip=`echo $str | sed "s/.*\[\(.*\)\].*/\1/g"`
user=`echo $str | sed "s/.*\=\(.*\)/\1/g"`
#sed "s/.*\[\(.*\)\].*/\1/g"
if [ -n "$ip" ]; then
isp=`whois $ip | grep netname |head -n 1| /usr/bin/awk '{ FS = ":"; gsub(/^ */,"", $2);print $2;}'`
if [ -z $isp ]; then
isp=`whois $ip | grep '\-num:' |head -n 1| /usr/bin/awk '{ FS = ":"; gsub(/^ */,"", $2);print $2;}'`
fi
if [ -z $isp ]; then
isp=`whois $ip | grep -i 'netname:' |head -n 1| /usr/bin/awk '{ FS = ":"; gsub(/^ */,"", $2);print $2;}'`
fi
if [ "$isp" == "N/A" ]; then
isp=`whois $ip | grep -i 'ownerid:' |head -n 1| /usr/bin/awk '{ FS = ":"; gsub(/^ */,"", $2);print $2;}'`
fi
year=`date "+%Y"`
echo $year $date
# echo $user
# echo $ip
# echo $isp
echo "INSERT INTO sasl_user_activity (sasl_username,ip,netname,tstamp) VALUES ('${user}','${ip}','${isp}',to_timestamp('${year} ${date
psql -q imp nobody
fi
done
Databse policyd2 table sasl_user_activity:
>psql policyd2 somedbuser
policyd2=> \d sasl_user_activity Table "public.sasl_user_activity"
Column | Type | Modifiers
---------------+-----------------------------+-----------------------------
user_id | integer | not null default nextval('sasl_user_activity_user_id_seq'::regclass)
sasl_username | character varying(30) | not null
ip | inet | not null
netname | character varying(70) |
tstamp | timestamp without time zone | not null default now()
Indexes:
"sasl_user_activity_pkey" PRIMARY KEY, btree (user_id)
Class PHP SASL_Activity.php:
<?php
require_once 'Net/Whois.php';
class SASL_Activity {
protected $db;
public function __construct($db,$debug) {
$this->db = $db;
$this->whois = new Net_Whois;
$this->pattern1 = '/origin: *(.*)/i';
$this->pattern3 = '/netname: *(.*)/';
$this->pattern2 = '/ownerid: *(.*)/i';
$this->pattern4 = '/netname: *(.*)/i';
$this->pattern5 = '/(.*) \(NET-.*\)/i';
$this->debug = $debug;
}
public function clear() {
$res=$this->db->query("DELETE FROM sasl_user_activity");
$result=$res->fetch();
return $result[0];
}
public function check($user,$ip) {
$res = $this->db->query("SELECT count(*) FROM sasl_user_activity WHERE sasl_username=? AND ip=?", array($user,$ip));
return ('0' == $res->fetchColumn(0));
}
public function get_isp($ip) {
$res = $this->db->query("SELECT netname FROM sasl_user_activity WHERE ip=? LIMIT 1", array($ip));
$isp = $res->fetchColumn(0);
if ( $isp == '') {
$data1 = $this->whois->query($ip);
if (preg_match($this->pattern1,$data1,$matches1)) {
$isp=trim($matches1[1]);
}else if (preg_match($this->pattern2,$data1,$matches1)) {
$isp=trim($matches1[1]);
}else if (preg_match($this->pattern3,$data1,$matches1)) {
$isp=trim($matches1[1]);
}else if (preg_match($this->pattern4,$data1,$matches1)) {
$isp=trim($matches1[1]);
}else if (preg_match($this->pattern5,$data1,$matches1)) {
$isp=trim($matches1[1]);
}else{
$isp="***UNDEFINED";
}
}else{
if ($this->debug) echo "ISP detected early: $isp\t";
}
return $isp;
public function store ($date,$user,$ip) {
if ($this->check($user,$ip)) {
$ispa=$this->get_isp($ip);
if ($this->debug) echo "ISP: $ispa";
if ($this->db->query("INSERT INTO sasl_user_activity (sasl_username,ip,netname,tstamp) VALUES (?,?,?,?)",array(${user},${ip},${ispa},${date}))) {
if ($this->debug) echo "\tInsterted OK\n";
}
}else{
if ($this->db->query("UPDATE sasl_user_activity SET (tstamp) = (?) WHERE sasl_username=? AND ip=?",array(${date},${user},${ip}))) {
if ($this->debug) echo "\tUpdated OK\n";
}
}
}
public function get_activity(){
$res = $this->db->query("SELECT foo.sasl_username, COUNT(foo.sasl_username) AS differ_isp FROM (SELECT Distinct sasl_username, netname FROM xm_sasl_user_activity GROUP BY netname, sasl_username) AS foo GROUP BY sasl_username ORDER by differ_isp DESC");
return $res;
}
public function get_hyper_activity($limit=0){
$res = $this->db->query("SELECT * FROM (SELECT foo.sasl_username, COUNT(foo.sasl_username) AS differ_isp FROM (SELECT Distinct sasl_username, netname FROM xm_sasl_user_activity GROUP BY netname, sasl_username) AS foo GROUP BY sasl_username) AS activity
return $res->fetchAll(Zend_Db::FETCH_ASSOC);
}
private function __clone()
{}
}
sasl-act.php:
date_default_timezone_set('UTC');
set_time_limit(60 * 60 * 24);
error_reporting( E_ALL ^ E_NOTICE);
require_once 'SASL_Activity.php';
$debug=0;
$dirMailLog = '/var/log/';
$fileMailLog = 'maillog.0.bz2';
$log_file=$dirMailLog . $fileMailLog;
$useractivity = new SASL_Activity($db,$debug);
//Oct 3 02:10:14 mail postfix/smtpd[49400]: 1B33DB44909: client=unknown[137.243.223.131], sasl_method=LOGIN, sasl_username=noman
//for parse log of postfix
$pattern = '/(\w{3}[^a-zA-Z]+)+ mail postfix.* client=.*?\[([0-9.]+)+\],.*sasl_username=(.*)/';
$fh = bzopen($log_file,'r') or die($php_errormsg);
$i = 1;
$file_prev_day=strtotime('-1 day', filemtime($log_file));
$file_year=date ("Y", $file_prev_day);
if ($debug) echo "$log_file year:$file_year from:". date('Y-m-d',$file_prev_day). PHP_EOL;
//echo "Purge table:".$useractivity->clear()."\n";
while (!feof($fh)) {
// read each line and trim off leading/trailing whitespace
if ($s = bzread($fh,16384)) { //fgets
// match the line to the pattern
if (preg_match($pattern,$s,$matches)) {
list($whole_match,$date,$remote_host,$user) = $matches;
if ($debug) echo "\n##:".$i." $file_year $date $remote_host $user";
if ($debug) echo "\nUser: $user\tIP: $remote_host";
$date="$file_year ".$date;
if ($debug) echo "\nStore: ";
$useractivity->store($date,$user,$remote_host);
} else {
// complain if the line didn't match the pattern
error_log("Can't parse line $i: $s");
}
}
$i++;
}
bzclose($fh) or die($php_errormsg);
add_newusers_activity($members,$useractivity);
//add hyper acivity users to tables newusers poilicy
$hyperact=$useractivity->get_hyper_activity(10);
foreach($hyperact as &$val) {
if ($debug) echo $val[sasl_username].PHP_EOL;
$user=$val[sasl_username];
// here some methods for addMember($user.') to policyd2 tables with members of newusers poilicy
}
1 коментар:
Набагато простіше та ефективніше буде встановити ASSP (http://www.howtoforge.com/installing-assp-anti-spam-smtp-proxy-on-ubuntu-server-10.04-debian-5.0) на тому ж сервері ;)
Дописати коментар