Scripte hinzugefügt.
This commit is contained in:
@@ -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);
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user