Сразу оговорюсь, что для моего случая задача облегчена, т.к. PHP подключен как FastCGI и работает от имени конкретного пользователя, что очень сильно упрощает задачу борьбы со СПАМ.
Итак, рассмотрим основные пути произведения спам рассылок - это sendmail, локальное не авторизированное соединение с localhost на 25й порт (при этом логин/пароль не требуются) и удаленное/локальное авторизированное соединение (т.е. клиент со своей машины подключается и начинает сваливать нам тонны СПАМА). Также есть очень экзотический вариант, когда спам-скрипт сам соединяется с удаленными серверами, выполняя роль MTA и рассылает СПАМ самостоятельно, но этот вариант уж очень сомнителен. Давайте рассмотрим все подробно и по пунктам.
Поставим необходимый диагностический софт:
apt-get install -y --force-yes strace
Создадим необходимых юзеров
useradd spammer
Входим под предполагаемым спамером:
su spammer
Sendmail
Через /usr/sbin/sendmail почту отправляют такие популярные программы как PHP и /usr/bin/mail.
echo "some spam" | mail -s testspam testspammail@domain.ru
В итоге выливается в:
[pid 17520] execve("/usr/sbin/sendmail", ["send-mail", "-i", "--", "testspammail@domain.ru"], [/* 16 vars */]) = 0
И при получении в строке хидеров будет следующая строчка однозначно идентифицирующая спамера:
Received: by domain.ru (Postfix, from userid 1001)
Теперь попробуем послать спам от PHP.
php -r "mail('testspammail@domain.ru', 'spamsubj','spambody');"
И получаем аналогичную картину:
[pid 17553] execve("/bin/sh", ["sh", "-c", "/usr/sbin/sendmail -t -i"], [/* 16 vars */]
Это происходит по той причине, что Linux/UNIX версии PHP отправляют почту только через sendmail, windows же версия использует SMTP на локал хост. Следовательно, письмо также будет промаркировано UID пользователя, что позволит его найти и наказать :)
Кстати, обе выше указанные записи в логе /var/log/maillog выльются в следующее (так что можно легко посредством парсинга логов вычислять нарушителей лимитов):
Jan 6 14:12:08 vrt1 postfix/qmgr[13940]: 7B112D52001: removed
Jan 6 14:14:49 vrt1 postfix/pickup[17379]: 54CF3D52001: uid=1001 from=
Jan 6 14:14:49 vrt1 postfix/cleanup[17546]: 54CF3D52001: message-id=<20100106131449.54CF3D52001@vrt1.domain.ru>
Локальное не авторизированное соединение с localhost на 25й порт
Теперь давайте с самого сервера попробуем выполнить следующие команды:
telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 vrt1.domain.ru ESMTP Postfix (Debian/GNU)
HELO 11
250 vrt1.domain.ru
MAIL FROM:spammer@spammer.ru
250 2.1.0 Ok
RCPT TO:testspammer@domain.ru
250 2.1.5 Ok
DATA
354 End data with.
spam
.
250 2.0.0 Ok: queued as 009C768180
В итоге такое письмо приедет получателю только с хидером, который никаким образом не позволяет вычислить источник:
Received: from 11 (localhost [127.0.0.1])
Логи также молчат:
Jan 6 14:32:26 vrt1 postfix/smtpd[17703]: connect from localhost[127.0.0.1]
Jan 6 14:32:52 vrt1 postfix/smtpd[17703]: 009C768180: client=localhost[127.0.0.1]
Jan 6 14:33:02 vrt1 postfix/cleanup[17705]: 009C768180: message-id=<20100106133252.009C768180@vrt1.domain.ru>
Jan 6 14:33:02 vrt1 postfix/qmgr[13940]: 009C768180: from=, size=333, nrcpt=1 (queue
В итоге необходимо запретить неавторизованный прием почты с локалхоста. Есть у Постфикса такой параметр mynetworks, который в документации описывается так:
By default, Postfix will relay mail for clients in authorized networks.
Authorized client networks are defined by the mynetworks parameter. The default is to authorize all clients in the IP subnetworks that the local machine is attached to.
Так что открываем:
vi /etc/postfix/main.cf
Видим там на строке mynetworks следующее:
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
Заменяем на:
mynetworks =
Перезапускаем постфикс:
/etc/init.d/postfix restart
Ну и все, теперь при попытке послать спам без авторизации будет следующее:
telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 vrt1.domain.ru ESMTP Postfix (Debian/GNU)
HELO 1
250 vrt1.domain.ru
MAIL FROM:spammer@domain.ru
250 2.1.0 Ok
RCPT TO:odintsov@domain.ru
554 5.7.1: Relay access denied
Теперь попробуем авторизироваться и послать почту.
Для начала нам понадобятся преобразованные в base64 логин и пароль:
# echo "test@suxx.us" | base64
dGVzdEBzdXh4LnVzCg==
# echo "qwerty" | base64
cXdlcnR5Cg==
Теперь отсекаем "Cg==" у закодированных значений и используем.
telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 vrt1.domain.ru ESMTP Postfix (Debian/GNU)
HELO 1
250 vrt1.domain.ru
AUTH LOGIN
334 VXNlcm5hbWU6
dGVzdEBzdXh4LnVz
334 UGFzc3dvcmQ6
cXdlcnR5
235 2.7.0 Authentication successful
MAIL FROM:test@test.ru
250 2.1.0 Ok
RCPT TO:test@domain.ru
250 2.1.5 Ok
DATA
354 End data with.
spam
.
250 2.0.0 Ok: queued as 737C6680C6
А в логах в это время:
Jan 6 15:32:31 vrt1 postfix/smtpd[18540]: connect from localhost[127.0.0.1]
Jan 6 15:33:08 vrt1 postfix/smtpd[18540]: 737C6680C6: client=localhost[127.0.0.1], sasl_method=LOGIN, sasl_username=test@suxx.us
Jan 6 15:33:13 vrt1 postfix/cleanup[18544]: 737C6680C6: message-id=<20100106143308.737C6680C6@vrt1.domain.ru>
Jan 6 15:33:13 vrt1 postfix/qmgr[18428]: 737C6680C6: from=, size=326, nrcpt=1 (queue active)
Jan 6 15:33:13 vrt1 postfix/smtp[18545]: 737C6680C6: to=, relay=ASPMX.L.GOOGLE.COM[2
Удаленное/локальное авторизированное соединение
А тут нам помогут логи из предыдущего примера :) Но в письме опять же отправитель явно никак не идентифицирован. И фишка тут в том, что поле FROM никак не контролируется.
Спам скрипт-MTA
Тут нам поможет только правило фаерволла, запрещающее соединение до 25го порта удаленных серверов всем, кроме нашего родного MTA Postfix.
Команда в тему. Просмотр хит-парада пяти самых активных пользователя за эту неделю:
ReplyDeletegrep 'uid=' /var/log/mail.log | awk -F 'uid=' {'print$2'} | awk {'print $1'} | sed 's/,//g' | sort | uniq -c | sort -n | tail -5 | awk {'print "UID:",$2,"MESSAGES COUNT:",$1'}
Чтобы за предыдущую, то другой файл: /var/log/mail.log.1
Ага, полезно!
ReplyDeleteалсо с именами юзверей сразу:
ReplyDeletegrep 'uid=' /var/log/maillog | getent passwd $(awk -F 'uid=' {'print$2'}) | awk -F: '{print $1}' | awk {'print $1'} | sed 's/,//g' | sort | uniq -c | sort -n | tail -5 | awk {'print $2," ",$1'}