WBCE CMS – Way Better Content Editing.
You are not logged in.
Pages: 1
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
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
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
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
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
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
Verbotenes Muster erkannt. Ihre IP-Adresse wurde gesperrt
Ich würde das "Verbotene Muster" einfach weglassen.
Offline
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
Seriöse Suchmaschinen kann man erkennen und ausnehmen.
Ich habe eine Amazon-Wishlist. Oder spende an das Projekt.
Ich kann, wenn ich will, aber wer will, dass ich muss, kann mich mal
Offline
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
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
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
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.json – hier werden die provisorisch gesperrten IP-Adressen bei der Eingabe von verbotenen Muster gespeichert
rate_limit.json– hier werden die provisorisch gesperrten IP-Adressen bei zu vielen Anfragen gespeichert
forbidden_patterns.txt – hier werden die verbotene Zeichenketten definiert
access_log.txt – hier 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
berny
Pages: 1