Sperrliste
Auffällige IP-Adressen, zum Abrufen für Ihre eigene Firewall.
Worum es geht
Wer eine Webseite betreibt, sieht dieselben Angreifer wie alle anderen: dieselben Scanner, dieselben Anmeldeversuche in Serie, dieselben Formularspammer. Bisher sperrt jeder sie für sich allein und fängt wieder von vorn an.
Diese Liste sammelt, was bei uns und bei anderen auffällt. Eine Adresse kommt darauf, wenn 3 unabhängige Melder sie nennen oder wenn unsere eigene Schutzschicht sie gesperrt hat. Mehrfache Meldungen desselben Melders zählen einmal.
- 282 Adressen stehen derzeit auf der Liste
- 0 Meldungen von 0 Meldern
- Abrufen ist frei und ohne Anmeldung. Melden braucht einen Schlüssel.
Abrufen
Kein Schlüssel nötig. Die Liste wird fünf Minuten zwischengespeichert; öfter als alle paar Minuten abzurufen bringt nichts.
Die ganze Liste
curl -s https://meinebrd.global/api/sperrliste/v1/liste.txt
Eine Adresse je Zeile, Kommentarzeilen beginnen mit #. Genau so, dass die üblichen Werkzeuge es ohne Umweg einlesen.
Eine einzelne Adresse nachschlagen
curl -s "https://meinebrd.global/api/sperrliste/v1/pruefen?ip=203.0.113.10"
{
"ok": true,
"ip": "203.0.113.10",
"gesperrt": true,
"meldungen": 4,
"punkte": 11,
"grund": "Anmeldeversuche in Serie (3), Abtasten nach Lücken (1)",
"quelle": "meldung",
"seit": "2026-09-14 08:21:03"
}
Dazu gibt es https://meinebrd.global/api/sperrliste/v1/liste.json mit Begründung je Eintrag und https://meinebrd.global/api/sperrliste/v1/zahlen für den Stand.
Einbauen
Linux: Firewall mit nftables
Ein Satz Adressen, den die Firewall kennt, und ein Auftrag, der ihn stündlich auffrischt.
# Einmalig: Satz und Regel anlegen
nft add table inet filter
nft add set inet filter sperrliste '{ type ipv4_addr; flags interval; }'
nft add rule inet filter input ip saddr @sperrliste drop
# /usr/local/bin/sperrliste-holen
#!/bin/bash
set -euo pipefail
LISTE=$(mktemp)
curl -fsS --max-time 60 https://meinebrd.global/api/sperrliste/v1/liste.txt \
| grep -vE '^#|^$' > "$LISTE"
# Erst prüfen, dann tauschen: Eine leere Antwort darf den Satz nicht leeren.
test -s "$LISTE"
nft flush set inet filter sperrliste
while read -r ip; do nft add element inet filter sperrliste "{ $ip }"; done < "$LISTE"
rm -f "$LISTE"
# Stündlich, mit etwas Versatz damit nicht alle zur vollen Stunde anfragen
17 * * * * root /usr/local/bin/sperrliste-holen
Linux: ipset und iptables
ipset create sperrliste hash:ip -exist
iptables -I INPUT -m set --match-set sperrliste src -j DROP
curl -fsS https://meinebrd.global/api/sperrliste/v1/liste.txt | grep -vE '^#|^$' | while read -r ip; do
ipset add sperrliste "$ip" -exist
done
fail2ban
Als zusätzliche Quelle neben den eigenen Regeln.
# /etc/fail2ban/action.d/sperrliste.conf ist nicht nötig:
# Die Liste kommt fertig, sie muss nur eingespielt werden.
# /etc/cron.hourly/sperrliste
#!/bin/sh
curl -fsS https://meinebrd.global/api/sperrliste/v1/liste.txt | grep -vE '^#|^$' | while read -r ip; do
fail2ban-client set sshd banip "$ip" > /dev/null 2>&1
done
nginx
# Erzeugt /etc/nginx/sperrliste.conf, danach nginx -s reload
curl -fsS https://meinebrd.global/api/sperrliste/v1/liste.txt \
| grep -vE '^#|^$' \
| sed 's/^/deny /; s/$/;/' > /etc/nginx/sperrliste.conf
# In der Serverkonfiguration:
# include /etc/nginx/sperrliste.conf;
Apache
curl -fsS https://meinebrd.global/api/sperrliste/v1/liste.txt \
| grep -vE '^#|^$' \
| sed 's/^/ Require not ip /' > /etc/apache2/sperrliste.conf
# In der Konfiguration:
# <RequireAll>
# Require all granted
# Include /etc/apache2/sperrliste.conf
# </RequireAll>
In der eigenen Anwendung
PHP
<?php
/**
* Nachschlagen, ob eine Adresse auf der Sperrliste steht.
*
* Das Ergebnis wird zwischengespeichert: Bei jedem Seitenaufruf zu
* fragen macht die eigene Seite so langsam wie die fremde, und wenn
* die fremde ausfaellt, faellt die eigene mit aus.
*/
function sperrlisteFragt(string $ip): bool
{
$datei = sys_get_temp_dir() . '/sperrliste_' . md5($ip) . '.json';
if (is_readable($datei) && time() - filemtime($datei) < 3600) {
$stand = json_decode(file_get_contents($datei), true);
return !empty($stand['gesperrt']);
}
$ctx = stream_context_create(['http' => ['timeout' => 3]]);
$roh = @file_get_contents(
'https://meinebrd.global/api/sperrliste/v1/pruefen?ip=' . urlencode($ip), false, $ctx);
// Im Zweifel durchlassen. Eine Sperrliste, die bei Stoerung die
// eigene Seite dichtmacht, ist schlimmer als der Angreifer.
if ($roh === false) {
return false;
}
file_put_contents($datei, $roh);
$stand = json_decode($roh, true);
return !empty($stand['gesperrt']);
}
if (sperrlisteFragt($_SERVER['REMOTE_ADDR'])) {
http_response_code(403);
exit('Zugriff nicht moeglich.');
}
Java
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Sperrliste {
private static final String BASIS = "https://meinebrd.global/api/sperrliste/v1";
private static final HttpClient CLIENT = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(3))
.build();
// Gemerkte Antworten, damit nicht jeder Aufruf ueber die Leitung geht.
private static final Map<String, Eintrag> GEMERKT = new ConcurrentHashMap<>();
private record Eintrag(boolean gesperrt, long bis) {}
public static boolean gesperrt(String ip) {
Eintrag e = GEMERKT.get(ip);
if (e != null && e.bis() > System.currentTimeMillis()) {
return e.gesperrt();
}
try {
HttpRequest anfrage = HttpRequest.newBuilder()
.uri(URI.create(BASIS + "/pruefen?ip=" + ip))
.timeout(Duration.ofSeconds(3))
.GET()
.build();
String rumpf = CLIENT.send(anfrage, HttpResponse.BodyHandlers.ofString()).body();
boolean treffer = rumpf.contains("\"gesperrt\":true");
GEMERKT.put(ip, new Eintrag(treffer, System.currentTimeMillis() + 3_600_000));
return treffer;
} catch (Exception ex) {
// Im Zweifel durchlassen: Eine Stoerung dort darf hier
// niemanden aussperren.
return false;
}
}
}
Python
import time
import urllib.request
import urllib.parse
import json
BASIS = "https://meinebrd.global/api/sperrliste/v1"
_gemerkt: dict[str, tuple[bool, float]] = {}
def gesperrt(ip: str) -> bool:
"""Steht die Adresse auf der Sperrliste?
Gemerkt fuer eine Stunde. Bei Stoerung wird durchgelassen: Eine
Sperrliste, die bei Ausfall die eigene Seite dichtmacht, ist
schlimmer als der Angreifer.
"""
stand = _gemerkt.get(ip)
if stand and stand[1] > time.time():
return stand[0]
try:
ziel = f"{BASIS}/pruefen?ip={urllib.parse.quote(ip)}"
with urllib.request.urlopen(ziel, timeout=3) as antwort:
daten = json.load(antwort)
treffer = bool(daten.get("gesperrt"))
except Exception:
return False
_gemerkt[ip] = (treffer, time.time() + 3600)
return treffer
Node.js, als Express-Zwischenschicht
const BASIS = 'https://meinebrd.global/api/sperrliste/v1';
const gemerkt = new Map();
async function gesperrt(ip) {
const stand = gemerkt.get(ip);
if (stand && stand.bis > Date.now()) return stand.treffer;
try {
const antwort = await fetch(`${BASIS}/pruefen?ip=${encodeURIComponent(ip)}`,
{ signal: AbortSignal.timeout(3000) });
const daten = await antwort.json();
gemerkt.set(ip, { treffer: Boolean(daten.gesperrt), bis: Date.now() + 3600000 });
return Boolean(daten.gesperrt);
} catch {
// Im Zweifel durchlassen.
return false;
}
}
app.use(async (req, res, next) => {
if (await gesperrt(req.ip)) return res.status(403).send('Zugriff nicht möglich.');
return next();
});
Melden
Melden geht nur mit einem Schlüssel, und den gibt es auf Antrag. Eine Liste, in die jeder ungefragt eintragen kann, ist wertlos: Ein einziger Verärgerter würde genügen, um eine fremde Adresse öffentlich zu brandmarken.
Deshalb prüfen wir jeden Antrag von Hand, und deshalb kommt eine Adresse erst auf die Liste, wenn 3 unabhängige Melder sie nennen.
curl -s -X POST https://meinebrd.global/api/sperrliste/v1/melden \
-H "Authorization: Bearer sl_IHR_SCHLUESSEL" \
-H "Content-Type: application/json" \
-d '{
"ip": "203.0.113.10",
"grund": "anmeldeversuche",
"notiz": "40 Versuche auf /wp-login.php in zwei Minuten"
}'
Der Schlüssel gehört in den Kopf der Anfrage und nie in den Adressteil: Was dort steht, landet im Protokoll jedes Servers dazwischen.
Mögliche Gründe
- anmeldeversuche – Anmeldeversuche in Serie
- scan – Abtasten nach Lücken
- formularspam – Formularspam
- werbemuell – Werbemüll
- schadsoftware – Schadsoftware
- missbrauch – Missbrauch
- sonstiges – Sonstiges
Schlüssel beantragen
Wir sehen uns jeden Antrag an. Sagen Sie kurz, wofür Sie melden wollen; daran entscheidet sich die Freigabe. Den Schlüssel bekommen Sie danach per E-Mail, und zwar genau einmal.
Zu Unrecht auf der Liste?
Das kommt vor. Hinter einer Adresse kann ein Anbieter stehen, hinter dem tausend unbeteiligte Anschlüsse hängen, oder jemand hat einen befallenen Rechner wieder sauber. Schreiben Sie uns mit der Adresse, dann sehen wir nach und nehmen sie herunter. Freigegebene Adressen kommen auch durch neue Meldungen nicht wieder darauf.
Zu jedem Eintrag halten wir fest, warum er dasteht, und geben das auf Nachfrage heraus. Eine IP-Adresse kann ein personenbezogenes Datum sein; Einzelheiten stehen in der Datenschutzerklärung.