Scripte hinzugefügt.

This commit is contained in:
2026-08-28 18:47:22 +02:00
parent f90abfbdc4
commit 5fc4aa46a6
4 changed files with 333 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
<?php
/**
* blitzbestaetigung_process.php
* Erkennt Bots, die einen Bestaetigungslink ungewoehnlich schnell nach der
* Anmeldung anklicken (z. B. automatisiertes Adress-Scanning bei fremden
* Organisationen). Kriterium: kurze Zeitspanne UND abweichende IP zwischen
* Anmeldung und Bestaetigung (siehe Wiki-Kapitel 7.2 fuer die Begruendung,
* warum reine Zeitspanne allein NICHT ausreicht).
*
* Verhalten bei Treffer:
* - Abonnent wird auf "unbestaetigt" zurueckgesetzt
* - IP wird zur Sperrliste vorgemerkt (Attribut "IP sperren")
* - NUR die IP wird an Stop Forum Spam gemeldet, NICHT die E-Mail-Adresse
* (koennte einem unbeteiligten Dritten gehoeren, siehe Wiki-Kapitel 7.4)
*
* Voraussetzungen:
* - phpList-Attribut "IP sperren" (Checkbox, siehe blocklist_update.php)
* - phpList-Attribut "Blitzbestaetigung - eher Bots" (Checkbox, Duplikatschutz)
*
* WICHTIG: Vor produktivem Einsatz zunaechst nur mit SELECT testen
* (siehe Wiki-Kapitel 7.5)!
*
* Aufruf: per Cron, z. B. stuendlich
*/
$dbHost = '127.0.0.1';
$dbName = 'DEINE_DB';
$dbUser = 'DEIN_DB_USER';
$dbPass = 'DEIN_DB_PASSWORT';
$sfsApiKey = 'DEIN_STOPFORUMSPAM_API_KEY';
$pdo = new PDO("mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4", $dbUser, $dbPass);
$attrIpBlockId = X; // ID des "IP sperren"-Attributs
$attrBlitzId = Y; // ID des "Blitzbestaetigung"-Attributs
$thresholdSeconds = 120; // < 2 Minuten zwischen Anmeldung und Bestaetigung = verdaechtig
$sql = "SELECT u.id, u.email,
sub.ip AS sub_ip, conf.ip AS conf_ip,
TIMESTAMPDIFF(SECOND, sub.date, conf.date) AS diff_seconds
FROM phplist_user_user u
JOIN phplist_user_user_history sub ON sub.userid = u.id AND sub.summary IN ('Subscription','Re-Subscription')
JOIN phplist_user_user_history conf ON conf.userid = u.id AND conf.summary = 'Confirmation'
LEFT JOIN phplist_user_user_attribute uab ON uab.userid = u.id AND uab.attributeid = :blitzId
WHERE u.confirmed = 1
AND TIMESTAMPDIFF(SECOND, sub.date, conf.date) BETWEEN 0 AND :threshold
AND sub.ip != conf.ip
AND (uab.value IS NULL OR uab.value = '')
GROUP BY u.id";
$stmt = $pdo->prepare($sql);
$stmt->execute(['blitzId' => $attrBlitzId, 'threshold' => $thresholdSeconds]);
$candidates = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($candidates as $c) {
// 1. Abonnent auf "unbestaetigt" zuruecksetzen
$reset = $pdo->prepare("UPDATE phplist_user_user SET confirmed = 0 WHERE id = :uid");
$reset->execute(['uid' => $c['id']]);
// 2. IP zur Sperrliste vormerken (nutzt bestehende Infrastruktur aus blocklist_update.php)
$blk = $pdo->prepare("INSERT INTO phplist_user_user_attribute (userid, attributeid, value)
VALUES (:uid, :aid, 'on')
ON DUPLICATE KEY UPDATE value = 'on'");
$blk->execute(['uid' => $c['id'], 'aid' => $attrIpBlockId]);
// 3. Nur die IP an SFS melden - NICHT die moeglicherweise fremde E-Mail-Adresse
$data = http_build_query([
'ip_addr' => $c['conf_ip'],
'username' => 'blitzbestaetigung-bot',
'api_key' => $sfsApiKey,
'evidence' => "Automatisiert erkannt: Bestaetigung nach nur {$c['diff_seconds']}s von abweichender IP (Anmelde-IP: {$c['sub_ip']})",
]);
$ch = curl_init('https://www.stopforumspam.com/add.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);
curl_close($ch);
// 4. Als bearbeitet markieren (Duplikatschutz)
$mark = $pdo->prepare("INSERT INTO phplist_user_user_attribute (userid, attributeid, value)
VALUES (:uid, :aid, 'on')
ON DUPLICATE KEY UPDATE value = 'on'");
$mark->execute(['uid' => $c['id'], 'aid' => $attrBlitzId]);
echo "Zurueckgesetzt & IP vorgemerkt: userid {$c['id']} ({$c['diff_seconds']}s, sub_ip: {$c['sub_ip']}, conf_ip: {$c['conf_ip']})\n";
sleep(1);
}
+72
View File
@@ -0,0 +1,72 @@
<?php
/**
* blocklist_update.php
* Pflegt eine lokale IP-Sperrliste (PHP-Datei), gespeist aus Honeypot-Treffern
* und/oder manueller Markierung ("IP sperren") in phpList.
* Die Sperrliste wird von config.php bei jedem Seitenaufruf ausgelesen
* (siehe Wiki-Kapitel 8.4 - Lese-Check).
*
* Voraussetzungen:
* - phpList-Attribut "Honeypot" (Text)
* - phpList-Attribut "IP sperren" (Checkbox, manuelle Markierung)
* - phpList-Attribut "IP bereits gesperrt" (Checkbox, Duplikatschutz)
*
* Aufruf: per Cron, z. B. alle 15 Minuten
*/
$dbHost = '127.0.0.1';
$dbName = 'DEINE_DB';
$dbUser = 'DEIN_DB_USER';
$dbPass = 'DEIN_DB_PASSWORT';
$pdo = new PDO("mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4", $dbUser, $dbPass);
$attrHoneypotId = X;
$attrIpBlockId = Y;
$attrBlockedId = Z;
$blocklistFile = '/pfad/zu/private/ip_blocklist.php';
$sql = "SELECT DISTINCT u.id
FROM phplist_user_user u
LEFT JOIN phplist_user_user_attribute uaip ON uaip.userid = u.id AND uaip.attributeid = :ipBlockId AND uaip.value = 'on'
LEFT JOIN phplist_user_user_attribute uah ON uah.userid = u.id AND uah.attributeid = :honeypotId AND uah.value != ''
LEFT JOIN phplist_user_user_attribute uab ON uab.userid = u.id AND uab.attributeid = :blockedId
WHERE (uaip.value = 'on' OR uah.value IS NOT NULL)
AND (uab.value IS NULL OR uab.value = '')";
$stmt = $pdo->prepare($sql);
$stmt->execute(['ipBlockId' => $attrIpBlockId, 'honeypotId' => $attrHoneypotId, 'blockedId' => $attrBlockedId]);
$candidates = $stmt->fetchAll(PDO::FETCH_COLUMN);
function getSubscriberIp($pdo, $userid) {
$stmt = $pdo->prepare("SELECT ip FROM phplist_user_user_history
WHERE userid = :uid AND ip IS NOT NULL AND ip != ''
ORDER BY date ASC LIMIT 1");
$stmt->execute(['uid' => $userid]);
return $stmt->fetchColumn();
}
$currentBlocklist = file_exists($blocklistFile) ? include $blocklistFile : [];
$newlyBlocked = [];
foreach ($candidates as $userid) {
$ip = getSubscriberIp($pdo, $userid);
if ($ip && !in_array($ip, $currentBlocklist, true)) {
$currentBlocklist[] = $ip;
$newlyBlocked[] = $ip;
}
$upd = $pdo->prepare("INSERT INTO phplist_user_user_attribute (userid, attributeid, value)
VALUES (:uid, :aid, 'on')
ON DUPLICATE KEY UPDATE value = 'on'");
$upd->execute(['uid' => $userid, 'aid' => $attrBlockedId]);
}
if (!empty($newlyBlocked)) {
$currentBlocklist = array_values(array_unique($currentBlocklist));
$content = "<?php\nreturn " . var_export($currentBlocklist, true) . ";\n";
file_put_contents($blocklistFile, $content, LOCK_EX);
echo "Neu gesperrt: " . implode(', ', $newlyBlocked) . "\n";
} else {
echo "Keine neuen IPs zu sperren.\n";
}
+93
View File
@@ -0,0 +1,93 @@
<?php
/**
* config_snippets.php
*
* Sammlung aller Ergaenzungen fuer die phpList config.php.
* Dies ist KEIN eigenstaendig lauffaehiges Skript, sondern eine
* Referenzsammlung - die einzelnen Bloecke werden in die produktive
* config.php uebernommen, nicht diese Datei selbst eingebunden.
*
* Reihenfolge in diesem Dokument = empfohlene Reihenfolge beim Einfuegen.
*/
// ============================================================
// 1) Server-seitiger Basis-Spamschutz (Wiki-Kapitel 4.1)
// ============================================================
define('USE_SPAM_BLOCK', 1);
define('NOTIFY_SPAM', 1);
// ============================================================
// 2) Bounce-Postfach-Konfiguration (Wiki-Kapitel 6.3)
// ============================================================
$bounce_mailbox_host = 'DEIN-SERVER';
$bounce_mailbox_user = 'bounces@nl.DEINE-DOMAIN.de';
$bounce_mailbox_password = 'DEIN_PASSWORT';
$bounce_mailbox_port = "995/pop3/ssl/novalidate-cert";
$bounce_mailbox_purge = 1;
$bounce_mailbox_purge_unprocessed = 1;
$bounce_unsubscribe_threshold = 5;
// Wichtig: Envelope-Absender auf die Bounce-Adresse setzen,
// sonst laufen Bounces weiterhin beim persoenlichen Postfach auf
$message_envelope = 'bounces@nl.DEINE-DOMAIN.de';
// ============================================================
// 3) Remote Processing Secret fuer Bounce-Cron (Wiki-Kapitel 6.6)
// Generieren mit: openssl rand -hex 20
// WICHTIG: Muss als $GLOBALS['config'][...] gesetzt werden,
// eine einfache Variable wird von getConfig() NICHT erkannt!
// ============================================================
$GLOBALS['config']['remote_processing_secret'] = 'DEIN_GENERIERTES_SECRET';
// ============================================================
// 4) IP-Sperrliste: Lese-Check (Wiki-Kapitel 8.4)
// Wird von blocklist_update.php befuellt.
// ============================================================
$blocklistFile = '/pfad/zu/private/ip_blocklist.php';
if (file_exists($blocklistFile)) {
$blocked_ips = include $blocklistFile;
$remote_ip = $_SERVER['REMOTE_ADDR'] ?? '';
if (in_array($remote_ip, $blocked_ips, true)) {
header('HTTP/1.1 403 Forbidden');
exit('Access denied.');
}
}
// ============================================================
// 5) Subscription-Bombing-Schutz (Wiki-Kapitel 9.2)
// Rate-Limiting pro Zieladresse statt pro IP - wirksam gegen
// Angreifer, die dieselbe (oft fremde) Zieladresse ueber
// wechselnde IPs wiederholt einreichen.
// ============================================================
if (isset($_GET['p']) && $_GET['p'] === 'subscribe' && !empty($_POST['email'])) {
$targetEmail = trim($_POST['email']);
try {
$bpdo = new PDO(
"mysql:host=127.0.0.1;dbname=DEINE_DB;charset=utf8mb4",
'DEIN_DB_USER',
'DEIN_DB_PASSWORT'
);
$stmt = $bpdo->prepare("
SELECT COUNT(*) FROM phplist_user_user_history h
JOIN phplist_user_user u ON u.id = h.userid
WHERE u.email = :email
AND h.summary IN ('Subscription', 'Re-Subscription')
AND h.date >= (NOW() - INTERVAL 24 HOUR)
");
$stmt->execute(['email' => $targetEmail]);
$recentAttempts = (int) $stmt->fetchColumn();
if ($recentAttempts >= 2) {
header('HTTP/1.1 429 Too Many Requests');
exit('Please wait before trying again.');
}
} catch (Exception $e) {
// Bei DB-Fehler nicht blockieren, normal weiterlaufen lassen
}
}
+80
View File
@@ -0,0 +1,80 @@
<?php
/**
* spam_report.php
* Meldet Honeypot-Treffer (verstecktes Formularfeld befuellt) automatisiert
* an Stop Forum Spam und sperrt den Abonnenten in phpList.
*
* Voraussetzungen:
* - phpList-Attribut "Honeypot" (Text) - ID unten eintragen
* - phpList-Attribut "Als Spam bereits gemeldet" (Checkbox) - ID unten eintragen
* - Stop-Forum-Spam-Account + API-Key
*
* Aufruf: per Cron, z. B. taeglich nachts
*/
$dbHost = '127.0.0.1'; // ggf. TCP statt Socket noetig
$dbName = 'DEINE_DB';
$dbUser = 'DEIN_DB_USER';
$dbPass = 'DEIN_DB_PASSWORT';
$sfsApiKey = 'DEIN_STOPFORUMSPAM_API_KEY';
$pdo = new PDO("mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4", $dbUser, $dbPass);
$attrHoneypotId = X; // ID deines Honeypot-Attributs
$attrReportedId = Y; // ID deines "Bereits gemeldet"-Attributs
$sql = "SELECT u.id, u.email
FROM phplist_user_user u
JOIN phplist_user_user_attribute uah ON uah.userid = u.id AND uah.attributeid = :honeypotId AND uah.value != ''
LEFT JOIN phplist_user_user_attribute uar ON uar.userid = u.id AND uar.attributeid = :reportedId
WHERE (uar.value IS NULL OR uar.value = '')";
$stmt = $pdo->prepare($sql);
$stmt->execute(['honeypotId' => $attrHoneypotId, 'reportedId' => $attrReportedId]);
$subscribers = $stmt->fetchAll(PDO::FETCH_ASSOC);
function getSubscriberIp($pdo, $userid) {
$stmt = $pdo->prepare("SELECT ip FROM phplist_user_user_history
WHERE userid = :uid AND ip IS NOT NULL AND ip != ''
ORDER BY date ASC LIMIT 1");
$stmt->execute(['uid' => $userid]);
return $stmt->fetchColumn();
}
foreach ($subscribers as $sub) {
$botIp = getSubscriberIp($pdo, $sub['id']);
if (!$botIp) {
echo "Uebersprungen (keine IP in History): {$sub['email']}\n";
continue;
}
$data = http_build_query([
'email' => $sub['email'],
'ip_addr' => $botIp,
'username' => $sub['email'],
'api_key' => $sfsApiKey,
'evidence' => 'Automatisch erkannt: Honeypot-Feld auf phpList-Anmeldeseite befuellt',
]);
$ch = curl_init('https://www.stopforumspam.com/add.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
if (strpos($response, 'success') !== false) {
$upd = $pdo->prepare("INSERT INTO phplist_user_user_attribute (userid, attributeid, value)
VALUES (:uid, :aid, 'on')
ON DUPLICATE KEY UPDATE value = 'on'");
$upd->execute(['uid' => $sub['id'], 'aid' => $attrReportedId]);
$blk = $pdo->prepare("UPDATE phplist_user_user SET blacklisted = 1 WHERE id = :uid");
$blk->execute(['uid' => $sub['id']]);
echo "Gemeldet & geblacklistet: {$sub['email']} (IP: $botIp)\n";
} else {
echo "Fehler bei: {$sub['email']} - $response\n";
}
sleep(1);
}