WBCE CMS Forum

WBCE CMS – Way Better Content Editing.

You are not logged in.

#1 23.11.2024 11:44:54

kleo
Member

Access deny

Moin!

Heutzutage sind die "unerwünschten Gäste" einer Webseite schon die Regel.

Ihre Webseitenaufrufe veränderen unter anderem die eigentliche Besucherstatistik. Man kann bestimmte URI´s sperren und damit auch deren Aufrufe in der Statistik. Die normale Aufrufe (sprich von den existierenden Seiten) werden aber weiter auch von diesen "Besucher" gezählt.

In diesem Zusammenhabf ist mir eine Frage aufgefallen.

Wäre es irgendwie möglich nach Aufruf einer URI mit bestimmten Wörter (Zeichenkomnination) die IP-Adresse des Benutzers für eine bestimmte Zeit sperren?

So was, wie bei mehreren Versuchen die falschen Logindaten einzugeben wird die IP-Adresse für eine bestimmte Zeit gesperret.

Offline

#2 24.11.2024 19:09:42

florian
Administrator

Re: Access deny

Interessante Idee, ich kenne das von Wordfence (einem Plugin für Würgpress).
Sprich doch mal Ruud direkt darauf an. Sein 404-Modul loggt bereits verdächtige Anfragen, vielleicht lässt sich das entsprechend erweitern.
Ruud freut sich übrigens auch über Nutzung seines Paypal-Spendenlinks.


Sorgen sind wie Nudeln: man macht sich meist zu viele.

Offline

#3 28.11.2024 13:04:12

mrbaseman
Developer

Re: Access deny

Für den Backend-Login ist ja schon eine IP-basierte Sperre drin, die bei Fehlversuchen einen Timeout hoch setzt.
Aber diese IPs dann für den Aufruf beliebiger Seiten auch sprren zu können, wäre eine interessante Erweiterung.
Das müsste man vermutlich in der Frontend-Klasse mit verdrahten, und für diese gesperrten IPs halt einen 401 Status code ausgeben.

Offline

#4 03.12.2024 11:59:21

kleo
Member

Re: Access deny

Moin!

Ich habe an einer Lösung gebastellt und scheinbar eine funktionierende Methode gefunden.

Es sind zwei neue Dateien, die im root der WBCE-Installation platziert sollen:

1. security_check.php - die ist für Sperrung
2. banned_ips.json - da werden die gesperrten IP-Adressen provisorisch gespeichert.

security_check.php:

[== PHP ==]
<?php
// Datei: security_check.php

// Einstellungen
$forbiddenPatterns = ['/test123/', '/hack/', '/badword/']; // Verbotene Muster (Regex)
$banDuration = 60; // Sperrdauer in Sekunden (z. B. 60 s =1 Minute)

// Funktion, um die IP-Adresse des Besuchers zu ermitteln
function getVisitorIP() {
    return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}

// Funktion, um eine IP-Adresse zu sperren
function banIP($ip, $duration) {
    $bannedIPsFile = 'banned_ips.json'; // Datei zur Speicherung der gesperrten IPs
    $bannedIPs = [];

    // Lade bestehende Sperren
    if (file_exists($bannedIPsFile)) {
        $bannedIPs = json_decode(file_get_contents($bannedIPsFile), true);
    }

    // Füge die aktuelle Sperre hinzu
    $bannedIPs[$ip] = time() + $duration;

    // Speichere die Sperren zurück in die Datei
    file_put_contents($bannedIPsFile, json_encode($bannedIPs));
}

// Funktion, um zu prüfen, ob eine IP gesperrt ist
function isIPBanned($ip) {
    $bannedIPsFile = 'banned_ips.json'; // Datei zur Speicherung der gesperrten IPs

    if (!file_exists($bannedIPsFile)) {
        return false;
    }

    $bannedIPs = json_decode(file_get_contents($bannedIPsFile), true);

    // Entferne abgelaufene Sperren
    $currentTime = time();
    foreach ($bannedIPs as $bannedIP => $expiry) {
        if ($expiry < $currentTime) {
            unset($bannedIPs[$bannedIP]);
        }
    }

    // Aktualisierte Liste speichern
    file_put_contents($bannedIPsFile, json_encode($bannedIPs));

    return isset($bannedIPs[$ip]);
}

// Hauptlogik
$visitorIP = getVisitorIP();
$requestURI = $_SERVER['REQUEST_URI'] ?? '/';

if (isIPBanned($visitorIP)) {
    // Sperrmeldung anzeigen und Zugriff blockieren
    http_response_code(403);
    die('Zugriff verweigert. Ihre IP-Adresse wurde gesperrt.');
}

// Überprüfe, ob die URI verbotene Muster enthält
foreach ($forbiddenPatterns as $pattern) {
    if (preg_match($pattern, $requestURI)) {
        // IP-Adresse sperren
        banIP($visitorIP, $banDuration);

        // Sperrmeldung anzeigen
        http_response_code(403);
        die('Verbotenes Muster erkannt. Ihre IP-Adresse wurde gesperrt.');
    }
}
?>

In der Datei index.php im root Verzeichnis der WBCE-Installation gleich am Anfang der Code nach <?php folgende Zeile einfügen:

[== PHP ==]
include 'security_check.php'; // Schutz für alle Seiten

Die verbotene Zeichenkombinationen sowie die Sperrzeit werden gleich am Anfang der Datei security_check.php definiert:

        // Einstellungen
        $forbiddenPatterns = ['/test123/', '/hack/', '/badword/']; // Verbotene Muster (Regex)
        $banDuration = 60; // Sperrdauer in Sekunden (z. B. 1 Stunde)

Die Meldung, die bei der Sperrung angezeigt wird, kann am Ende der selben Datei definiert werden:

        // Sperrmeldung anzeigen
        http_response_code(403);
        die('Verbotenes Muster erkannt. Ihre IP-Adresse wurde gesperrt.');

Um die vorgeschlagene Lösung zu testen:

        Besuche die Webseite mit erlaubten URLs, um sicherzustellen, dass der Zugriff funktioniert.
        Greife mit einer verbotenen Zeichenkombination in der URL zu (z. B. /index.php?test123), um zu prüfen, ob die Sperrung funktioniert.
        Warte die Sperrdauer ab und prüfe, ob die IP-Adresse nach Ablauf wieder Zugriff hat.

Vielleicht kann man daraus ein WBCE-Modul basteln...

Offline

#5 03.12.2024 12:33:58

kleo
Member

Re: Access deny

Ich habe es mir überlegt, die Vorgehensweise etwas zu erweitern.

Die "fleißigen" unerwünschten Besucher rufen oft in sekundenschnelle mehrere UPI´s. Ein „normaler“ Mensch kann so was nicht.

Man kann versuchen durch die „Häufigkeitsbegrenzung“ auch solche Besucher sperren.

Um die oben vorgeschlagene Lösung zu ergänzen erstelle ich noch eine Datei Namens rate_limit.json im root Verzeichnis

Alle Einstellungen (auch die Fehlermeldungtexte) habe ich zur besseren Übersicht oben bei den Einstellungen gruppiert.

Hier ist die dafür der angepasste Code der Datei security_check.php:


[== PHP ==]
<?php
// Einstellungen
$forbiddenPatterns = ['/test123/', '/hack/', '/badword/']; // Verbotene Muster (Regex)
$banDuration = 30; // Sperrdauer in Sekunden (z. B. 60 = 1 Minute, 3600 = 1 Stunde)
$rateLimit = 3; // Maximale Anfragen pro Zeitfenster
$rateLimitWindow = 3; // Zeitfenster in Sekunden (z. B. 60 = 1 Minute, 3600 = 1 Stunde)

// Fehlermeldungen
$errorMessages = [
    'banned' => 'Zugriff verweigert. Ihre IP-Adresse wurde gesperrt.',
    'rate_limit' => 'Zu viele Anfragen. Bitte versuchen Sie es später erneut.',
    'forbidden_pattern' => 'Verbotenes Muster erkannt. Ihre IP-Adresse wurde gesperrt.'
];

// Dateien zur Speicherung von Sperren und Limits
$bannedIPsFile = 'banned_ips.json'; // Datei zur Speicherung der gesperrten IPs
$rateLimitFile = 'rate_limit.json'; // Datei zur Speicherung der Anfrage-Zähler

// Funktion, um die IP-Adresse des Besuchers zu ermitteln
function getVisitorIP() {
    return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}

// Funktion, um eine IP-Adresse zu sperren
function banIP($ip, $duration) {
    global $bannedIPsFile;

    $bannedIPs = [];
    if (file_exists($bannedIPsFile)) {
        $bannedIPs = json_decode(file_get_contents($bannedIPsFile), true);
    }

    $bannedIPs[$ip] = time() + $duration;
    file_put_contents($bannedIPsFile, json_encode($bannedIPs));
}

// Funktion, um zu prüfen, ob eine IP gesperrt ist
function isIPBanned($ip) {
    global $bannedIPsFile;

    if (!file_exists($bannedIPsFile)) {
        return false;
    }

    $bannedIPs = json_decode(file_get_contents($bannedIPsFile), true);
    $currentTime = time();

    // Entferne abgelaufene Sperren
    foreach ($bannedIPs as $bannedIP => $expiry) {
        if ($expiry < $currentTime) {
            unset($bannedIPs[$bannedIP]);
        }
    }

    file_put_contents($bannedIPsFile, json_encode($bannedIPs));
    return isset($bannedIPs[$ip]);
}

// Funktion, um die Häufigkeitsbegrenzung zu überprüfen
function checkRateLimit($ip) {
    global $rateLimit, $rateLimitWindow, $rateLimitFile;

    $rateData = [];
    if (file_exists($rateLimitFile)) {
        $rateData = json_decode(file_get_contents($rateLimitFile), true);
    }

    $currentTime = time();

    // Entferne veraltete Einträge
    foreach ($rateData as $ipAddress => $data) {
        if ($data['timestamp'] + $rateLimitWindow < $currentTime) {
            unset($rateData[$ipAddress]);
        }
    }

    // Aktualisiere die Daten für die aktuelle IP
    if (!isset($rateData[$ip])) {
        $rateData[$ip] = ['count' => 1, 'timestamp' => $currentTime];
    } else {
        $rateData[$ip]['count'] += 1;
    }

    // Sperre die IP, wenn das Limit überschritten wurde
    if ($rateData[$ip]['count'] > $rateLimit) {
        file_put_contents($rateLimitFile, json_encode($rateData)); // Daten speichern
        return false; // Limit überschritten
    }

    file_put_contents($rateLimitFile, json_encode($rateData)); // Daten speichern
    return true;
}

// Hauptlogik
$visitorIP = getVisitorIP();
$requestURI = $_SERVER['REQUEST_URI'] ?? '/';

// Prüfen, ob die IP bereits gesperrt ist
if (isIPBanned($visitorIP)) {
    http_response_code(403);
    die($errorMessages['banned']);
}

// Häufigkeitsbegrenzung überprüfen
if (!checkRateLimit($visitorIP)) {
    http_response_code(429); // 429 Too Many Requests
    die($errorMessages['rate_limit']);
}

// Überprüfen auf verbotene Muster in der URI
foreach ($forbiddenPatterns as $pattern) {
    if (preg_match($pattern, $requestURI)) {
        // IP-Adresse sperren
        banIP($visitorIP, $banDuration);

        http_response_code(403);
        die($errorMessages['forbidden_pattern']);
    }
}

?>

Last edited by kleo (05.12.2024 13:42:01)

Offline

#6 03.12.2024 12:53:10

kleo
Member

Re: Access deny

Und noch eine Erweiterung.

Wenn der gesperrte Besucher innerhalb der Sperrzeit erneut die Webseite aufruft, wird die Sperrzeit neu gesetzt:

[== PHP ==]
<?php
// Einstellungen
$forbiddenPatterns = ['/adminer/', '/test123/', '/badword/']; // Verbotene Muster (Regex)
$banDuration = 10; // Sperrdauer in Sekunden (z. B. 60 = 1 Minute, 3600 = 1 Stunde)
$rateLimit = 3; // Maximale Anfragen pro Zeitfenster
$rateLimitWindow = 3; // Zeitfenster in Sekunden (z. B. 60 = 1 Minute, 3600 = 1 Stunde)

// Fehlermeldungen
$errorMessages = [
    'banned' => 'Zugriff verweigert.',
    'rate_limit' => 'Zu viele Anfragen. Bitte versuchen Sie es später erneut.',
    'forbidden_pattern' => 'Zugriff verweigert.'
];

// Dateien zur Speicherung von Sperren und Limits
$bannedIPsFile = 'banned_ips.json'; // Datei zur Speicherung der gesperrten IPs
$rateLimitFile = 'rate_limit.json'; // Datei zur Speicherung der Anfrage-Zähler

// Funktion, um die IP-Adresse des Besuchers zu ermitteln
function getVisitorIP() {
    return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}

// Funktion, um eine IP-Adresse zu sperren
function banIP($ip, $duration) {
    global $bannedIPsFile;

    $bannedIPs = [];
    if (file_exists($bannedIPsFile)) {
        $bannedIPs = json_decode(file_get_contents($bannedIPsFile), true);
    }

    // Setze die neue Sperrzeit
    $bannedIPs[$ip] = time() + $duration;
    file_put_contents($bannedIPsFile, json_encode($bannedIPs));
}

// Funktion, um zu prüfen, ob eine IP gesperrt ist und ggf. die Sperrzeit zu verlängern
function isIPBanned($ip) {
    global $bannedIPsFile, $banDuration;

    if (!file_exists($bannedIPsFile)) {
        return false;
    }

    $bannedIPs = json_decode(file_get_contents($bannedIPsFile), true);
    $currentTime = time();

    // Entferne abgelaufene Sperren
    foreach ($bannedIPs as $bannedIP => $expiry) {
        if ($expiry < $currentTime) {
            unset($bannedIPs[$bannedIP]);
        }
    }

    file_put_contents($bannedIPsFile, json_encode($bannedIPs));

    // Falls die IP gesperrt ist, verlängere die Sperrzeit
    if (isset($bannedIPs[$ip])) {
        banIP($ip, $banDuration); // Sperrzeit zurücksetzen
        return true;
    }

    return false;
}

// Funktion, um die Häufigkeitsbegrenzung zu überprüfen
function checkRateLimit($ip) {
    global $rateLimit, $rateLimitWindow, $rateLimitFile;

    $rateData = [];
    if (file_exists($rateLimitFile)) {
        $rateData = json_decode(file_get_contents($rateLimitFile), true);
    }

    $currentTime = time();

    // Entferne veraltete Einträge
    foreach ($rateData as $ipAddress => $data) {
        if ($data['timestamp'] + $rateLimitWindow < $currentTime) {
            unset($rateData[$ipAddress]);
        }
    }

    // Aktualisiere die Daten für die aktuelle IP
    if (!isset($rateData[$ip])) {
        $rateData[$ip] = ['count' => 1, 'timestamp' => $currentTime];
    } else {
        $rateData[$ip]['count'] += 1;
    }

    // Sperre die IP, wenn das Limit überschritten wurde
    if ($rateData[$ip]['count'] > $rateLimit) {
        file_put_contents($rateLimitFile, json_encode($rateData)); // Daten speichern
        return false; // Limit überschritten
    }

    file_put_contents($rateLimitFile, json_encode($rateData)); // Daten speichern
    return true;
}

// Hauptlogik
$visitorIP = getVisitorIP();
$requestURI = $_SERVER['REQUEST_URI'] ?? '/';

// Prüfen, ob die IP bereits gesperrt ist
if (isIPBanned($visitorIP)) {
    http_response_code(403);
    die($errorMessages['banned']);
}

// Häufigkeitsbegrenzung überprüfen
if (!checkRateLimit($visitorIP)) {
    http_response_code(429); // 429 Too Many Requests
    die($errorMessages['rate_limit']);
}

// Überprüfen auf verbotene Muster in der URI
foreach ($forbiddenPatterns as $pattern) {
    if (preg_match($pattern, $requestURI)) {
        // IP-Adresse sperren
        banIP($visitorIP, $banDuration);

        http_response_code(403);
        die($errorMessages['forbidden_pattern']);
    }
}

?>

Offline

#7 04.12.2024 11:46:10

berny
Member

Re: Access deny

Verbotenes Muster erkannt. Ihre IP-Adresse wurde gesperrt

Ich würde das "Verbotene Muster" einfach weglassen.

Offline

#8 04.12.2024 12:49:42

kleo
Member

Re: Access deny

Die Fehlermeldungen kann man frei definieren. Und wenn man will, auch in verschiedenen Sprachen.
Man kann auch ein Bild an der Stelle von Textmeldungen zeigen.

Es ist zu überlegen, ob durch die "Häufigkeitsbegrenzung" nicht die Suchmaschinen bei Ihrer Arbeit gehindert werden.
Ich habe keine Ahnung, wie schnell die Suchmaschinen-Crawler einzelne Aufrufe tätigen.
Vielleicht kann man da durch eine bestimmte Einstellung für maximale Anzahl der Anfragen pro Zeitfenster regeln.

Eine andere Überlegung:
die beiden Dateien banned_ips.json und rate_limit.json mittels .htaccess von fremden Ansehen zu schützen.

Am besten wäre vielleicht ein Modul daraus zu erstellen.
Ich glaube aber nicht, dass ich es alleine schaffe...

Offline

#9 04.12.2024 13:47:50

webbird
Administrator

Re: Access deny

Seriöse Suchmaschinen kann man erkennen und ausnehmen.


Ich habe eine Amazon-Wishlist. wink Oder spende an das Projekt.
Ich kann, wenn ich will, aber wer will, dass ich muss, kann mich mal

Offline

#10 05.12.2024 13:29:56

kleo
Member

Re: Access deny

Hi Webbird,

danke für den Tipp.

Ich habe weiter gebastellt und der Code mit der Suchmaschinen Erkennung ergänzt.

Hier ist es zum Testen:

[== PHP ==]
<?php
// Einstellungen
$forbiddenPatterns = ['/adminer/', '/test123/', '/badword/']; // Verbotene Muster (Regex)
$banDuration = 10; // Sperrdauer in Sekunden (z. B. 60 = 1 Minute, 3600 = 1 Stunde)
$rateLimit = 3; // Maximale Anfragen pro Zeitfenster
$rateLimitWindow = 1; // Zeitfenster in Sekunden (z. B. 60 = 1 Minute, 3600 = 1 Stunde)

// Fehlermeldungen
$errorMessages = [
    'banned' => 'Zugriff verweigert.',
    'rate_limit' => 'Zu viele Anfragen. Bitte versuchen Sie es später erneut.',
    'forbidden_pattern' => 'Zugriff verweigert.'
];

// Suchmaschinen-Roboter-User-Agents
$searchEngineBots = [
    'Googlebot',
    'Bingbot',
    'Slurp', // Yahoo
    'DuckDuckBot',
    'Baiduspider',
    'YandexBot',
    'Sogou',
    'Exabot',
    'facebot',
    'ia_archiver' // Alexa Crawler
];

// Dateien zur Speicherung von Sperren und Limits
$bannedIPsFile = 'banned_ips.json'; // Datei zur Speicherung der gesperrten IPs
$rateLimitFile = 'rate_limit.json'; // Datei zur Speicherung der Anfrage-Zähler

// Funktion, um die IP-Adresse des Besuchers zu ermitteln
function getVisitorIP() {
    return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}

// Funktion, um zu prüfen, ob der aktuelle User-Agent ein Suchmaschinen-Bot ist
function isSearchEngineBot($userAgent, $bots) {
    foreach ($bots as $bot) {
        if (stripos($userAgent, $bot) !== false) {
            return true;
        }
    }
    return false;
}

// Funktion, um eine IP-Adresse zu sperren
function banIP($ip, $duration) {
    global $bannedIPsFile;

    $bannedIPs = [];
    if (file_exists($bannedIPsFile)) {
        $bannedIPs = json_decode(file_get_contents($bannedIPsFile), true);
    }

    // Setze die neue Sperrzeit
    $bannedIPs[$ip] = time() + $duration;
    file_put_contents($bannedIPsFile, json_encode($bannedIPs));
}

// Funktion, um zu prüfen, ob eine IP gesperrt ist und ggf. die Sperrzeit zu verlängern
function isIPBanned($ip) {
    global $bannedIPsFile, $banDuration;

    if (!file_exists($bannedIPsFile)) {
        return false;
    }

    $bannedIPs = json_decode(file_get_contents($bannedIPsFile), true);
    $currentTime = time();

    // Entferne abgelaufene Sperren
    foreach ($bannedIPs as $bannedIP => $expiry) {
        if ($expiry < $currentTime) {
            unset($bannedIPs[$bannedIP]);
        }
    }

    file_put_contents($bannedIPsFile, json_encode($bannedIPs));

    // Falls die IP gesperrt ist, verlängere die Sperrzeit
    if (isset($bannedIPs[$ip])) {
        banIP($ip, $banDuration); // Sperrzeit zurücksetzen
        return true;
    }

    return false;
}

// Funktion, um die Häufigkeitsbegrenzung zu überprüfen
function checkRateLimit($ip) {
    global $rateLimit, $rateLimitWindow, $rateLimitFile;

    $rateData = [];
    if (file_exists($rateLimitFile)) {
        $rateData = json_decode(file_get_contents($rateLimitFile), true);
    }

    $currentTime = time();

    // Entferne veraltete Einträge
    foreach ($rateData as $ipAddress => $data) {
        if ($data['timestamp'] + $rateLimitWindow < $currentTime) {
            unset($rateData[$ipAddress]);
        }
    }

    // Aktualisiere die Daten für die aktuelle IP
    if (!isset($rateData[$ip])) {
        $rateData[$ip] = ['count' => 1, 'timestamp' => $currentTime];
    } else {
        $rateData[$ip]['count'] += 1;
    }

    // Sperre die IP, wenn das Limit überschritten wurde
    if ($rateData[$ip]['count'] > $rateLimit) {
        file_put_contents($rateLimitFile, json_encode($rateData)); // Daten speichern
        return false; // Limit überschritten
    }

    file_put_contents($rateLimitFile, json_encode($rateData)); // Daten speichern
    return true;
}

// Hauptlogik
$visitorIP = getVisitorIP();
$requestURI = $_SERVER['REQUEST_URI'] ?? '/';
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';

// Prüfen, ob der Besucher ein Suchmaschinen-Bot ist
if (isSearchEngineBot($userAgent, $searchEngineBots)) {
    echo "Suchmaschinen-Bot erkannt. Keine Begrenzung angewendet.";
    exit;
}

// Prüfen, ob die IP bereits gesperrt ist
if (isIPBanned($visitorIP)) {
    http_response_code(403);
    die($errorMessages['banned']);
}

// Häufigkeitsbegrenzung überprüfen
if (!checkRateLimit($visitorIP)) {
    http_response_code(429); // 429 Too Many Requests
    die($errorMessages['rate_limit']);
}

// Überprüfen auf verbotene Muster in der URI
foreach ($forbiddenPatterns as $pattern) {
    if (preg_match($pattern, $requestURI)) {
        // IP-Adresse sperren
        banIP($visitorIP, $banDuration);

        http_response_code(403);
        die($errorMessages['forbidden_pattern']);
    }
}

?>

Ich habe hier zum Testen die Sperrdauer auf 10 Sekunden eingestellt, normal würde ich sie wahrscheinlich mindestens auf 1 Stunde setzen.

Last edited by kleo (05.12.2024 13:41:10)

Offline

#11 10.12.2024 12:26:55

kleo
Member

Re: Access deny

Moin!

Ich teste momentan diese Lösung auf mehreren Webseiten und komme zu unterschiedlichen Ergebnissen.
Bei allen Webseiten ist die neuste WBCE Version 1.6.3 installiert, bei allen ist die PHP-Version 8+
Allerdings sind es verschiedene Hostinganbieter.

Bei einigen Hostinganbieter funktioniert es einwandfrei, genau, wie oben beschrieben.
Bei den Anderen ist es irgendwie komisch...

Z.B. bei Strato wird der Aufruf mit einem verbotenem Muster gesperrt, aber gleich danach eine existierende Seite weiterhin erlaubt.

Bei einem anderen Hostinganbieter sperrt die vorgeschlagene Lösung nicht nur alle Aufrufe mit verbotenen Mustern, sondern auch alle nicht existierende Seiten (wirkt in dem Fall wie eine 404-Seite).

Ich habe zuerst gedacht, dass es wahrscheinlich an den Zugriffsrechten für die beiden JOIN-Dateien liegt, und habe mit allen denkbaren Varianten bis zu 777 getestet: es liegt wahrscheinlich woanders…

Es wäre denkbar alle Einstellungen in der DB abzuspeichern um die Problematik mit Schreib- und Leserechten umzugehen.

Oder übersehe ich etwas?
Ist vielleicht die Hauptlogik irgendwie falsch?
Oder sind es möglicherweise irgendwelche Einstellungen in den .htaccess Dateien nötig (oder überflüssig)?

Offline

#12 10.12.2024 14:28:22

Slugger
Member

Re: Access deny

Da musst du wahrscheinlich mal die php-Module mit info.php vergleichen.

Oder über das Modul: System Information (sysinfo)

Last edited by Slugger (10.12.2024 15:17:49)


Hoster: ALL-INKL *** Grundsätzliche WBCE Konfig ***
WBCE: 1.5.4 • BE: 2.1.0 • PHP: 8.1.16 * 1. Projekt: FE: Simple responsive • BE: Argos * 2. Projekt: FE: hortal • BE: Argos * 3. Projekt: FE: WBCEZon • BE: Argos * 4. Projekt: FE: WBCETik • BE: Argos
Status Projekt 1-4:  OK

Offline

#13 16.12.2024 11:52:23

kleo
Member

Re: Access deny

Moin!

Ich habe an den Tagen weiter am Code getüftelt, es scheint jetzt zu funktionieren.

Es sind insgesamt fünf Dateien:

security_check.php  – die Haupdatei
banned_ips.jsonhier werden die provisorisch gesperrten IP-Adressen bei der Eingabe von verbotenen Muster gespeichert
rate_limit.jsonhier werden die provisorisch gesperrten IP-Adressen bei zu vielen Anfragen gespeichert
forbidden_patterns.txthier werden die verbotene Zeichenketten definiert
access_log.txthier werden alle gesperrten Aufrufe protokolliert

Hier ist die Hauptdatei security_check.php:

[== PHP ==]
<?php
declare(strict_types=1); // Aktiviert den strikten Typmodus

// Einstellungen
$banDuration = 7200; // Sperrdauer in Sekunden
$rateLimit = 3; // Maximale Anfragen pro Zeitfenster
$rateLimitWindow = 1; // Zeitfenster in Sekunden (z.B. 60 Sekunden)
$loggingEnabled = true; // Loggen: Ja = true/Nein = false
$logIP = false; // IP-Adresse in der Logdatei speichern: Ja = true/Nein = false
$logFile = __DIR__ . '/access_log.txt'; // Absoluter Pfad zur LOG-Datei
$maxLogFileSize = 1048576; // Maximale Größe der LOG-Datei in Bytes (1 MB)
$patternsFile = __DIR__ . '/forbidden_patterns.txt'; // Absoluter Pfad zur Datei mit verbotenen Mustern

// Fehlermeldungen
$errorMessages = [
    'banned' => 'Zugriff verweigert.',
    'rate_limit' => 'Zu viele Anfragen.',
    'forbidden_pattern' => 'Zugriff verweigert.'
];

// Suchmaschinen-Roboter-User-Agents
$searchEngineBots = [
    'Googlebot',
    'Bingbot',
    'Slurp', // Yahoo
    'DuckDuckBot',
    'Baiduspider',
    'YandexBot',
    'Sogou',
    'Exabot',
    'facebot',
    'ia_archiver' // Alexa Crawler
];

// Dateien zur Speicherung
$bannedIPsFile = __DIR__ . '/banned_ips.json'; // Absoluter Pfad zur Datei der gesperrten IPs
$rateLimitFile = __DIR__ . '/rate_limit.json'; // Absoluter Pfad zur Datei der Anfrage-Zähler

// Funktion, um die IP-Adresse des Besuchers zu ermitteln
function getVisitorIP(): string {
    return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}

// Funktion, um zu prüfen, ob der aktuelle User-Agent ein Suchmaschinen-Bot ist
function isSearchEngineBot(string $userAgent, array $bots): bool {
    foreach ($bots as $bot) {
        if (stripos($userAgent, $bot) !== false) {
            return true;
        }
    }
    return false;
}

// Funktion, um eine IP-Adresse zu sperren
function banIP(string $ip, int $duration, string $bannedIPsFile): void {
    try {
        $bannedIPs = [];
        if (file_exists($bannedIPsFile)) {
            $content = file_get_contents($bannedIPsFile);
            if ($content !== false) {
                $bannedIPs = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
            }
        }

        $bannedIPs[$ip] = time() + $duration;
        file_put_contents($bannedIPsFile, json_encode($bannedIPs, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
    } catch (JsonException $e) {
        error_log("JSON-Fehler in banIP: " . $e->getMessage());
    } catch (Exception $e) {
        error_log("Allgemeiner Fehler in banIP: " . $e->getMessage());
    }
}

// Funktion, um zu prüfen, ob eine IP gesperrt ist und ggf. die Sperrzeit zu verlängern
function isIPBanned(string $ip, string $bannedIPsFile, int $banDuration): bool {
    try {
        if (!file_exists($bannedIPsFile)) {
            return false;
        }

        $fileContent = file_get_contents($bannedIPsFile);
        $bannedIPs = $fileContent !== false 
            ? json_decode($fileContent, true, 512, JSON_THROW_ON_ERROR) 
            : [];

        if (!is_array($bannedIPs)) {
            $bannedIPs = []; // Fallback auf ein leeres Array, wenn JSON ungültig ist
        }

        $currentTime = time();

        // Entferne abgelaufene Sperren
        $bannedIPs = array_filter($bannedIPs, fn($expiry) => $expiry >= $currentTime);

        // Aktualisiere die Datei ohne abgelaufene Sperren
        file_put_contents($bannedIPsFile, json_encode($bannedIPs, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));

        if (isset($bannedIPs[$ip])) {
            // Sperrzeit verlängern
            banIP($ip, $banDuration, $bannedIPsFile);
            return true;
        }

        return false;
    } catch (JsonException $e) {
        error_log("JSON-Fehler in isIPBanned: " . $e->getMessage());
        return false;
    } catch (Exception $e) {
        error_log("Allgemeiner Fehler in isIPBanned: " . $e->getMessage());
        return false;
    }
}

// Funktion, um die Häufigkeitsbegrenzung zu überprüfen
function checkRateLimit(string $ip, int $rateLimit, int $rateLimitWindow, string $rateLimitFile): bool {
    try {
        $rateData = file_exists($rateLimitFile)
            ? json_decode(file_get_contents($rateLimitFile), true, 512, JSON_THROW_ON_ERROR)
            : [];

        $currentTime = time();

        // Entferne veraltete Einträge
        $rateData = array_filter($rateData, fn($data) => $data['timestamp'] + $rateLimitWindow >= $currentTime);

        if (!isset($rateData[$ip])) {
            $rateData[$ip] = ['count' => 1, 'timestamp' => $currentTime];
        } else {
            $rateData[$ip]['count'] += 1;
        }

        if ($rateData[$ip]['count'] > $rateLimit) {
            file_put_contents($rateLimitFile, json_encode($rateData, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
            return false;
        }

        file_put_contents($rateLimitFile, json_encode($rateData, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
        return true;
    } catch (JsonException $e) {
        error_log("JSON-Fehler in checkRateLimit: " . $e->getMessage());
        return true; // Im Fehlerfall erlauben, um nicht fälschlicherweise IPs zu blockieren
    } catch (Exception $e) {
        error_log("Allgemeiner Fehler in checkRateLimit: " . $e->getMessage());
        return true;
    }
}

// Funktion, um verbotene Muster aus einer Datei zu laden
function loadForbiddenPatterns(string $patternsFile): array {
    if (!file_exists($patternsFile)) {
        error_log("Verbotene Muster-Datei nicht gefunden: $patternsFile");
        return [];
    }

    $patterns = file($patternsFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    if ($patterns === false) {
        error_log("Fehler beim Lesen der verbotenen Muster-Datei: $patternsFile");
        return [];
    }

    // Entferne Zeilen, die mit # beginnen (Kommentare)
    $patterns = array_filter($patterns, fn($line) => strpos(trim($line), '#') !== 0);

    // Bereinige die Muster (z.B. zusätzliche Leerzeichen entfernen)
    return array_map('trim', $patterns);
}

// Funktion, um Logs zu speichern
function logAttempt(string $ip, string $uri, bool $loggingEnabled, bool $logIP, string $logFile, int $maxLogFileSize): void {
    if (!$loggingEnabled) {
        return;
    }

    try {
        // Überprüfen, ob die Log-Datei die maximale Größe erreicht hat
        if (file_exists($logFile) && filesize($logFile) >= $maxLogFileSize) {
            $lines = file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
            if ($lines === false) {
                error_log("Fehler beim Lesen der LOG-Datei: $logFile");
                return;
            }

            $half = (int)(count($lines) / 2); // Hälfte der Zeilen entfernen
            $lines = array_slice($lines, $half);
            file_put_contents($logFile, implode("\n", $lines) . "\n");
        }

        // Neuen Log-Eintrag hinzufügen
        $logEntry = sprintf(
            "[%s] %sURI: %s\n",
            date('Y-m-d H:i:s'),
            $logIP ? "IP: $ip, " : "",
            $uri
        );

        file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
    } catch (Exception $e) {
        error_log("Fehler beim Schreiben in die LOG-Datei: " . $e->getMessage());
    }
}

// Hauptlogik
$visitorIP = getVisitorIP();
$requestURI = $_SERVER['REQUEST_URI'] ?? '/';
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';

// 1. Prüfen, ob die IP bereits gesperrt ist
if (isIPBanned($visitorIP, $bannedIPsFile, $banDuration)) {
    logAttempt($visitorIP, $requestURI, $loggingEnabled, $logIP, $logFile, $maxLogFileSize);
    http_response_code(403);
    die($errorMessages['banned']);
}

// 2. Prüfen auf verbotene Muster in der URI
$forbiddenPatterns = loadForbiddenPatterns($patternsFile);
foreach ($forbiddenPatterns as $pattern) {
    if (preg_match($pattern, $requestURI)) {
        banIP($visitorIP, $banDuration, $bannedIPsFile);
        logAttempt($visitorIP, $requestURI, $loggingEnabled, $logIP, $logFile, $maxLogFileSize);
        http_response_code(403);
        die($errorMessages['forbidden_pattern']);
    }
}

// 3. Prüfen, ob der Besucher ein Suchmaschinen-Bot ist
$isBot = isSearchEngineBot($userAgent, $searchEngineBots);

// 4. Häufigkeitsbegrenzung überprüfen (nur für Nicht-Bots)
if (!$isBot) {
    if (!checkRateLimit($visitorIP, $rateLimit, $rateLimitWindow, $rateLimitFile)) {
        banIP($visitorIP, $banDuration, $bannedIPsFile);
        logAttempt($visitorIP, $requestURI, $loggingEnabled, $logIP, $logFile, $maxLogFileSize);
        http_response_code(429);
        die($errorMessages['rate_limit']);
    }
}

?>

Die verbotene Zeichenketten habe ich in eine separate Datei ausgegliedert:
forbidden_patterns.txt

Da habe ich einige Muster vordefiniert. Man kann die Muster gruppieren und mit einer Leerzeile zur besseren Übersicht auseinander halten. Kommentare kann man auch schreiben: die dafür vorgesehene Zeilen sollten # am Anfang haben.

Hier ist ein Beispiel der Datei forbidden_patterns.txt:

## Sicherheitskritische Muster

# Gegen Directory Traversal-Angriffe durch den Pfad ../ auf übergeordnete Verzeichnisse zuzugreifen:
/\.\.\//i

# Gegen Cross-Site Scripting (XSS)-Angriffen:
/<script>/i

# Gegen SQL-Injection-Angriffen:
/select.*from/i

# Gegen SQL-Injection mit dem UNION-Operator:
/union.*select/i


## Verbotene Muster (mit /i nicht case-sensitiv):
/badword1/i
/badword2/i
/badword3/i


# Muster zum Testen
/badword/

Beide Dateien banned_ips.json und rate_limit.json sollen beim Erstellen folgenden Inhalt haben:

[]

Die Datei access_log.txt wird einfach als leere Datei erstellt.

Um die vorgeschlagene Lösung zu nutzen sollen alle 5 Dateien im root-Verzeichnis der WBCE-Installation platziert werden. Man kann die Dateien auch in ein separates Verzeichnis platzieren, dafür müssen aber die Pfade zu einzelnen Dateien eingegeben werden.

Die empfohlene Zugriffsrechte für alle Dateien: 660

In der index.php  im root-Verzeichnis der WBCE-Installation gleich am Anfang im Code nach <?php folgende Zeile einfügen:

[== PHP ==]
include 'security_check.php'; // Schutz für alle Seiten

Zu den Einstellungen:

$banDuration = 7200; // Sperrdauer in Sekunden
$rateLimit = 3; // Maximale Anfragen pro Zeitfenster
$rateLimitWindow = 1; // Zeitfenster in Sekunden (z.B. 60 Sekunden)

Hier kann man die Meldungen definieren:

$errorMessages = [
    'banned' => 'Zugriff verweigert.',
    'rate_limit' => 'Zu viele Anfragen.',
    'forbidden_pattern' => 'Zugriff verweigert.'
];

In der Datei access_log.txt werden beim Bedarf alle alle gesperrten Aufrufe protokolliert. Dafür in der security_check.php bei den Einstellungen ganz oben

$loggingEnabled = true;

eingeben. Wenn die Protokollführung nicht erwünscht ist:

$loggingEnabled = false;

Für die Testzwecke ist hilfreich bei der  Protokollführung auch die IP-Adresse zu speichern:

$logIP = true;

Für den normalen Betrieb wegen der DSGVO sollte das aber deaktiviert werden:

$logIP = false;

Man sollte auch die maximale Größe der Logging-Datei eingeben:

$maxLogFileSize = 1048576; // Maximale Größe der LOG-Datei in Bytes (1 MB)

Kurz vor dem Erreichen der  Maximalgröße werden die ältesten 50% der Sätze gelöscht und so der Platz für die Einträge geräumt.

Zum Testen

Die Sperrdauer würde ich empfehlen auf  10-20 Sekunden einzustellen:

$banDuration = 20;

Dann ruft man die Webseite und überprüft, dass die Webseite einwandfrei funktioniert.
Dann gibt man in der Browseradressleiste zu der eingegebenen Adresse eine verbotene Zeichenkette, z.B.

www.IhreWebseite/badword

Ab jetzt soll jede Seite der Webseite gesperrt werden. Als Inhalt wird jetzt die bei der $errorMessages  Meldung gezeigt

Die Sperrdauer wird erneut bei jedem neuen Aufruf neu gesetzt.

Die Zeit für die eingegebene  Sperrdauer abwarten und erneut die Webseite aufrufen. Es soll jetzt alles wieder normal funktionieren.

Für die Prüfung der Häufigkeitsbegrenzung kann man auf ein Menüpnkt der Webseite mehrmals schnell klicken. Um es vereinfachen kann man bei den Einstellungen folgende Eingaben machen

$rateLimit = 3; 
$rateLimitWindow = 10;

Nach drei Aufrufen innerhalb weniger als 10 Sekunden wird die Webseite blockiert und die vordefinierte Meldung gezeigt.

Im Normalbetrieb kann man alle Meldungen auch leer machen, dann wird nur der weiße Bildschirm dargestellt. Oder irgendein Bild, wenn man will.


Ich würde mich freuen eure Meinung dazu zu bekommen, bzw, eure Testergebnise.

Offline

Liked by:

berny

Board footer

up