# Bedienungsanleitung — Website Integrity Scanner Überwachungswerkzeug für die statische Website **bredelar.info** gegen SEO-Spam und unbefugte Manipulationen. Der Scanner läuft lokal im Verantwortungsbereich des Betreibers. --- ## Inhaltsverzeichnis 1. [Grundprinzip](#1-grundprinzip) 2. [Voraussetzungen und Installation](#2-voraussetzungen-und-installation) 3. [Erstinbetriebnahme](#3-erstinbetriebnahme) 4. [Täglicher Betrieb](#4-täglicher-betrieb) 5. [Befehle im Überblick](#5-befehle-im-überblick) 6. [Ergebnis-Level und Exit-Codes](#6-ergebnis-level-und-exit-codes) 7. [Berichte lesen](#7-berichte-lesen) 8. [Änderungen freigeben (approve)](#8-änderungen-freigeben-approve) 9. [Vorgehen bei einem Alarm](#9-vorgehen-bei-einem-alarm) 10. [Automatisierung per Cron-Job](#10-automatisierung-per-cron-job) 11. [E-Mail-Benachrichtigung einrichten](#11-e-mail-benachrichtigung-einrichten) 12. [Konfigurationsreferenz](#12-konfigurationsreferenz) 13. [Externe Domains verwalten](#13-externe-domains-verwalten) 14. [Rausch-Unterdrückung konfigurieren](#14-rausch-unterdrückung-konfigurieren) 15. [Was der Scanner erkennt](#15-was-der-scanner-erkennt) 16. [Verzeichnisstruktur](#16-verzeichnisstruktur) --- ## 1. Grundprinzip Der Scanner arbeitet nach dem **Baseline-Diff-Prinzip**: ``` Initiale Sichtprüfung → Baseline festlegen ↓ Täglicher Scan ↓ Aktueller Zustand ──vergleiche── genehmigte Baseline ↓ Abweichungen → Score → GREEN / YELLOW / RED ``` - Die **Baseline** ist ein einmal manuell geprüfter und freigegebener Referenzzustand. Sie wird **niemals automatisch** aktualisiert. - Jede Abweichung erhält Punkte. Der Gesamt-Score entscheidet über das Alarm-Level. - Legitime Änderungen (z.B. neuer Artikel) müssen explizit per `approve` freigegeben werden. --- ## 2. Voraussetzungen und Installation ```bash # Python 3.10 oder neuer erforderlich python3 --version # Virtuelle Umgebung anlegen und aktivieren cd /home/dschlueter/Python_Programs/integrity_scanner_fuer_statische_Webseiten python3 -m venv venv source venv/bin/activate # Abhängigkeiten installieren pip install -r requirements.txt ``` Alle Befehle müssen aus dem Projektverzeichnis ausgeführt werden (dort liegt `config.yaml`). --- ## 3. Erstinbetriebnahme ### Schritt 1: Ersten Crawl durchführen ```bash python -m scanner init ``` Die Ausgabe zeigt: - alle gefundenen internen URLs - alle externen Domains (mit Hinweis, ob in der Whitelist) - potenzielle Auffälligkeiten (Hidden Content, Meta-Refresh, verdächtige Scripts) - Crawl-Fehler (Seiten, die nicht erreichbar waren) ### Schritt 2: Ausgabe manuell prüfen Wichtig vor dem nächsten Schritt: - Alle externen Domains prüfen. Legitime Domains in `config/allowed_external.yaml` eintragen (siehe [Abschnitt 13](#13-externe-domains-verwalten)). - Auffälligkeiten im Browser nachschauen. ### Schritt 3: Baseline freigeben Nur wenn alles sauber ist: ```bash python -m scanner approve --all --note "Initiale Baseline, geprüft am $(date +%Y-%m-%d)" ``` Ab jetzt ist der tägliche Betrieb möglich. --- ## 4. Täglicher Betrieb ### Manueller Scan ```bash python -m scanner scan ``` Ergebnis erscheint direkt im Terminal. Der vollständige Report liegt in `reports/`. ### Automatischer Scan (Cron) ```bash # Cron-Eintrag hinzufügen crontab -e ``` Eintrag: ```cron # Website-Scan täglich um 03:00 Uhr 0 3 * * * cd /home/dschlueter/Python_Programs/integrity_scanner_fuer_statische_Webseiten && /home/dschlueter/Python_Programs/integrity_scanner_fuer_statische_Webseiten/venv/bin/python -m scanner scan >> logs/cron.log 2>&1 ``` Der Exit-Code (0/1/2) ist für Cron-basierte Alarmierung nutzbar (z.B. mit `cronitor` oder systemd-Timern mit `OnFailure=`). --- ## 5. Befehle im Überblick Alle Befehle werden mit `python -m scanner ` aufgerufen. Globale Optionen: `--config path/to/config.yaml`, `--verbose` (`-v`). | Befehl | Zweck | |---|---| | `init` | Erstcrawl, Ausgabe aller Funde, noch kein Baseline-Update | | `crawl` | Crawlt die Website, speichert Snapshot (kein Vergleich) | | `check` | Vergleicht letzten Snapshot mit Baseline, erstellt Report | | `scan` | `crawl` + `check` in einem Schritt (für den Cron-Betrieb) | | `approve --all` | Alle neuen Snapshot-URLs in die Baseline übernehmen | | `approve --url URL` | Einzelne URL freigeben | | `approve --rebuild` | Gesamte Baseline ersetzen (nach großem Redesign) | | `report` | Letzten Report im Terminal anzeigen | | `report --format json` | Letzten Report als JSON anzeigen | | `status` | Baseline-Datum, letzter Scan, aktueller Risk-Level | ### Beispiele ```bash # Einzelne Seite freigeben (z.B. nach Impressums-Update) python -m scanner approve --url https://bredelar.info/impressum/ --note "Impressum aktualisiert Juni 2026" # Alle Änderungen auf einmal freigeben (nur nach vollständiger Prüfung!) python -m scanner approve --all --note "Redesign vom 2026-06-12 freigegeben" # Baseline nach komplettem Umbau neu aufbauen python -m scanner approve --rebuild --note "Komplettes Redesign v2, alle Seiten neu geprüft" # Bestimmten Snapshot freigeben (statt den neuesten) python -m scanner approve --all --snapshot data/snapshots/20260612_030000 # Aktuellen Stand prüfen python -m scanner status # Letzten Report anzeigen python -m scanner report ``` --- ## 6. Ergebnis-Level und Exit-Codes | Level | Score | Exit-Code | Bedeutung | |---|---|---|---| | **GREEN** | 0–19 | `0` | Keine Auffälligkeiten | | **YELLOW** | 20–59 | `1` | Warnung — manuelle Prüfung empfohlen | | **RED** | ≥ 60 | `2` | Alarm — sofortige Prüfung notwendig | Die Schwellenwerte sind in `config.yaml` unter `thresholds` anpassbar. --- ## 7. Berichte lesen Reports liegen in `reports/YYYYMMDD_HHMMSS/` als `report.md` und `report.json`. ```bash # Neuesten Report im Terminal lesen python -m scanner report # Direkt die Datei öffnen ls reports/ cat reports/20260612_030000/report.md ``` ### Aufbau des Reports | Abschnitt | Inhalt | |---|---| | **Zusammenfassung** | Kennzahlen auf einen Blick | | **Risikobewertung** | Alle Gründe für den Score (was hat wie viele Punkte ausgelöst) | | **Neue externe Domains** | Domains, die bisher nicht in der Baseline waren | | **Neue interne URLs** | Seiten, die neu dazugekommen sind | | **Fehlende URLs** | Seiten, die in der Baseline stehen, jetzt aber nicht mehr gefunden wurden | | **Geänderte Seiten** | Detailansicht: Textänderungen, Link-Diffs, Meta-Änderungen | | **Kaputte Links** | `[NEU]` = noch nicht in Baseline, `[bekannt]` = schon bekannt, wird nicht bewertet | | **Unerwartetes JSON-LD** | Strukturierte Daten mit unbekanntem `@type` | | **Verdächtige Dateinamen** | Links auf Dateien mit Webshell-typischen Namen | | **Whitelist-Verstösse** | Externe Domains, die nicht in `allowed_external.yaml` stehen | | **Fehlende Security-Header** | Informativ (kein Score) — nur auf HTTP-200-Seiten | | **Crawl-Fehler** | URLs, die der Crawler nicht laden konnte | | **Nächste Schritte** | Abhängig vom Level: konkrete Handlungsempfehlungen | --- ## 8. Änderungen freigeben (approve) Wann `approve` nötig ist: - Nach einer legitimen Inhaltsänderung (neuer Artikel, aktualisiertes Impressum, ...) - Nach einem Redesign - Nach Bereinigung eines echten Angriffs ### Einzelne URL freigeben Wenn nur eine bestimmte Seite geändert wurde: ```bash python -m scanner approve --url https://bredelar.info/veranstaltungen/ \ --note "Veranstaltungskalender aktualisiert" ``` ### Alle Änderungen freigeben Nach Prüfung aller markierten URLs: ```bash python -m scanner approve --all --note "Monatliches Update geprüft und freigegeben" ``` **Achtung**: `--all` übernimmt alles aus dem letzten Snapshot. Nur anwenden, wenn jede gemeldete Änderung manuell gesichtet wurde. ### Komplette Baseline neu aufbauen Nach einem großen Redesign oder nach Bereinigung eines Angriffs: ```bash python -m scanner approve --rebuild --note "Nach Angriff bereinigt, neues Redesign" ``` `--rebuild` ersetzt die gesamte bestehende Baseline durch den aktuellen Snapshot. --- ## 9. Vorgehen bei einem Alarm ### YELLOW — Warnung 1. Report lesen: `python -m scanner report` 2. Markierte URLs im Browser aufrufen und Quelltext prüfen 3. Ist die Änderung legitim? - Ja: `python -m scanner approve --url --note "Grund"` - Nein: Abschnitt [Angriff](#angriff) lesen ### RED — Alarm Der Score ist ≥ 60. Typische Auslöser: neue externe Domain (50 Pkt.) + Hidden Content (40 Pkt.), Webshell-Dateiname (60 Pkt.), Meta-Refresh (40 Pkt.). #### Angriff 1. **Nicht sofort approve aufrufen** — das würde den kompromittierten Zustand als neue Baseline festschreiben. 2. Report sichern: `cp -r reports/$(ls reports/ | tail -1) ~/alarm-$(date +%Y%m%d)/` 3. Website im Browser aufrufen und nach sichtbaren Fremdinhalten suchen. 4. Quelltext der betroffenen Seiten prüfen (insbesondere `display:none`-Bereiche). 5. Hoster kontaktieren, SSH-/FTP-Zugriffslogs prüfen. 6. Dateien mit Backup vergleichen (diff oder rsync --dry-run). 7. Nach vollständiger Bereinigung: ```bash python -m scanner scan # muss GREEN ergeben python -m scanner approve --rebuild --note "Nach Angriff bereinigt $(date +%Y-%m-%d)" ``` --- ## 10. Automatisierung per Cron-Job ### Cron-Eintrag ```bash crontab -e ``` ```cron # Website-Scan täglich um 03:00 Uhr 0 3 * * * cd /home/dschlueter/Python_Programs/integrity_scanner_fuer_statische_Webseiten && /home/dschlueter/Python_Programs/integrity_scanner_fuer_statische_Webseiten/venv/bin/python -m scanner scan >> logs/cron.log 2>&1 ``` ### Log prüfen ```bash tail -50 logs/cron.log tail -f logs/scanner.log # detaillierteres Log des Scanners selbst ``` ### Log-Rotation (optional) Datei `/etc/logrotate.d/integrity-scanner`: ``` /home/dschlueter/Python_Programs/integrity_scanner_fuer_statische_Webseiten/logs/*.log { daily rotate 30 compress missingok notifempty } ``` --- ## 11. E-Mail-Benachrichtigung einrichten ### config.yaml anpassen ```yaml alerting: min_level: "yellow" # oder "red" — nur bei rotem Alarm mailen email: enabled: true smtp_host: "mail.linix.de" # SMTP-Server des Providers smtp_port: 587 smtp_tls: true smtp_user: "scanner@linix.de" smtp_password_env: "SCANNER_SMTP_PASSWORD" from: "scanner@bredelar.info" to: - "dieter.schlueter@linix.de" ``` ### Passwort als Umgebungsvariable setzen **Niemals** das Passwort direkt in `config.yaml` eintragen. Stattdessen: ```bash # Temporär (bis zum nächsten Login) export SCANNER_SMTP_PASSWORD="geheimesPasswort" # Dauerhaft in ~/.bashrc oder ~/.profile echo 'export SCANNER_SMTP_PASSWORD="geheimesPasswort"' >> ~/.bashrc ``` Für den Cron-Job die Variable in der Crontab oder in `/etc/environment` setzen: ```cron SCANNER_SMTP_PASSWORD=geheimesPasswort 0 3 * * * cd /home/dschlueter/... ``` --- ## 12. Konfigurationsreferenz Alle Einstellungen in `config.yaml`. Fehlende Werte werden aus den Defaults übernommen. ### Kern-Einstellungen | Schlüssel | Standard | Bedeutung | |---|---|---| | `target` | — | Zu überwachende URL (Pflicht) | | `user_agent` | `integrity-scanner/1.0` | HTTP User-Agent | | `request_timeout` | `20` | Timeout pro Seite in Sekunden | ### Crawl-Einstellungen (`crawl:`) | Schlüssel | Standard | Bedeutung | |---|---|---| | `max_pages` | `200` | Maximale Anzahl gecrawlter Seiten | | `delay_seconds` | `1.0` | Pause zwischen Requests (Server-Schonung) | | `skip_extensions` | `.jpg`, `.pdf` etc. | Dateitypen, die nicht gecrawlt werden | ### Score-Gewichte (`scoring:`) | Ereignis | Standard-Punkte | |---|---| | Verdächtiger Dateiname (Webshell) | 60 | | Neue externe Domain | 50 | | Hidden Content (CSS-verstecktes Element mit Links/Text) | 40 | | Meta-Refresh | 40 | | Unerwartete Canonical-URL | 35 | | Unerwartetes JSON-LD `@type` | 35 | | Neue interne URL | 30 | | Neues verdächtiges Inline-Script | 30 | | Link in HTML-Kommentar | 25 | | Großer Textblock (>200 Zeichen) | 20 | | Fehlende interne URL | 20 | | Neuer kaputte Link (4xx/5xx, nicht in Baseline) | 20 | Alle Werte sind in `config.yaml` unter `scoring:` anpassbar. ### Alarm-Schwellen (`thresholds:`) ```yaml thresholds: yellow: 20 # ab hier: YELLOW red: 60 # ab hier: RED large_text_block_chars: 200 # ab dieser Zeichenanzahl gilt ein Textblock als "groß" ``` ### Erlaubte JSON-LD-Typen (`allowed_jsonld_types:`) Alle `@type`-Werte, die in den strukturierten Daten der Website vorkommen dürfen. Unbekannte Typen lösen einen Fund aus (35 Punkte). Aktuell konfiguriert: `Organization`, `WebSite`, `WebPage`, `Article`, `BreadcrumbList`, `LocalBusiness`, `Event`, `FAQPage` u.a. ### Security-Header (`security_headers:`) Liste der Header, die auf jeder Seite erwartet werden. Fehlende Header werden im Report gemeldet, aber **nicht** bewertet (kein Score-Einfluss). --- ## 13. Externe Domains verwalten Datei: `config/allowed_external.yaml` ```yaml # Legitime externe Domains — eine pro Zeile - fonts.googleapis.com - www.kloster-bredelar.de ``` Neue externe Domain erscheint im Scanner: 1. Domain im Browser prüfen — gehört sie zur Website? 2. Wenn legitim: in `config/allowed_external.yaml` eintragen 3. `python -m scanner approve --url ` oder `--all` Wenn eine Domain **nicht** in der Liste steht, löst jedes Vorkommen 50 Punkte aus (YELLOW-Alarm, bei weiteren Funden RED). --- ## 14. Rausch-Unterdrückung konfigurieren Manche Inhalte ändern sich automatisch (Datumsangaben, Zeitstempel) und würden sonst bei jedem Scan einen Fund auslösen. Zwei Mechanismen: ### CSS-Selektoren ignorieren (`ignore_selectors:`) Elemente werden vor dem Diff vollständig aus dem DOM entfernt: ```yaml normalization: ignore_selectors: - "#last-modified" - ".timestamp" - ".cookie-banner" ``` Datei: direkt in `config.yaml` oder in `config/ignore_rules.yaml` (kommentierte Beispiele dort vorhanden). ### Text-Muster ignorieren (`ignore_patterns:`) Reguläre Ausdrücke, die im normalisierten Text durch `[IGNORED]` ersetzt werden: ```yaml normalization: ignore_patterns: - '\b\d{1,2}\.\s*(?:Januar|Februar|März|April|Mai|Juni|Juli|August|September|Oktober|November|Dezember)\s+\d{4}\b' - '\b\d{1,2}\.\d{1,2}\.\d{4}\b' - 'Stand:\s+\S+' ``` --- ## 15. Was der Scanner erkennt ### Erkennt der Scanner ... | Prüfung | Erkannt? | Anmerkung | |---|---|---| | Neuen Text auf einer Seite | Ja | Ab 200 Zeichen bewertet | | Versteckten Text (CSS display:none etc.) | Ja | 40 Punkte | | Neue externe Domain | Ja | 50 Punkte | | Meta-Refresh-Weiterleitung | Ja | 40 Punkte | | Webshell-Dateien (shell.php, c99 etc.) | Ja | 60 Punkte | | Links in HTML-Kommentaren | Ja | 25 Punkte | | Geänderte Canonical-URL | Ja | 35 Punkte | | Unbekanntes JSON-LD `@type` | Ja | 35 Punkte | | `eval()`, `atob()`, `XMLHttpRequest` in Scripts | Ja | 30 Punkte | | Kaputte interne Links (neu) | Ja | 20 Punkte | | Kaputte interne Links (bekannt) | Ja, informativ | Kein Score | | Fehlende Security-Header | Ja, informativ | Kein Score | | Kaputte **externe** Links | Nein | Nur ob Domain neu ist | | Veränderungen in Bilddateien | Nein | Nur Dateiname/URL | | Serverseitige Code-Änderungen | Nein | Nur sichtbare HTML-Ausgabe | ### Grenzen des Scanners - Er prüft nur, was der Crawler als HTML-Seite sieht. Serverseitig eingeschleuster Code, der nur unter bestimmten Bedingungen aktiv wird (z.B. nur für Suchmaschinen-Bots), wird möglicherweise nicht erkannt. - Bilder und andere Binärdateien werden nicht auf Inhalt geprüft. - Externe Links werden nur auf Neuheit geprüft, nicht auf Erreichbarkeit. --- ## 16. Verzeichnisstruktur ``` config.yaml Hauptkonfiguration config/ allowed_external.yaml Whitelist erlaubter externer Domains ignore_rules.yaml Kommentierte Beispiele für Rausch-Unterdrückung data/ baseline/ Freigegebene Referenz (NIEMALS automatisch geändert) manifest.json Wer/was/wann freigegeben hat pages/ Pro URL eine JSON-Datei (16-stelliger Hex-Name) snapshots/ Ein Verzeichnis pro Crawl-Durchlauf YYYYMMDD_HHMMSS/ manifest.json Zeitstempel, Seitenzahl, Fehler pages/ Extrahierte Seiten-Daten logs/ scanner.log Detailliertes Laufzeit-Log cron.log Ausgabe der Cron-Ausführungen reports/ YYYYMMDD_HHMMSS/ report.md Menschenlesbarer Report report.json Maschinenlesbarer Report (für spätere Auswertung) scanner/ Python-Paket (Quellcode) tests/ Automatisierte Tests ``` ### Tests ausführen ```bash pytest tests/ -v # alle Tests pytest tests/test_differ.py -v # einzelnes Testmodul pytest tests/test_differ.py::TestScoreDiff::test_green_for_no_changes -v # einzelner Test ```