ValidationHandler: Namensmuster-Filter und Project Honey Pot als zusaetzliche Ablehnungsgruende
This commit is contained in:
@@ -27,6 +27,11 @@ use Plugin\fp_altcha_spamschutz\src\Service\AltchaService;
|
|||||||
* Verhalten zu verlassen, wird hier bewusst derselbe frühe, hook-unabhängige Ansatz wie bei der
|
* Verhalten zu verlassen, wird hier bewusst derselbe frühe, hook-unabhängige Ansatz wie bei der
|
||||||
* Newsletter-Anmeldung verwendet: direkte Prüfung von $_POST, noch bevor JTL-Shop das Formular
|
* Newsletter-Anmeldung verwendet: direkte Prüfung von $_POST, noch bevor JTL-Shop das Formular
|
||||||
* verarbeitet (siehe guardContactSubmission()).
|
* verarbeitet (siehe guardContactSubmission()).
|
||||||
|
*
|
||||||
|
* Zusätzlich zur ALTCHA-Prüfung gibt es zwei weitere, unabhängige Ablehnungsgründe (siehe
|
||||||
|
* matchesBlockedPattern() und isFlaggedByHoneypot()): beide fangen gezielt Bots ab, die zwar
|
||||||
|
* JavaScript ausführen und die Rechenaufgabe lösen können, sich aber an einem bekannten
|
||||||
|
* Namensmuster oder einer bereits einschlägig aufgefallenen IP-Adresse erkennen lassen.
|
||||||
*/
|
*/
|
||||||
class ValidationHandler
|
class ValidationHandler
|
||||||
{
|
{
|
||||||
@@ -47,10 +52,16 @@ class ValidationHandler
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->altchaService->verifyPost()) {
|
$blockedByAltcha = !$this->altchaService->verifyPost();
|
||||||
|
$blockedByPattern = $this->matchesBlockedPattern();
|
||||||
|
$blockedByHoneypot = $this->isFlaggedByHoneypot();
|
||||||
|
|
||||||
|
if (!$blockedByAltcha && !$blockedByPattern && !$blockedByHoneypot) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->logRejection('Registrierung', $blockedByAltcha, $blockedByPattern, $blockedByHoneypot);
|
||||||
|
|
||||||
if (isset($args['fehlendeAngaben']) && \is_array($args['fehlendeAngaben'])) {
|
if (isset($args['fehlendeAngaben']) && \is_array($args['fehlendeAngaben'])) {
|
||||||
$args['fehlendeAngaben'][] = 'Sicherheitsprüfung';
|
$args['fehlendeAngaben'][] = 'Sicherheitsprüfung';
|
||||||
}
|
}
|
||||||
@@ -87,10 +98,16 @@ class ValidationHandler
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->altchaService->verifyPost()) {
|
$blockedByAltcha = !$this->altchaService->verifyPost();
|
||||||
|
$blockedByPattern = $this->matchesBlockedPattern();
|
||||||
|
$blockedByHoneypot = $this->isFlaggedByHoneypot();
|
||||||
|
|
||||||
|
if (!$blockedByAltcha && !$blockedByPattern && !$blockedByHoneypot) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->logRejection('Newsletter-Anmeldung', $blockedByAltcha, $blockedByPattern, $blockedByHoneypot);
|
||||||
|
|
||||||
// Feld entfernen, damit JTL-Shop die Anmeldung nicht als "abonnieren"-Request erkennt
|
// Feld entfernen, damit JTL-Shop die Anmeldung nicht als "abonnieren"-Request erkennt
|
||||||
// und stattdessen nur das (leere) Formular erneut anzeigt.
|
// und stattdessen nur das (leere) Formular erneut anzeigt.
|
||||||
unset($_POST['abonnieren']);
|
unset($_POST['abonnieren']);
|
||||||
@@ -123,10 +140,16 @@ class ValidationHandler
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->altchaService->verifyPost()) {
|
$blockedByAltcha = !$this->altchaService->verifyPost();
|
||||||
|
$blockedByPattern = $this->matchesBlockedPattern();
|
||||||
|
$blockedByHoneypot = $this->isFlaggedByHoneypot();
|
||||||
|
|
||||||
|
if (!$blockedByAltcha && !$blockedByPattern && !$blockedByHoneypot) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->logRejection('Kontaktformular', $blockedByAltcha, $blockedByPattern, $blockedByHoneypot);
|
||||||
|
|
||||||
// Pflichtfeld leeren, damit JTL-Shops eigene Prüfung ("Pflichtfeld nicht ausgefüllt")
|
// Pflichtfeld leeren, damit JTL-Shops eigene Prüfung ("Pflichtfeld nicht ausgefüllt")
|
||||||
// die Anfrage abweist -- der Besucher bekommt die normale, vertraute Fehlermeldung.
|
// die Anfrage abweist -- der Besucher bekommt die normale, vertraute Fehlermeldung.
|
||||||
unset($_POST['nachricht']);
|
unset($_POST['nachricht']);
|
||||||
@@ -165,4 +188,103 @@ class ValidationHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft Vorname, Nachname, Ort und Straße aus dem aktuellen POST-Request gegen die in den
|
||||||
|
* Plugin-Einstellungen hinterlegte Liste bekannter Bot-Namensmuster (z. B. "Test", "Test
|
||||||
|
* User"). Verglichen wird der komplette, getrimmte Feldwert ohne Beachtung von Groß-/
|
||||||
|
* Kleinschreibung -- absichtlich kein Teilstring-Vergleich, damit z. B. eine echte Straße
|
||||||
|
* oder ein echter Ort, der zufällig den Begriff enthält, nicht fälschlich blockiert wird.
|
||||||
|
*/
|
||||||
|
private function matchesBlockedPattern(): bool
|
||||||
|
{
|
||||||
|
$patterns = $this->altchaService->getNameBlocklist();
|
||||||
|
if ($patterns === []) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (['vorname', 'nachname', 'ort', 'strasse'] as $field) {
|
||||||
|
$value = isset($_POST[$field]) && \is_string($_POST[$field]) ? \trim($_POST[$field]) : '';
|
||||||
|
if ($value === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($patterns as $pattern) {
|
||||||
|
if (\mb_strtolower($value) === \mb_strtolower($pattern)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragt Project Honey Pot (http:BL) fuer die IP-Adresse des aktuellen Besuchers ab, sofern
|
||||||
|
* in den Einstellungen ein Zugangsschluessel hinterlegt ist. http:BL unterstuetzt nur IPv4;
|
||||||
|
* bei IPv6-Adressen oder wenn die Abfrage fehlschlaegt, wird sicherheitshalber NICHT
|
||||||
|
* blockiert (lieber ein Bot mehr durchlassen als echte Kunden faelschlich abweisen).
|
||||||
|
*
|
||||||
|
* Wichtig: es wird bewusst nur $_SERVER['REMOTE_ADDR'] verwendet, nicht z. B.
|
||||||
|
* X-Forwarded-For -- dieser Header liesse sich von einem Bot beliebig faelschen und wuerde
|
||||||
|
* die Pruefung wirkungslos machen. Laeuft der Shop hinter einem Reverse-Proxy, der die echte
|
||||||
|
* Besucher-IP nicht in REMOTE_ADDR durchreicht, liefert diese Pruefung keine sinnvollen
|
||||||
|
* Ergebnisse mehr.
|
||||||
|
*/
|
||||||
|
private function isFlaggedByHoneypot(): bool
|
||||||
|
{
|
||||||
|
if (!$this->altchaService->isHoneypotEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
|
||||||
|
if (!\is_string($ip) || \filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4) === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$reversedIp = \implode('.', \array_reverse(\explode('.', $ip)));
|
||||||
|
$accessKey = $this->altchaService->getHoneypotApiKey();
|
||||||
|
$query = $accessKey . '.' . $reversedIp . '.dnsbl.httpbl.org';
|
||||||
|
|
||||||
|
$records = @\dns_get_record($query, \DNS_A);
|
||||||
|
if (!\is_array($records) || $records === [] || !isset($records[0]['ip'])) {
|
||||||
|
// Keine Antwort/kein Eintrag -- IP ist bei http:BL nicht (mehr) bekannt.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = \explode('.', (string) $records[0]['ip']);
|
||||||
|
if (\count($parts) !== 4 || $parts[0] !== '127') {
|
||||||
|
// Unerwartetes Antwortformat -- sicherheitshalber nicht blocken.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$threatScore = (int) $parts[2];
|
||||||
|
$type = (int) $parts[3];
|
||||||
|
|
||||||
|
if ($type === 0) {
|
||||||
|
// Typ 0 = ausschliesslich als Suchmaschine gelistet, kein Ablehnungsgrund.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $threatScore >= $this->altchaService->getHoneypotThreshold();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function logRejection(
|
||||||
|
string $formular,
|
||||||
|
bool $blockedByAltcha,
|
||||||
|
bool $blockedByPattern,
|
||||||
|
bool $blockedByHoneypot
|
||||||
|
): void {
|
||||||
|
if (!$this->altchaService->isDebug()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log(\sprintf(
|
||||||
|
'[fp_altcha_spamschutz] %s abgelehnt (altcha=%s, namensmuster=%s, honeypot=%s)',
|
||||||
|
$formular,
|
||||||
|
$blockedByAltcha ? 'ja' : 'nein',
|
||||||
|
$blockedByPattern ? 'ja' : 'nein',
|
||||||
|
$blockedByHoneypot ? 'ja' : 'nein'
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user