Script-Time: Informationen über eine IP

Schnelleinschätzung einer IP-Adresse

Script-Time: Informationen über eine IP
Photo by Mohammad Rahmani / Unsplash

Wenn mich ein Vorgesetzter kurzfristig fragt, ob eine eben genannte IP-Adresse eher positiv oder negativ einzuordnen ist, ist es hilfreich, schnell eine fundierte Antwort geben zu können.

Wenn mich also jemand fragt: "Wer issn 45.141.215.167 ?", dann tippe ich die IP fix in die Konsole und sehe:

So habe ich schnell eine finale Bewertung und kann sofort antworten.

Von Hand?

Bei der Untersuchung verdächtiger Verbindungen oder beim Bewerten externer Systeme ist es entscheidend, möglichst viele Informationen über eine IP-Adresse zu sammeln. Dazu gehören geografische Daten, der zugehörige Provider oder die Organisation (ASN), Kontaktinformationen des Netzwerkverwalters über WHOIS, die Reputation der IP auf bekannten Blacklists (DNSBL) sowie in Reputationsdatenbanken wie AbuseIPDB oder VirusTotal. Darüber hinaus liefern spezialisierte Suchmaschinen wie Shodan Einblicke in offene Ports, bekannte Dienste und sogar Schwachstellen, die mit der IP-Adresse assoziiert sind. Das Zusammentragen all dieser Informationen erfordert typischerweise den Besuch mehrerer Websites oder die Ausführung separater Befehle und Tools, was bei einer größeren Anzahl von IPs schnell unübersichtlich wird. Wir werden in einem späteren Beitrag noch sehen, wie man mit KI seine SIEM-Alerts auswerten kann. Doch hier geht es um ein schnelles Script.

Voraussetzungen und wichtige Konfiguration (APIs!)

Um das Skript nutzen zu können, müssen einige Standard-Tools auf dem System installiert sein. Dazu gehören curl zum Abrufen von Daten über HTTP(S), jq zum Parsen von JSON-formatierten Antworten, dig für DNS-Abfragen (speziell für DNSBL-Checks) und whois zum Abrufen von WHOIS-Informationen. Diese sind auf den meisten Linux-Distributionen verfügbar und können bei Bedarf über den Paketmanager nachinstalliert werden (z.B. sudo apt install curl jq dnsutils whois unter Debian/Ubuntu oder sudo dnf install curl jq bind-utils whois unter Fedora).

Der entscheidende Punkt für die volle Funktionalität des Skripts ist die Konfiguration persönlicher API Keys. Dienste wie AbuseIPDB, Shodan und VirusTotal bieten umfangreiche Daten, die über ihre APIs zugänglich sind. Die Nutzung dieser APIs erfordert in der Regel eine Authentifizierung mittels eines API Keys. Das Skript ist so konzipiert, dass es ohne diese Keys zwar grundlegende Informationen (GeoIP, WHOIS, TOR, DNSBL) abruft, aber die wertvollen Reputations- und Security-Posture-Daten von AbuseIPDB, Shodan und VirusTotal überspringt, wenn die Standard-Platzhalter ("DEIN_API_SCHLUESSEL_HIER") nicht durch gültige Keys ersetzt werden.

Es wird dringend empfohlen, sich kostenlose API Keys auf den jeweiligen Plattformen zu registrieren und diese im Konfigurationsbereich des Skripts einzutragen:

Die kostenlosen Tarife dieser Dienste bieten in der Regel ausreichende Anfragen pro Minute/Tag für den individuellen Gebrauch.

Die dynamische Risiko-Einschätzung

Nachdem alle verfügbaren Informationen gesammelt wurden, bewertet das Skript die Ergebnisse anhand der gefundenen negativen Indikatoren (z.B. hoher AbuseIPDB-Score, viele VirusTotal-Meldungen, gefundene Schwachstellen in Shodan, Listung auf Blacklists, TOR-Exit-Node). Basierend auf der Anzahl und Schwere dieser Indikatoren wird eine Gesamteinschätzung (Niedrig, Mittel, Hoch, Kritisch) generiert und eine entsprechende Handlungsempfehlung gegeben. Diese Einschätzung ist eine automatisierte Zusammenfassung und dient als schnelle Orientierungshilfe.

Beispielsweise könnte eine IP mit einem AbuseIPDB-Score über 90 und einer Listung auf mehreren DNSBLs als "Kritisch" eingestuft werden, während eine IP ohne negative Funde als "Niedrig" bewertet wird.

Nutzung des Skripts

Ein einfacher Aufruf mit wer_issn [IP], und schon erhält man die eingangs gezeigte Einschätzung.

Das fertige Script

Ja. Es ist wohl das längste Script geworden.

wer_issn() {
  # --- Konfiguration & API Keys ---
  # !!! WICHTIG: Trage hier DEINE persönlichen API Keys ein! !!!
  # Ohne gültige Keys werden die entsprechenden Abschnitte übersprungen.
  # Kostenlose Keys sind auf den jeweiligen Plattformen erhältlich.
  # Es gibt KEINE zuverlässigen generischen/öffentlichen Keys für diese Dienste.

  # HIER DEINEN KEY EINTRAGEN: https://www.abuseipdb.com/register
  local ABUSEIPDB_API_KEY="DEIN_API_SCHLUESSEL_HIER"

  # HIER DEINEN KEY EINTRAGEN: https://account.shodan.io/register
  local SHODAN_API_KEY="DEIN_API_SCHLUESSEL_HIER"

  # HIER DEINEN KEY EINTRAGEN: https://www.virustotal.com/gui/join-us
  local VIRUSTOTAL_API_KEY="DEIN_API_SCHLUESSEL_HIER"

  # HIER DEINEN KEY EINTRAGEN (Community Key reicht): https://www.greynoise.io/signup
  local GREYNOISE_API_KEY="DEIN_API_SCHLUESSEL_HIER"

  # Setze auf "true" für farbige Ausgabe, wenn vom Terminal unterstützt
  local USE_COLOR="true"

  # --- Variablen Initialisierung ---
  local ip_address="$1"
  # Regex zur grundlegenden Validierung des IP-Formats (nicht 100% perfekt, aber ausreichend)
  local ip_regex='^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'

  # --- Farbdefinitionen ---
  # Definiert Farben nur, wenn USE_COLOR true ist, das Terminal unterstützt Farben (-t 1)
  # und der Befehl tput existiert.
  local COLOR_RESET="" COLOR_BOLD="" COLOR_RED="" COLOR_GREEN="" COLOR_YELLOW="" COLOR_BLUE="" COLOR_CYAN=""
  if [[ "$USE_COLOR" == "true" ]] && [[ -t 1 ]] && command -v tput &> /dev/null; then
    COLOR_RESET=$(tput sgr0)    # Farbe zurücksetzen
    COLOR_BOLD=$(tput bold)     # Fettschrift
    COLOR_RED=$(tput setaf 1)   # Rot
    COLOR_GREEN=$(tput setaf 2) # Grün
    COLOR_YELLOW=$(tput setaf 3) # Gelb
    COLOR_BLUE=$(tput setaf 4)  # Blau
    COLOR_CYAN=$(tput setaf 6)  # Cyan
  fi

  # --- Hilfsfunktionen ---

  # Gibt eine formatierte Zeile aus (Label : Wert [mit Farbe])
  print_line() {
    local label="$1"
    local value="$2"
    local value_color="${3:-${COLOR_RESET}}" # Dritter Parameter ist optional für Farbwert
    printf "%-30s: %s%s%s\n" "${COLOR_BLUE}${label}${COLOR_RESET}" "$value_color" "$value" "${COLOR_RESET}"
  }

  # Gibt eine Überschrift für einen Abschnitt aus
  print_section_header() {
    echo
    echo "${COLOR_BOLD}${COLOR_CYAN}--- ${1} ---${COLOR_RESET}"
  }

  # Gibt die Hauptüberschrift mit der IP-Adresse aus
  print_major_header() {
    local ip=$1
    echo "${COLOR_BOLD}${COLOR_YELLOW}==================================================${COLOR_RESET}"
    echo "${COLOR_BOLD}${COLOR_YELLOW} IP Address Report für: ${ip}${COLOR_RESET}"
    echo "${COLOR_BOLD}${COLOR_YELLOW}==================================================${COLOR_RESET}"
  }

  # Prüft, ob ein benötigter Befehl existiert
  check_command() {
    if ! command -v "$1" &> /dev/null; then
      echo "${COLOR_RED}Fehler: Befehl '$1' nicht gefunden. Bitte installieren (${COLOR_YELLOW}z.B. apt install $1 / dnf install $1 / brew install $1${COLOR_RED}).${COLOR_RESET}" >&2
      return 1
    fi
    return 0
  }

  # --- Initialisierung für dynamische Bewertung ---
  # Zähler für negative Indikatoren, die das Risiko erhöhen
  local negative_indicators=0
  # Status Flags für Fehler/Nicht-Verfügbarkeit (werden nur für assessment_reasons verwendet)
  local ipinfo_failed=false
  local whois_failed=false
  local abuseipdb_skipped=false abuseipdb_failed=false
  local virustotal_skipped=false virustotal_failed=false virustotal_notfound=false
  local greynoise_skipped=false greynoise_failed=false greynoise_notfound=false
  local shodan_skipped=false shodan_failed=false
  local torcheck_failed=false


  # Liste der Gründe für die aktuelle Risikoeinschätzung (diese korrelieren mit den Indikatoren ODER sind Statusmeldungen)
  local assessment_reasons=()

  # --- Eingabevalidierung ---
  # Prüft, ob eine IP-Adresse als Argument übergeben wurde
  if [[ -z "$ip_address" ]]; then
      echo "${COLOR_RED}Fehler: IP-Adresse fehlt.${COLOR_RESET}" >&2
      echo "Benutzung: wer_isn <IP_Adresse>" >&2
      return 1
  fi
  # Prüft, ob das Format der IP-Adresse gültig ist
  if ! [[ "$ip_address" =~ $ip_regex ]]; then
      echo "${COLOR_RED}Fehler: Ungültiges IP-Format '${ip_address}'.${COLOR_RESET}" >&2
      return 1
  fi

  # --- Abhängigkeiten prüfen ---
  # Stellt sicher, dass alle benötigten Befehle installiert sind
  check_command "curl" || return 1
  check_command "jq" || return 1
  check_command "dig" || return 1
  check_command "whois" || return 1
  check_command "wget" || return 1 # Neu für den spezifischen TOR Check
  check_command "tr" || return 1 # Neu für String-Bereinigung

  # --- Report Erstellung starten ---
  print_major_header "$ip_address"

  # --- Section: GeoIP & ASN (via ipinfo.io) ---
  print_section_header "GeoIP & Netzwerk (ipinfo.io)"
  local ipinfo_data
  # Ruft GeoIP- und ASN-Daten von ipinfo.io ab
  # -s: silent, -m 10: Timeout nach 10 Sekunden
  ipinfo_data=$(curl -s -m 10 "https://ipinfo.io/${ip_address}/json")

  # Prüft auf curl Fehler, leere Antwort oder Bogon/Error-Status von ipinfo.io
  if [[ $? -ne 0 ]] || [[ -z "$ipinfo_data" ]] || [[ "$(echo "$ipinfo_data" | jq -r '.bogon // .error // ""')" != "" ]]; then
     print_line "Status" "Fehler beim Abrufen der ipinfo.io Daten" "$COLOR_RED"
     ipinfo_failed=true
     # Fehler bei grundlegenden Infos erhöhen das Risiko nicht direkt, werden aber als Grund vermerkt
     assessment_reasons+=("ipinfo.io Fehler")
  else
     # Extrahiert und gibt relevante Daten aus
     print_line "Land" "$(echo "$ipinfo_data" | jq -r '.country // "N/A"')"
     print_line "Region" "$(echo "$ipinfo_data" | jq -r '.region // "N/A"')"
     print_line "Stadt" "$(echo "$ipinfo_data" | jq -r '.city // "N/A"')"
     print_line "Koordinaten" "$(echo "$ipinfo_data" | jq -r '.loc // "N/A"')"
     print_line "ASN" "$(echo "$ipinfo_data" | jq -r '.asn.asn // "N/A"')"
     print_line "Organisation (ASN)" "$(echo "$ipinfo_data" | jq -r '.asn.name // .org // "N/A"')"
     local asn_type=$(echo "$ipinfo_data" | jq -r '.asn.type // "N/A"')
     print_line "Typ (ASN)" "$asn_type"
     # Versucht, den IP-Typ zu erraten
     local ip_type_guess="Unbekannt/ISP"
     if echo "$ipinfo_data" | jq -e '.abuse' > /dev/null; then ip_type_guess="Hosting/Server (via Abuse Info)"; fi
     if echo "$ipinfo_data" | jq -e '.company.type' > /dev/null; then ip_type_guess="$(echo "$ipinfo_data" | jq -r '.company.type // "Unternehmen"') (via Company Info)"; fi
     if [[ "$asn_type" == "hosting" ]]; then ip_type_guess="Hosting/Server (via ASN Type)"; fi
     print_line "IP-Typ (Vermutung)" "$ip_type_guess" "$COLOR_YELLOW"
  fi

  # --- Section: WHOIS Information ---
  print_section_header "WHOIS Informationen"
  local WHOIS_DATA
  # Ruft WHOIS-Daten ab
  WHOIS_DATA=$(whois "$ip_address")

  # Prüft auf whois Fehler oder leere Daten
  if [[ $? -ne 0 ]] || [[ -z "$WHOIS_DATA" ]]; then
      print_line "Status" "Fehler beim Abrufen der WHOIS Daten oder keine Daten gefunden." "$COLOR_RED"
      whois_failed=true
      assessment_reasons+=("WHOIS Fehler/Keine Daten")
  else
      print_line "Kontaktinformationen" "(Auszug)"
      # Extrahiert relevante WHOIS-Felder und formatiert die Ausgabe
      echo "$WHOIS_DATA" | grep -Ei '^(inetnum|netname|descr|country|orgname|org-name|organization|admin-c|tech-c|abuse-c|person|address|phone|e-mail|nic-hdl):' | sed -e 's/^[a-zA-Z0-9-]+: *//' -e 's/^/  /' | head -n 15 # Limitiert auf 15 Zeilen
      # Versucht, verwandte Netzwerke über Handle (admin-c/tech-c) zu finden
      local nic_handle=$(echo "$WHOIS_DATA" | grep -Ei '^(admin-c|tech-c):' | sed -e 's/.*: *//' | head -n 1)
      if [[ -n "$nic_handle" ]]; then
          print_line "Verwandte Netzwerke (via $nic_handle)" "(WHOIS-Suche, kann ungenau sein)"
          # KORRIGIERTE ZEILE (vorher s/^ /): Füge zwei Leerzeichen am Anfang ein (s/^/  /)
          local related_nets=$(whois -i admin-c,tech-c "$nic_handle" 2>/dev/null | grep -Ei '^(inetnum|inet6num):' | sed -e 's/.*: *//' -e 's/^/  /' | sort -u | head -n 5) # Limitiert auf 5 Netzwerke
          if [[ -n "$related_nets" ]]; then echo "$related_nets"; else echo "  Keine direkt verwandten Netzwerke gefunden."; fi
      fi
  fi

  # --- Section: Reputation & Missbrauch ---
  print_section_header "Reputation & Missbrauch"

  # AbuseIPDB Check
  print_line "${COLOR_CYAN}AbuseIPDB Check${COLOR_RESET}" ""
  # Prüft, ob ein API Key konfiguriert ist
  if [[ "$ABUSEIPDB_API_KEY" == "DEIN_ABUSEIPDB_API_SCHLUESSEL_HIER" ]] || [[ -z "$ABUSEIPDB_API_KEY" ]]; then
    print_line "  Status" "Kein persönlicher AbuseIPDB API Key konfiguriert oder Beispiel-Key. Übersprungen." "$COLOR_YELLOW"
    abuseipdb_skipped=true
    assessment_reasons+=("AbuseIPDB N/A")
  else
    local abuse_data
    # Ruft AbuseIPDB Daten ab
    abuse_data=$(curl -s -m 15 -G "https://api.abuseipdb.com/api/v2/check" \
      --data-urlencode "ipAddress=${ip_address}" -d maxAgeInDays=90 -d verbose \
      -H "Key: ${ABUSEIPDB_API_KEY}" -H "Accept: application/json")

    # Prüft auf curl/jq Fehler oder API Fehler
    if [[ $? -ne 0 ]] || [[ -z "$abuse_data" ]] || ! echo "$abuse_data" | jq -e '.data' > /dev/null ; then
      print_line "  Status" "Fehler beim Abrufen der AbuseIPDB Daten" "$COLOR_RED"
      abuseipdb_failed=true
      assessment_reasons+=("AbuseIPDB Fehler")
    else
      # Extrahiert Missbrauch-Score und Report-Zahl
      abuse_score=$(echo "$abuse_data" | jq -r '.data.abuseConfidenceScore // 0')
      local report_count=$(echo "$abuse_data" | jq -r '.data.totalReports // 0')
      local usage_type=$(echo "$abuse_data" | jq -r '.data.usageType // "N/A"')

      # Färbung basierend auf Score und Aktualisierung der Indikatoren
      local score_color="${COLOR_GREEN}"
      if (( abuse_score >= 90 )); then score_color="${COLOR_RED}"; negative_indicators=$((negative_indicators + 2)); assessment_reasons+=("AbuseScore>90");
      elif (( abuse_score >= 50 )); then score_color="${COLOR_YELLOW}"; negative_indicators=$((negative_indicators + 1)); assessment_reasons+=("AbuseScore>50");
      elif (( abuse_score > 0 )); then score_color="${COLOR_YELLOW}"; assessment_reasons+=("AbuseScore>0"); fi # Leichte Markierung auch bei niedrigem Score

      print_line "  Missbrauch-Score (0-100)" "$abuse_score" "$score_color"
      print_line "  Anzahl Reports (90T)" "$report_count"
      print_line "  Nutzungstyp (AbuseIPDB)" "$usage_type"
      # Weitere Details wie Kategorien könnten hier bei Bedarf extrahiert und angezeigt werden
    fi
  fi

  # VirusTotal Check
  print_line "${COLOR_CYAN}VirusTotal Check${COLOR_RESET}" ""
  # Prüft, ob ein API Key konfiguriert ist
   if [[ "$VIRUSTOTAL_API_KEY" == "DEIN_VIRUSTOTAL_API_SCHLUESSEL_HIER" ]] || [[ -z "$VIRUSTOTAL_API_KEY" ]]; then
    print_line "  Status" "Kein persönlicher VirusTotal API Key konfiguriert oder Beispiel-Key. Übersprungen." "$COLOR_YELLOW"
    virustotal_skipped=true
    assessment_reasons+=("VirusTotal N/A")
  else
    local vt_data
    # Ruft VirusTotal Daten ab (v3 API)
    vt_data=$(curl -s -m 15 --request GET --url "https://www.virustotal.com/api/v3/ip_addresses/${ip_address}" --header "x-apikey: ${VIRUSTOTAL_API_KEY}" 2>/dev/null)

    # Prüft auf curl Fehler, leere Antwort oder jq Fehler (Daten nicht gefunden)
    if [[ $? -ne 0 ]] || [[ -z "$vt_data" ]] || ! echo "$vt_data" | jq -e '.data.attributes' > /dev/null; then
         # Prüft spezifisch auf "Not found" Fehler
         if echo "$vt_data" | jq -e '.error.code == "NotFoundError"' >/dev/null; then
             print_line "  Status" "VirusTotal: IP nicht in der Datenbank gefunden." "$COLOR_YELLOW"
             virustotal_notfound=true
             assessment_reasons+=("VT Nicht gefunden")
         else
              print_line "  Status" "Fehler beim Abrufen der VirusTotal Daten" "$COLOR_RED"
              virustotal_failed=true
              assessment_reasons+=("VirusTotal Fehler")
         fi
    else
        # Extrahiert Statistik der letzten Analyse
        local vt_stats=$(echo "$vt_data" | jq -r '.data.attributes.last_analysis_stats // {}')
        local vt_harmless_count=$(echo "$vt_stats" | jq -r '.harmless // 0')
        local vt_undetected_count=$(echo "$vt_stats" | jq -r '.undetected // 0')
        vt_malicious_count=$(echo "$vt_stats" | jq -r '.malicious // 0')
        vt_suspicious_count=$(echo "$vt_stats" | jq -r '.suspicious // 0')
        local vt_owner=$(echo "$vt_data" | jq -r '.data.attributes.as_owner // "N/A"')
        local vt_last_analysis=$(echo "$vt_data" | jq -r '.data.attributes.last_analysis_date // null')
        local vt_last_analysis_date="N/A"
        if [[ "$vt_last_analysis" != "null" ]]; then
           vt_last_analysis_date=$(date -d "@${vt_last_analysis}" +"%Y-%m-%d %H:%M" 2>/dev/null || echo "Datum N/A")
        fi

        print_line "  AS Owner (VT)" "$vt_owner"
        print_line "  Letzte Analyse" "$vt_last_analysis_date"
        print_line "  Statistik (Engines)" ""
        printf "    %-15s: %s\n" "Bösartig" "$vt_malicious_count"
        printf "    %-15s: %s\n" "Verdächtig" "$vt_suspicious_count"
        printf "    %-15s: %s\n" "Harmlos" "$vt_harmless_count"
        printf "    %-15s: %s\n" "Unerkannt" "$vt_undetected_count"


        # Aktualisierung der Indikatoren basierend auf VT-Statistik
        local vt_rep_color="${COLOR_GREEN}"
        if (( vt_malicious_count >= 5 )); then vt_rep_color="${COLOR_RED}"; negative_indicators=$((negative_indicators + 2)); assessment_reasons+=("VT Malicious>=5");
        elif (( vt_malicious_count > 0 )); then vt_rep_color="${COLOR_RED}"; negative_indicators=$((negative_indicators + 1)); assessment_reasons+=("VT Malicious>0");
        elif (( vt_suspicious_count > 0 )); then vt_rep_color="${COLOR_YELLOW}"; negative_indicators=$((negative_indicators + 1)); assessment_reasons+=("VT Suspicious>0"); fi # Diese Bedingung wird nur geprüft, wenn malicious_count 0 ist.
        print_line "  VT Reputation" "$((vt_malicious_count + vt_suspicious_count)) Engines melden Probleme" "$vt_rep_color"
        print_line "  VT Report URL" "https://www.virustotal.com/gui/ip-address/${ip_address}/detection"
    fi
  fi

    # GreyNoise Check
    print_line "${COLOR_CYAN}GreyNoise Check${COLOR_RESET}" ""
    # Prüft, ob ein API Key konfiguriert ist
    if [[ "$GREYNOISE_API_KEY" == "DEIN_GREYNOISE_API_SCHLUESSEL_HIER" ]] || [[ -z "$GREYNOISE_API_KEY" ]]; then
        print_line "  Status" "Kein persönlicher GreyNoise API Key konfiguriert oder Beispiel-Key. Übersprungen." "$COLOR_YELLOW"
        greynoise_skipped=true
        assessment_reasons+=("GreyNoise N/A")
    else
        local greynoise_data
        # Ruft GreyNoise Community Daten ab
        greynoise_data=$(curl -s -m 15 --request GET --url "https://api.greynoise.io/v3/community/${ip_address}" --header "key: ${GREYNOISE_API_KEY}")

        # Prüft auf curl/jq Fehler oder API Fehler
        if [[ $? -ne 0 ]] || [[ -z "$greynoise_data" ]] || echo "$greynoise_data" | jq -e '.message == "error"' > /dev/null; then
            # Prüft spezifisch auf "IP not found" oder ähnliche Nachrichten
            if echo "$greynoise_data" | jq -e '.message | contains("IP not found") or contains("not seen")' >/dev/null; then
                 print_line "  Status" "GreyNoise: IP nicht gefunden/gesehen." "$COLOR_GREEN" # Eine nicht gesehene IP ist oft positiv
                 greynoise_notfound=true
            else
                 print_line "  Status" "Fehler beim Abrufen der GreyNoise Daten" "$COLOR_RED"
                 greynoise_failed=true
                 assessment_reasons+=("GreyNoise Fehler")
            fi
        else
            # Extrahiert relevante GreyNoise Felder
            local gn_noise=$(echo "$greynoise_data" | jq -r '.noise // false')
            local gn_riot=$(echo "$greynoise_data" | jq -r '.riot // false')
            # Verbesserte Extraktion: Enferne Zeilenumbrüche aus der Klassifizierung
            local gn_classification=$(echo "$greynoise_data" | jq -r '.classification // "unknown"' | tr -d '\n\r')
            local gn_name=$(echo "$greynoise_data" | jq -r '.name // "N/A"')
            local gn_last_seen=$(echo "$greynoise_data" | jq -r '.last_seen // "N/A"')

            print_line "  GreyNoise Noise?" "$gn_noise" "$( [[ "$gn_noise" == "true" ]] && echo "$COLOR_YELLOW" || echo "$COLOR_GREEN" )"
            print_line "  GreyNoise RIoT?" "$gn_riot" "$( [[ "$gn_riot" == "true" ]] && echo "$COLOR_GREEN" || echo "$COLOR_YELLOW" )" # RIoT ist eher positiv
            print_line "  GreyNoise Classification" "$gn_classification" "$( [[ "$gn_classification" == "malicious" ]] && echo "$COLOR_RED" || [[ "$gn_classification" == "unknown" ]] && echo "$COLOR_YELLOW" || echo "$COLOR_GREEN" )"
            print_line "  GreyNoise Name" "$gn_name"
            print_line "  GreyNoise Last Seen" "$gn_last_seen"
            print_line "  GreyNoise Report URL" "$(echo "$greynoise_data" | jq -r '.link // "N/A"')"

            # Aktualisierung der Indikatoren basierend auf GreyNoise
            # Noise aber kein RIoT => potentiell unerwünschter Scanner
            if [[ "$gn_noise" == "true" ]] && [[ "$gn_riot" == "false" ]]; then
                 negative_indicators=$((negative_indicators + 1))
                 assessment_reasons+=("GN Noisy (Not RIoT)")
            fi
            # Eindeutig als bösartig klassifiziert
            if [[ "$gn_classification" == "malicious" ]]; then
                 negative_indicators=$((negative_indicators + 2))
                 assessment_reasons+=("GN Malicious")
             fi
        fi
    fi

  # --- Section: Security Posture ---
  print_section_header "Sicherheits-Posture"

  # Shodan Host Info
  print_line "${COLOR_CYAN}Shodan Host Check${COLOR_RESET}" ""
  # Prüft, ob ein API Key konfiguriert ist
   if [[ "$SHODAN_API_KEY" == "DEIN_SHODAN_API_SCHLUESSEL_HIER" ]] || [[ -z "$SHODAN_API_KEY" ]]; then
    print_line "  Status" "Kein persönlicher Shodan API Key konfiguriert oder Beispiel-Key. Übersprungen." "$COLOR_YELLOW"
    shodan_skipped=true
    assessment_reasons+=("Shodan N/A")
  else
    local shodan_data
    # Ruft Shodan Host Daten ab
    shodan_data=$(curl -s -m 15 "https://api.shodan.io/shodan/host/${ip_address}?key=${SHODAN_API_KEY}")

    # Prüft auf curl/jq Fehler oder API Fehler (z.B. IP nicht gefunden)
    if [[ $? -ne 0 ]] || [[ -z "$shodan_data" ]] || echo "$shodan_data" | jq -e '.error' > /dev/null; then
      print_line "  Status" "Fehler beim Abrufen der Shodan Daten oder IP nicht gefunden." "$COLOR_RED"
      shodan_failed=true
      assessment_reasons+=("Shodan Fehler")
    else
      # Extrahiert offene Ports und Schwachstellen-Zahl
      local open_ports_array=$(echo "$shodan_data" | jq -c '.ports // []')
      # Formatiert die Port-Liste
      local open_ports_str=$(echo "$open_ports_array" | jq -r 'join(", ")')
      if [[ -z "$open_ports_str" ]]; then open_ports_str="Keine offenen Ports gefunden"; fi

      shodan_vuln_count=$(echo "$shodan_data" | jq -r '.vulns | length // 0')
      local last_update=$(echo "$shodan_data" | jq -r '.last_update // "N/A"')

      print_line "  Offene Ports (Shodan)" "$open_ports_str"
      # Aktualisierung der Indikatoren basierend auf Schwachstellen
      local vuln_color="${COLOR_GREEN}"
      if (( shodan_vuln_count > 0 )); then vuln_color="${COLOR_RED}"; negative_indicators=$((negative_indicators + 1)); assessment_reasons+=("Shodan Vulns"); fi
      print_line "  Bekannte Schwachstellen (Shodan)" "$shodan_vuln_count" "$vuln_color"
      print_line "  Letztes Update (Shodan)" "$last_update"
      print_line "  Shodan Report URL" "https://www.shodan.io/host/${ip_address}"
    fi
  fi

  # TOR Exit Node Check (spezifische Methode von rueckgr.at)
  print_line "${COLOR_CYAN}TOR Exit Node Check (rueckgr.at)${COLOR_RESET}" ""
  # Führt den wget Check durch und prüft den Exit Code von grep
  # grep -q unterdrückt die Ausgabe, gibt aber 0 zurück, wenn eine Übereinstimmung gefunden wurde
  # wget -qO -: Silent, Output to stdout. --referer/--post-data imitiert Browser-Anfrage
  if wget -qO - --referer="https://torstatus.rueckgr.at/tor_exit_query.php" --post-data "QueryIP=${ip_address}&DestinationIP=&DestinationPort=" "https://torstatus.rueckgr.at/tor_exit_query.php" 2>/dev/null | grep -q " matches "; then
     print_line "  Ist TOR Exit Node?" "Ja" "$COLOR_RED"
     negative_indicators=$((negative_indicators + 2)) # TOR Nodes sind oft mit Missbrauch assoziiert, starke Indikation
     assessment_reasons+=("TOR Exit Node")
  else
     # grep hat nichts gefunden (Exit Code 1)
     print_line "  Ist TOR Exit Node?" "Nein" "$COLOR_GREEN"
  fi


  # --- Section: Blacklist Check (DNSBL - Beispiele) ---
  print_section_header "Blacklist Check (DNSBL - Beispiele)"
  # Kehrt die IP-Adresse um für die DNSBL-Abfrage
  local reversed_ip=$(echo "$ip_address" | awk -F. '{print $4"."$3"."$2"."$1}')
  # Liste bekannter DNSBL-Server (Beispiele)
  local dnsbl_servers=( "zen.spamhaus.org" "bl.spamcop.net" "dnsbl.sorbs.net" "b.barracudacentral.org" "dnsbl-1.uceprotect.net" "all.s5h.net" )
  local listed_on_str=""
  local dnsbl_listed_count=0 # Initialisierung sicherstellen
  # Iteriert durch die DNSBL-Server und führt eine dig-Abfrage durch
  for server in "${dnsbl_servers[@]}"; do
    # dig +short: Nur das Ergebnis, +time=1 +tries=1: Schneller Timeout
    if dig +short +time=1 +tries=1 "$reversed_ip.$server." &> /dev/null; then
       listed_on_str+="${server}, "
       dnsbl_listed_count=$((dnsbl_listed_count + 1))
    fi
  done

  # Aktualisierung der Indikatoren basierend auf DNSBL-Listungen
  if [[ $dnsbl_listed_count -gt 0 ]]; then
    # Gewichtung: Mehr Listungen erhöhen das Risiko stärker
    if (( dnsbl_listed_count >= 3 )); then
         negative_indicators=$((negative_indicators + 2)) # 3+ Listen sind ein stärkeres Signal
         assessment_reasons+=("Blacklisted(${dnsbl_listed_count}>=3)")
    else # dnsbl_listed_count is 1 or 2
         negative_indicators=$((negative_indicators + 1)) # 1-2 Listen
         assessment_reasons+=("Blacklisted(${dnsbl_listed_count})")
    fi
    listed_on_str=${listed_on_str%, } # Entfernt das letzte Komma und Leerzeichen
    print_line "Gelistet auf ($dnsbl_listed_count)" "$listed_on_str" "$COLOR_RED"
  else
    print_line "Status" "Auf den geprüften Beispiel-Listen nicht gefunden." "$COLOR_GREEN"
  fi

  # --- Finale Bewertung & Empfehlung ---
  # Bestimmt die finale Risikoeinschätzung basierend auf der Gesamtzahl der negativen Indikatoren
  local assessment="Niedrig"
  local recommendation="Keine dringende Aktion nötig. Standard-Monitoring."
  local assessment_color="${COLOR_GREEN}"

  if (( negative_indicators >= 7 )); then
      assessment="Kritisch"
      assessment_color="${COLOR_RED}"
      recommendation="Sehr hohes Risiko. Sofortige und permanente Blockierung dringend empfohlen."
  elif (( negative_indicators >= 4 )); then
      assessment="Hoch"
      assessment_color="${COLOR_YELLOW}"
      recommendation="Deutliches Risiko. Blockierung empfohlen, insbesondere wenn kritische Indikatoren (TOR, hohe Scores, Malicious GN) vorliegen."
  elif (( negative_indicators >= 1 )); then
      assessment="Mittel"
      assessment_color="${COLOR_YELLOW}"
      recommendation="Erhöhte Aufmerksamkeit, ggf. spezifische Logs prüfen. Blockierung erwägen bei sensiblen Systemen."
  else # negative_indicators == 0
      assessment="Niedrig"
      assessment_color="${COLOR_GREEN}"
      recommendation="Keine dringende Aktion nötig. Standard-Monitoring."
  fi

   # Text für die finale Bewertung zusammenstellen, inklusive der Gründe
   # Wichtig: Der Stringaufbau muss korrekt sein, um Indikatorzahl und Gründe anzuzeigen
   local final_assessment_text="${assessment} (Indikatoren: ${negative_indicators}"
   # Füge die gesammelten Gründe hinzu (kommagetrennt) *falls* es Gründe gibt
   if [[ ${#assessment_reasons[@]} -gt 0 ]]; then
       local reasons_str=$(printf ", %s" "${assessment_reasons[@]}")
       reasons_str=${reasons_str:2} # Entferne das führende ", "
       final_assessment_text+=", Gründe: ${reasons_str}"
   fi
   final_assessment_text+=")" # This closes the parenthesis

  # --- Footer mit finaler Bewertung ---
  echo
  echo "${COLOR_BOLD}${COLOR_YELLOW}================ FINALE BEWERTUNG ================${COLOR_RESET}"
  # Gib die finale Bewertung und Empfehlung aus
  print_line "Risiko-Einschätzung" "$final_assessment_text" "$assessment_color"
  print_line "Handlungsempfehlung" "$recommendation" "$assessment_color"
  echo "${COLOR_BOLD}${COLOR_YELLOW}==================================================${COLOR_RESET}"

  return 0
}
wer_issn 94.230.208.147
==================================================
 IP Address Report für: 94.230.208.147
==================================================

--- GeoIP & Netzwerk (ipinfo.io) ---
Land               : CH
Region             : Zurich
Stadt              : Zürich
Koordinaten        : 47.3667,8.5500
ASN                : N/A
Organisation (ASN) : AS29691 Nine Internet Solutions AG
Typ (ASN)          : N/A
IP-Typ (Vermutung) : Unbekannt/ISP

--- WHOIS Informationen ---
Kontaktinformationen: (Auszug)
  inetnum:        94.230.208.144 - 94.230.208.151
  netname:        CUST-DIGITALEGESELLSCHAFT-NET
  descr:          Digitale Gesellschaft
  country:        CH
  admin-c:        DG11277-RIPE
  tech-c:         DG11277-RIPE
  abuse-c:        DG11277-RIPE
  org-name:       Digitale Gesellschaft
  address:        CH-4000 Basel
  abuse-c:        DG11277-RIPE
  address:        CH-4000 Basel
  nic-hdl:        DG11277-RIPE
  descr:          Nine Internet Solutions AG, Switzerland
Verwandte Netzwerke (via DG11277-RIPE): (WHOIS-Suche, kann ungenau sein)
  185.195.71.240 - 185.195.71.255
  195.176.3.16 - 195.176.3.31
  94.230.208.144 - 94.230.208.151

--- Reputation & Missbrauch ---
AbuseIPDB Check: 
  Missbrauch-Score (0-100): 100
  Anzahl Reports (90T): 114
  Nutzungstyp (AbuseIPDB): Data Center/Web Hosting/Transit
VirusTotal Check: 
  AS Owner (VT)    : Nine Internet Solutions AG
  Letzte Analyse   : 2025-04-23 22:31
  Statistik (Engines): 
    Bösartig      : 8
    Verdächtig    : 2
    Harmlos        : 55
    Unerkannt      : 29
  VT Reputation    : 10 Engines melden Probleme
  VT Report URL    : https://www.virustotal.com/gui/ip-address/94.230.208.147/detection
GreyNoise Check: 
  GreyNoise Noise? : true
  GreyNoise RIoT?  : false
  GreyNoise Classification: 
malicious
  GreyNoise Name   : unknown
  GreyNoise Last Seen: 2025-04-25
  GreyNoise Report URL: https://viz.greynoise.io/ip/94.230.208.147

--- Sicherheits-Posture ---
Shodan Host Check: 
  Offene Ports (Shodan): 80, 8080, 443, 8443
  Bekannte Schwachstellen (Shodan): 0
  Letztes Update (Shodan): 2025-04-24T13:33:57.147446
  Shodan Report URL: https://www.shodan.io/host/94.230.208.147
TOR Exit Node Check (rueckgr.at): 
  Ist TOR Exit Node?: Ja

--- Blacklist Check (DNSBL - Beispiele) ---
Gelistet auf (6)   : zen.spamhaus.org, bl.spamcop.net, dnsbl.sorbs.net, b.barracudacentral.org, dnsbl-1.uceprotect.net, all.s5h.net

================ FINALE BEWERTUNG ================
Risiko-Einschätzung: Kritisch (Indikatoren: 11, Gründe: AbuseScore>90, VT Malicious>=5, GN Noisy (Not RIoT), GN Malicious, TOR Exit Node, Blacklisted(6>=3))
Handlungsempfehlung: Sehr hohes Risiko. Sofortige und permanente Blockierung dringend empfohlen.
==================================================