Botnet-Angriffe mit rsyslog und iptables recent module abwehren

fail2ban gilt gemeinhin als das Werkzeug der Wahl wenn Botnet-Drohnen davon abgehalten werden sollen, alle eingehenden SMTP-Verbindungen eines SMTP-Server (DoS-Attacke) zu sättigen. Das Tool verfolgt Fehlermeldungen im LOG des Mailservers und blockt ab einer bestimmten Anzahl von Fehlern die IP des Botnet-Clients.

Auf normal belasteten Servern funktioniert das gut, es scheitert aber gründlich wenn fail2ban mit grösseren Mengen von Botnet-Clients zu tun hat, denn dann ist das beliebte Programm schlichtweg zu langsam. Genau das ist mir auf einem Backup-MX passiert, das zusätzlich ein SMTP Boundary Filter für eine bei Bots sehr beliebte Domain darstellt.

Der Server ist nicht besonders groß dimensioniert, ein Quad Core Intel Xeon (2.66GHz 4 GB RAM), und die betroffene Domain hat nur 15 Empfänger - gerade passend. Als Betriebssystem kommt openSUSE 11.2 (i586) zum Einsatz. Ausser Postfix und Mailgraph betreibe ich dort keine weiteren nennenswerten Dienste.

Um die Botnet-Last vom Postfix SMTP-Server (smtpd) fernzuhalten hatte ich folgende Methoden bereits ausgeschöpft:

  • RBLs
  • Postscreen
  • Greylisting
  • Anvil Connection Limiting

Vorsicht!

Achtung, hier ging es nicht wirklich darum zu verhindern, dass Spam eingeliefert wird. Der Server funktionierte mit "normalen" Postfix Settings und ClamAV Milter sehr gut.

Aber die Anzahl der Botnet-Connects war so hoch, dass dies einem Angriff gleichkam. Logs waren - weil riesengroß - zur Analyse kaum mehr brauchbar und der DNS Traffic wegen ständiger RBL-Abfragen erheblich. Es kam sporadisch zur Ablehnung von SMTP-Verbindungen, weil alle SMTP-Server-Prozesse von Botnet-Clients in Anspruch genommen wurden. Ich musste die Anzahl verbundener Botnet-Clients reduzieren.

Das Ablehnen von Verbindungen, weil der Client auf einer RBL-Liste vermerkt ist, hatte ich bereits ausgeschöpft. Trotzdem hatte ich zu viele Verbindungen mit unerwünschten Clients. Mein Plan war diese per Firewall auszusperren. Dazu wollte ich sie anhand fehlerhaften Verhaltens im Log erkennen und anschliessend Verbindungen von deren IP blockieren lassen.

fail2ban

fail2ban war mein erster Ansatz. Genau dafür ist es bekannt. Es zeigte sich aber schnell, dass die Anzahl der Botnet-Verbindungsversuche fail2ban*s Fähigkeit überfordert, Logs zu parsen - *fail2ban kam den Ereignissen nicht hinterher. Dadurch war der Zeitversatz zur Erstellung einer DROP/REJECT-Regel in iptables zu groß und der Botnet-Client konnte bis zum DROP zu lange versuchen, Spam abzusetzen.

Um dieses Problem zu lösen, wollte ich fail2ban mehr Zeit zum Log parsen verschaffen. Dazu wollte ich die Anzahl von STMP-Verbindungen pro IP und Zeiteinheit limitieren.

iptables recent Modul

Um es kurz zu machen. Der Effekt reichte nicht aus, denn zu viele Bots bauten zu schnell Verbindungen auf.

Bemerkung

Für einen Moment erwog ich, ganze Länder anhand geographischer Zuordnung der IP-Adresse auszuperren, aber ich verwarf diese Idee schnell wieder. Sie hätte zu viele False-Positives durch unberechtigtes Blocking generiert.

Die nächste Idee, brachte mich auf den richtigen Weg: Ich wollte den Zeitraum zwischen Fehler und Beginn des Firewalling verkürzen, indem ich fail2ban gegen etwas anderes, schnelleres ersetze und mir die Trigger für den IP-Block direkt aus dem Mail-Log holen würde. Das Kommando für den Block hatte ich schon vor Augen:

% echo +ip.add.res.se > /proc/net/xt_recent/SMTP

Nur was sollte ich als Trigger zum blockieren verwenden ?

Postfix Postscreen

Postscreen ist ein Connection Filter Daemon des Postfix SMTP Servers. Ich nutze Postscreen mit zen.spamhaus.org und deshalb loggt Postfix brav welche IPs Postscreen abweist:

postscreen_dnsbl_sites = zen.spamhaus.org, list.dnswl.org*-5
postscreen_dnsbl_threshold = 1
postscreen_dnsbl_action = enforce
postscreen_access_list =
    permit_mynetworks,
    cidr:/etc/postfix/postscreen_access.cidr
postscreen_blacklist_action = drop
postscreen_greet_action = enforce
postscreen_hangup_action = drop
smtp_tls_block_early_mail_reply = yes
postscreen_bare_newline_action = drop
postscreen_bare_newline_enable = yes
postscreen_non_smtp_command_enable = yes
postscreen_pipelining_enable = yes
reject_rbl_client zen.spamhaus.org

Eine typische Postscreen log-Zeile sieht in etwa so aus:

Nov 16 16:43:31 mxback postfix/postscreen[30717]: \
      NOQUEUE: reject: RCPT from [x.x.x.x]:65156: 550 5.7.1 Service unavailable; \
      client [x.x.x.x] blocked using zen.spamhaus.org; from=<...>, to=<...>, \
      proto=ESMTP, helo=<x.lan>

Mein Plan war deshalb, die geblockten IP-Adressen über eine vorgefilterte Syslog-Pipe zu erfassen und diese anschliessend sofort mit iptables zu blocken. Der folgende Filter in /etc/rsyslog.conf schreibt die IP-Adressen nach /tmp/testpipe (Die Pipe muss evtl. mit z. B. mkfifo zunächst erzeugt werden):

mail.*        -/var/log/mail;RSYSLOG_TraditionalFileFormat
mail.info     -/var/log/mail.info;RSYSLOG_TraditionalFileFormat
    $template MyTemplate,"%msg:R,ERE,1,BLANK:.*\[([0-9.]+)].*--end%\n"
    if $msg contains 'blocked using zen.spamhaus.org' then | /tmp/testpipe;MyTemplate
mail.warning  -/var/log/mail.warn;RSYSLOG_TraditionalFileFormat
mail.err      /var/log/mail.err;RSYSLOG_TraditionalFileFormat

Mit dem nachfolgenden bash-Skript (zen-spam-blocker.sh) konnte ich dann die IP-Adressen aus der Syslog-Pipe in die recent-Datei schreiben:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/bin/bash

chmod 600 /sys/module/xt_recent/parameters/ip_list_tot
echo 10000000 > /sys/module/xt_recent/parameters/ip_list_tot
chmod 400 /sys/module/xt_recent/parameters/ip_list_tot

#RESET Firewall Suse special
SuSEfirewall2 stop
SuSEfirewall2 start

#WAIT  POSTFIX
/etc/init.d/postfix stop
sleep 10
/etc/init.d/postfix start

#create rule block for 24 hours

NAME="SPAM"

iptables -I INPUT -p tcp --dport 25 -m state --state NEW \
    -m recent --name $NAME --rcheck --seconds 86400 -j DROP

while true
do
if read line </tmp/testpipe; then
        echo +$line > /proc/net/xt_recent/$NAME
fi
done

Das Ergebnis überzeugt! Hier die Anzahl der Rejects pro Tag vor Einführung:

% grep reject mail-20110911 | wc -l
1016323

Das entspricht ca. 4 Rejects pro Sekunde.

Heute sieht das schon viel entspannter aus:

% zgrep reject mail-20121116.bz2 | wc -l
8977

Das wären dann nur noch 0,1 Rejects pro Sekunde.

Das Skript läuft seit einem Jahr mit gelegentlichen Restarts um die Tabelle zu leeren und es gab bisher keine False-Positives.

Vorsicht!

Die beschriebene Lösung ist sozusagen massgeschneidert auf diesen Anwendungsfall, sie sollte nicht einfach auf andere Systeme uebertragen werden. Die Lösung soll nur als Ideengeber dienen für Administratoren mit ähnlichen Problemen.


Kommentare

  1. Florian Schaal

    Florian Schaal (1 Jahr, 9 Monate) # Reply

    Hallo Robert,

    ein toller und detaillierte Beitrag. Ich mache im Prinzip das gleiche, verwende aber syslog-ng: http://blog.schaal-24.de/?p=1626

    Gruß
    Florian

  2. Robert Schetterer

    Robert Schetterer (1 Jahr, 9 Monate) # Reply

    Ein Kollege hat mir folgende Loesung zukommen lassen, ich poste diese mal zusaetzlich ungetestet

    /etc/rsyslog.d/87-rns-block-postscreen-attackers.conf_DISABLED:

    ----------------------------------------------------------------------
    $Template tpl,"+%msg:R,ERE,1,BLANK:.* \[([[:alnum:].:]+)\]:.*--end%\n"

    if \
    $syslogfacility-text == 'mail' and \
    $syslogtag contains 'postscreen' and \
    $msg contains 'PREGREET' \
    then /proc/net/xt_recent/postscreen-PREGREET;tpl

    if \
    $syslogfacility-text == 'mail' and \
    $syslogtag contains 'postscreen' and \
    $msg contains 'BLACKLISTED' \
    then /proc/net/xt_recent/postscreen-BLACKLISTED;tpl

    if \
    $syslogfacility-text == 'mail' and \
    $syslogtag contains 'postscreen' and \
    $msg contains 'DNSBL' \
    then /proc/net/xt_recent/postscreen-DNSBL;tpl
    ----------------------------------------------------------------------

    iptables ist wie folgt gepimmt:

    ----------------------------------------------------------------------
    iptables -N postscreen
    iptables -I INPUT -j postscreen -m tcp -p tcp --dport 25

    iptables -A postscreen -m tcp -p tcp -m recent --name postscreen-PREGREET --update --seconds 86400 -j DROP
    iptables -A postscreen -m tcp -p tcp -m recent --name postscreen-PREGREET --remove

    iptables -A postscreen -m tcp -p tcp -m recent --name postscreen-BLACKLISTED --update --seconds 86400 -j DROP
    iptables -A postscreen -m tcp -p tcp -m recent --name postscreen-BLACKLISTED --remove

    iptables -A postscreen -m tcp -p tcp -m recent --name postscreen-DNSBL --update --seconds 86400 -j DROP
    iptables -A postscreen -m tcp -p tcp -m recent --name postscreen-DNSBL --remove
    ----------------------------------------------------------------------

    Vorteil dieser Lösung: Es kann auch IPv6

  3. Marcus

    Marcus (8 Monate, 2 Wochen) # Reply

    Hallo,

    sehr interessant zur Bot-Abwehr. Kämpfe auch gerade damit.

    Ich bin gerade beim Optimieren von policyd und Anpassen von SASL sowie der smtpd_client_connection_rate_limit und anvil_rate_time_unit. Das blockt schon recht gut - vorerst.

  4. Robert Schetterer

    Robert Schetterer (8 Monate, 2 Wochen) # Reply

    Mittlerweile bin ich schon etwas schlauer, es ist durchaus moeglich direkt aus rsyslog heraus beliebige script aktionen anzusteuern, das duerfte der Koenigsweg sein, allerdings ist die Syntax dazu eher schwierig , damit waeren sehr viele "Rueckkopplungen" zu allen moeglichen log Eintraegen mit minimaler Zeitverzoegerung denkbar. Ausserdem kann man schon diverse Filter vorschalten und bei Eintraegen auch "mitzaehlen" usw

Leave a comment.
Your Comment

×