angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

IP addresses mit ipset und Python sammeln und sperren

Und sobald wir eine Liste von IP addresses haben, können wir sie mit IP2Location und whois analysieren

21 Mai 2023
post main image
https://unsplash.com/@enginakyurt

Wenn Sie einen Server haben, der mit dem Internet verbunden ist, haben Sie dies wahrscheinlich in Ihren Protokolldateien gesehen: viele illegale externe Anfragen, die versuchen, auf Ihre Dienste zuzugreifen.

Ich betreibe einen Debian -Server und verwende Fail2Ban zur Intrusion Prevention. Standardverfahren, installieren, konfigurieren und vergessen. Da der Server zu bestimmten Zeiten heruntergefahren wurde, beschloss ich, mir das genauer anzusehen.

Ich beschäftige mich hauptsächlich mit der Programmierung, und dies ist eher eine Aufgabe für Systemadministratoren. Ja, sie sind die Spezialisten, ich bin nur ein Laie. Aber es ist immer schön zu lernen. In diesem Beitrag werde ich mich auf Portscanner beschränken, die den Port SMTP angreifen. Über (D)DOS-Angriffe werde ich ein anderes Mal schreiben. Mein Debian -Server ist nur über ipv4-Adressen mit dem Internet verbunden (ich zögere noch, ipv6 hinzuzufügen). Wenn Sie dies selbst ausprobieren:

WARNUNG: VERWENDUNG AUF EIGENE GEFAHR / SPERREN SIE SICH NICHT OUT

Verwendung von ipset und Python

Ich nehme an, dass einige der IP addresses, die am Scannen des SMTP -Ports beteiligt sind, auch für andere Angriffe verwendet werden können, aber ist das wirklich wahr? Was ich erreichen wollte, war, so viele IP addresses wie möglich zu sammeln, die den Port SMTP scannen. Dazu muss ich diese IP addresses dauerhaft blockieren und dann davon ausgehen (hoffen), dass der Scanner einen anderen IP address verwendet, um den Angriff fortzusetzen, usw.

Mit Fail2Ban werden IP addresses nicht dauerhaft gebannt. Außerdem blockiert Fail2Ban standardmäßig nicht alle Ports, sondern nur einen einzigen.

Da ich das aktuelle Fail2Ban-Setup nicht anfassen wollte, musste ich iptables verwenden. Aber ich wollte auch keine langen Listen von IP addresses zu iptables hinzufügen und sie später wieder entfernen. ipset ist die Rettung. ipset ist eine Erweiterung von iptables, die es uns ermöglicht, firewall -Regeln zu erstellen, die mit Gruppen von IP addresses (oder IP-Netzwerken) übereinstimmen. ipset-Gruppen werden in indizierten Datenstrukturen gespeichert, was die Suche sehr effizient macht.
Sobald wir die IP addresses in einem Set haben, zum Beispiel ein Set namens blocklist_postfix_sasl, können wir sie blockieren, indem wir
eine Regel zu iptables hinzufügen:

iptables -I INPUT -m set --match-set blocklist_network src -j DROP

Hier blockiere ich alle Ports, aber wenn Sie wollen, können Sie die Regel so ändern, dass nur der Port SMTP blockiert wird.

Ich verwende Python anstelle eines Bash-Skripts aus Gründen, die in früheren Beiträgen erläutert wurden. Bash ist sehr nützlich für kleine Skripte, aber sobald Sie etwas mehr Kontrolle und/oder Verarbeitung benötigen, sind Sie mit Python besser dran. Mit Python können wir auch Linux Kommandozeilenprogramme mit subprocess ausführen.

iptables-Regeln und ipset persistent machen

Als erstes müssen Sie Ihre iptables-Regeln persistent machen, d.h. sie bleiben auch nach einem Neustart erhalten. Sie müssen auch Ihre ipset-Sets beständig machen. Hier gibt es einen Haken, denn ipset-Sets müssen geladen werden, bevor iptables geladen wird. Sie können im Internet Informationen darüber finden, wie man das macht, ich habe unten zwei Links hinzugefügt.

Sammeln von IP addresses

Ich habe ein Python -Skript erstellt, siehe unten, das Folgendes tut:

  • Durchsucht die Fail2Ban-Logdateien alle paar Stunden nach Banned postfix-sasl IP addresses.
  • Erstellen Sie ein ipset set mit dem Namen blocklist_postfix_sasl, falls noch nicht erstellt.
  • Fügen Sie diese IP addresses zu diesem Set und zu einer Datei hinzu.
  • Speichern Sie das Set, damit die Daten bei einem Neustart nicht verloren gehen.

Wenn eine IP address zu einem Set hinzugefügt wird, ist sie sofort wirksam (wenn das Set zu iptables hinzugefügt wurde). Mit Cron führe ich dieses Skript Python alle drei Stunden aus.

Die Datei mit dem verbotenen IP addresses hat Zeilen wie:

2023-05-20T07:00:02.785796 add blocklist_postfix_sasl 123.xxx.xxx.xxx
2023-05-20T07:00:02.801597 add blocklist_postfix_sasl 456.xxx.xxx.xxx

Post-processing

In einer Woche sammelte ich einige tausend einzigartige IP addresses, die von Fail2Ban gesperrt wurden. Dann kopierte ich diese Liste auf meinen Computer, um sie zu analysieren. In einem ersten Schritt wollte ich eine CSV-Datei mit Zeilen von IP addresses und so vielen Informationen wie möglich erstellen.

Sie können einen Online-Dienst wie Abuseipdb.com verwenden, um IP addresses manuell zu überprüfen, oder ein Skript schreiben und deren API verwenden. Deren kostenloser Dienst hat jedoch ein Limit von 1000 Abfragen pro Tag.

Dann habe ich das Python -Paket IP2Location mit der kostenlosen Datenbank gefunden:

IP2LOCATION-LITE-DB11.BIN

Damit können wir die Geolocation, wie Land und Stadtinformationen, erhalten. Das Schöne daran ist, dass man diese Datenbank auf dem eigenen Rechner hat und nicht aus der Ferne nachschauen muss.

Von den 3900 eindeutigen IP addresses konnte ich nun sehen, dass 3750 aus China stammten, das sind 96%, WTF?

Als nächstes installierte ich 'whois' auf meinem Rechner:

apt install whois

und benutzte dies mit einem Skript, um weitere Informationen zu erhalten. Um die Anzahl der Remote-Whois-Abfragen zu reduzieren, habe ich die IP addresses aus China vorerst ausgeschlossen. Nach einiger Codierung konnte ich die für diese IP addresses verantwortlichen Organisationen und mehr extrahieren.

Ergebnisse

Wie bereits oben erwähnt, stammen fast alle IP addresses aus China. Warum in aller Welt sollte China die Ports auf meinem Server scannen wollen? Ein Grund könnte sein, dass das Scannen von Ports für China zur Tagesordnung gehört. Bereiten sie sich auf den Dritten Weltkrieg vor? Oder hoffen sie vielleicht, dass ich diese IP addresses dauerhaft blockiere, damit die Leute aus China nicht auf Websites zugreifen können, die auf meinem Server gehostet werden?

Kann jemand gefälschte IP addresses verwenden, um meinen Server anzugreifen, und Port-Scanning durch China vorschlagen? Aber dann müssten sie die Router meines Hosting-Anbieters hacken? Alles ist möglich, ich weiß es nicht.

Ich kann die Ergebnisse der anderen IP addresses in detail nicht nennen. Aber ich muss erwähnen, dass eine bestimmte Hosting-Organisation aus den Niederlanden ein Anbieter von vielen Hackern / Port-Scannern zu sein scheint. Es gibt schockierende reviews über sie im Internet. Ahnungslose Menschen, die ihr Hosting nutzen und feststellen, dass ihre Websites auf der ganzen Welt blockiert sind.

Der Python -Code zum Sammeln von IP addresses

Nachstehend finden Sie den Code zum Sammeln der IP addresses, wenn Sie es selbst versuchen möchten. Beachten Sie den Unterschied zwischen subprocess.run() und subprocess.popen(). Mit letzterer können wir eine vollständige Befehlszeile ausführen, mit Filtern, Pipes und Umleitungen.

#!/usr/bin/python3
# manage_ipset_blocklist_postfix_sasl.py
import datetime
import glob
import logging
import os
import re
import shlex
import subprocess
import sys

# CONSTANTS
PROGRAM_NAME = 'manage_ipset_blocklist_postfix_sasl'
# never add these ips to ipset
NEVER_ADD_IPS = [
    'xxx.xxx.xxx.xxx',
]
FAIL2BAN_FILES_DIR = '/var/log'
FILES_DIR = '/root/f2b_bans'
# log file
LOG_FILE = os.path.join(FILES_DIR, PROGRAM_NAME + '.log')
# temporary file(s)
BANS_FILE = os.path.join(FILES_DIR, 'bans')
# ipset
IPSET_EXE = '/usr/sbin/ipset'
IPSET_BLOCKLIST_NAME = 'blocklist_postfix_sasl'
IPSET_BLOCKLIST_LOG_FILE = os.path.join(FILES_DIR, IPSET_BLOCKLIST_NAME + '.log')
# this file is loaded by ipset on reboot
IPSET_SAVE_FILE = '/etc/iptables/ipset'

def get_logger(
    console_log_level=logging.DEBUG,
    file_log_level=logging.DEBUG,
    log_file=os.path.splitext(__file__)[0] + '.log',
):
    logger_format = '%(asctime)s %(levelname)s [%(filename)-30s%(funcName)30s():%(lineno)03s] %(message)s'
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    if console_log_level:
        # console
        console_handler = logging.StreamHandler(sys.stdout)
        console_handler.setLevel(console_log_level)
        console_handler.setFormatter(logging.Formatter(logger_format))
        logger.addHandler(console_handler)
    if file_log_level:
        # file
        file_handler = logging.FileHandler(log_file)
        file_handler.setLevel(file_log_level)
        file_handler.setFormatter(logging.Formatter(logger_format))
        logger.addHandler(file_handler)
    return logger

logger = get_logger(
    #console_log_level=logging.INFO,
    #console_log_level=None,
    log_file=LOG_FILE,
)
logger.debug('START')

class CmdLine:
    def __init__(
        self,
        logger=None,
    ):
        self.logger = logger

    def run(self, command, stdout=None, check=False, stdout_file=None, stdout_file_mode='w'):
        if stdout_file is None:
            result = subprocess.run(shlex.split(command), capture_output=True, text=True, stdout=stdout, check=check)
        else:
            with open(stdout_file, stdout_file_mode) as fo:
                result = subprocess.run(shlex.split(command), text=True, stdout=fo, check=check)
        self.logger.debug(f'type(result.stdout) = {type(result.stdout)}, result.stdout = {result.stdout}')
        self.logger.debug(f'type(result.stderr) = {type(result.stderr)}, result.stderr = {result.stderr}')
        self.logger.debug(f'type(result.returncode) = {type(result.returncode)}, result.returncode = {result.returncode}')
        return result

    def popen(self, cmd):
        self.logger.debug(f'cmd = {cmd}')
        p = subprocess.Popen(cmd, shell=True)
        returncode = p.wait()
        self.logger.debug(f'type(returncode) = {type(returncode)}, returncode = {returncode}')
        return returncode

    def get_file_line_count(self, f):
        p = subprocess.Popen(['wc', '-l', f], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        result, err = p.communicate()
        if p.returncode != 0:
            raise IOError(err)
        return int(result.strip().split()[0])

    def get_file_size(self, f):
        return os.path.getsize(f)

    def create_dir(self, d):
        os.makedirs(d, exist_ok=True)
        return True

    def get_dir_files(self, d, pattern):
        self.logger.debug(f'd = {d}, pattern = {pattern}')
        pathname = os.path.join(d, pattern)
        self.logger.debug(f'pathname = {pathname}')
        return glob.glob(os.path.join(d, pattern))

    def remove_file(self, f):
        self.logger.debug(f'(f = {f})')
        try:
            os.remove(f)
        except OSError:
            pass

def main():

    # 2023-04-16 17:28:50,860 fail2ban.actions        [790]: NOTICE  [postfix-sasl] Ban xxx.xxx.xxx.xxx
    ban_pattern = re.compile(r'^.*?\[postfix\-sasl\]\s+Ban\s+(.*?)\s*$')
    ip4_pattern = re.compile(r'^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$')

    cmd_line = CmdLine(
        logger=logger,
    )

    # create ipset for blocklist if not there yet
    command = f'{IPSET_EXE} create {IPSET_BLOCKLIST_NAME} hash:ip hashsize 4096'
    cmd_line.run(command)

    # get ipset blocklist members to avoid excessive calling 'ipset add <ip address>'
    blocklist_members = []
    command = f'{IPSET_EXE} list {IPSET_BLOCKLIST_NAME}'
    result = cmd_line.run(command)
    for line in result.stdout.split('\n'):
        if ':' in line:
            continue
        line = line.strip()
        if ip4_pattern.match(line):
            blocklist_members.append(line)
    blocklist_members_len = len(blocklist_members)
    logger.debug(f'blocklist_members_len = {blocklist_members_len}')

    # filter Fail2Ban logs Bans to BANS_FILE
    logger.debug(f'filter fail2ban.log Bans to bans file {BANS_FILE} ...')
    cmd_line.remove_file(BANS_FILE)
    files = cmd_line.get_dir_files(FAIL2BAN_FILES_DIR, 'fail2ban.log*')
    for f in files:
        logger.debug(f'iterating file {f}')
        grep = 'grep'
        if f.endswith('.gz'):
            grep = 'zgrep'
        command = f'{grep} NOTICE "{f}" | grep "postfix-sasl" | grep Ban >> {BANS_FILE}'
        cmd_line.popen(command)

    # get ips from BANS_FILE
    ban_ips = []
    with open(BANS_FILE, 'r') as fo:
        data = fo.read()
    lines = data.split('\n')
    for line in lines:
        m = ban_pattern.search(line)
        if m is None:
            continue
        ip = m.group(1)
        if ip4_pattern.match(ip) is None:
            continue
        # never
        if ip in NEVER_ADD_IPS:
            continue
        # no duplicates
        if ip in ban_ips:
            continue
        # not if already in members
        if ip in blocklist_members:
            continue
        ban_ips.append(ip)
    ban_ips_count = len(ban_ips)
    logger.debug(f'ban_ips_count = {ban_ips_count}')

    # add ips to ipset blocklist
    added_count = 0
    rejected_count = 0
    for ban_ip in ban_ips:
        command = f'{IPSET_EXE} add {IPSET_BLOCKLIST_NAME} {ban_ip}'
        result = cmd_line.run(command)
        returncode = result.returncode
        logger.debug(f'type(returncode) = {type(returncode)}, returncode = {returncode}')
        if returncode == 0:
            added_count += 1
            # also add ip with timestamp to ipset_added file
            dt = datetime.datetime.utcnow().isoformat()
            with open(IPSET_BLOCKLIST_LOG_FILE, 'a') as fo_add_file:
                fo_add_file.write(f'{dt} add {IPSET_BLOCKLIST_NAME} {ban_ip}\n')
        else:
            rejected_count += 1
    logger.info(f'added_count = {added_count}, rejected_count = {rejected_count}')

    logger.debug(f'saving ipset to {IPSET_SAVE_FILE}')
    command = f'{IPSET_EXE} save -file {IPSET_SAVE_FILE}'
    cmd_line.run(command)

    logger.debug(f'to save again: {command}')

main()


Zusammenfassung

Ich habe ipset noch nie benutzt, es ist gut zu lernen, wie man es benutzt. Fail2Ban kann auch mit ipset verwendet werden, aber ich wollte meine Fail2Ban-Konfiguration nicht ändern. Mit ipset und dem Skript Python kann ich schlechte IP addresses sammeln und den Zugriff auf meinen Server automatisch blockieren. Und mit dem Python -Paket IP2Location und dem Kommandozeilendienstprogramm whois kann ich ganz einfach weitere Informationen von diesen IP addresses erhalten.

Links / Impressum

Fail2ban
https://www.fail2ban.org

How to find the actual address of spoofed IPs?
https://security.stackexchange.com/questions/48523/how-to-find-the-actual-address-of-spoofed-ips

How to Make iptables Firewall Rules Persistent on Debian/Ubuntu
https://linuxiac.com/persistent-iptables-firewall-rules

IP2Location
https://pypi.org/project/IP2Location

ipset
https://ipset.netfilter.org/ipset.man.html

Persistent ipset for Ubuntu/Debian compatible with ufw and iptables-persistent
https://selivan.github.io/2018/07/27/ipset-save-with-ufw-and-iptables-persistent-and.html

WHOIS(1)
https://manpages.debian.org/bullseye/whois/whois.1.en.html

Einen Kommentar hinterlassen

Kommentieren Sie anonym oder melden Sie sich zum Kommentieren an.

Kommentare

Eine Antwort hinterlassen

Antworten Sie anonym oder melden Sie sich an, um zu antworten.