Schwarze Liste von IP-Adressen auf Ihrer Flask -Website, die auf Linux läuft
Manchmal möchten Sie IP-Adressen sofort blockieren. Dieser Beitrag beschreibt eine Methode, wie Sie dies tun können.
Sie haben eine Website und sie funktioniert gut. Aber Sie stellen fest, dass bestimmte Besucher versuchen, mit Ihren Formularen herumzuspielen. Sie kommen von bestimmten IP-Adressen. Dann gibt es auch Bots, die Ihre Website scannen. Einige sind notwendig, aber andere sollten wegbleiben. Hassen Sie das nicht auch? Doch, ich hasse es. In der Vergangenheit habe ich einmal ein Modul geschrieben, das eine nicht so nette Antwort sehr langsam, Byte für Byte, zurückgab und ihre Systeme verlangsamte. Oder eine nicht enden wollende Menge an Daten zurückgab. Aber das ist eine andere Geschichte.
Vorerst möchte ich mich auf eine andere Methode konzentrieren: die Blockierung dieser Anträge. Geben Sie einfach ein HTTP 403 Forbidden zurück. Ich möchte in der Lage sein, dies on-the-fly von meinem Website-Administrationsbereich aus zu tun. Dort geben wir die IP-Adressen oder den Bereich von IP-Adressen an, die wir blockieren wollen. Es gibt auch andere Möglichkeiten dazu, wie die Verwendung von .htaccess -Dateien und Web-Servereinstellungen. Ich werde sie am Ende dieses Beitrags erwähnen.
Mehrere Gründe zu blockieren
Ich habe bereits erwähnt, dass einer der Gründe, den Zugang zu Ihrer Website zu blockieren, darin besteht, böswillige Besucher zu blockieren. Sie wollen sehen, wie sie Ihre Site kaputtmachen, Ihren Kommentarbereich mit Werbung oder anderen verrückten Botschaften vollstopfen können. Es gibt viele Gründe, warum sie das tun, ich glaube, einer davon ist, dass sie Sie zwingen wollen, sich ein Anti-Spam-Plugin eines Dritten zu schnappen. Diese können sehr effektiv sein, da sie sich mit riesigen Datenbanken mit Spam-Informationen verbinden. Aber wenn wir die Privatsphäre unserer Besucher respektieren wollen, können wir ein solches Plugin nicht verwenden. Wir müssen andere Wege gehen, und ein letzter Ausweg ist oft die Sperrung von IP-Adressen.
Es kann auch notwendig sein, bestimmte Bots zu blockieren, die Ihre Site scannen. Einige Bots erzeugen verrückte Mengen an Datenverkehr. Ich habe den gesamten Verkehr zu dieser Website für einen bestimmten Zeitraum überprüft, und es stellte sich heraus, dass nur 10%, wahrscheinlich sogar noch weniger, der Anfragen von echten Besuchern kamen! Natürlich sind nicht alle Bots schlecht, aber einige halten sich wirklich nicht an die Regeln. Die meisten Bots können anhand der User-Agent-Zeichenfolge identifiziert werden. Ich habe die folgenden beiden gefunden, die ich wirklich blockieren möchte:
- SemrushBot
- AhrefsBot
Seien Sie vorsichtig, was zu blockieren ist, viele Bots werden verwendet, um Ihre Website in den Suchmaschinenergebnissen zu erhalten. Bei SemrushBot geht es um SEO, das benutze ich im Moment nicht. Das Blockieren von User Agents wird in diesem Beitrag nicht behandelt. Er wird sich nicht so oft ändern, und Sie können Blöcke auf andere Weise setzen.
Gutes über unerwünschte Anfragen
Wenn Sie eine ordnungsgemäße Protokollierung implementieren, können Sie auch unerwünschte Anfragen ausnutzen. Die folgende Liste zeigt einige Anfragen, die einen HTTP 404-Fehler für diese Site verursacht haben:
http://peterspython.com/css/album.css
http://www.peterspython.com/wordpress
http://peterspython.com/blog/wp-includes/wlwmanifest.xml
http://peterspython.com/wordpress/wp-includes/wlwmanifest.xml
http://peterspython.com/website/wp-includes/wlwmanifest.xml
http://peterspython.com/public/ui/v1/js/sea.js
http://www.peterspython.com/public/ui/v1/js/sea.js
http://peterspython.com/vendor/phpunit/phpunit/phpunit.xsd
http://peterspython.com/vendor/phpunit/phpunit/LICENSE
http://www.peterspython.com/apple-touch-icon.png
http://peterspython.com/humans.txt
http://peterspython.com/license.txt
Wir sehen, dass Bots nach der Datei wlwmanifest.xml suchen. Dies scheint eine Datei zu sein, die mit "Windows Live Writer" assoziiert ist, einer von Microsoft entwickelten Anwendung zur Veröffentlichung von Blogs, die 2017 eingestellt wurde und möglicherweise anfällig ist. Ein weiterer Angriff sucht nach PHPUnit, einer PHP -Einheit, die framework testet. Diese enthielt eine Schwachstelle, die möglicherweise noch nicht gepatcht wurde. Andere Angriffs-Bots können URLs generieren, die einen HTTP 500-Fehler verursachen. Dies kann beabsichtigt sein, kann aber auch durch Schwächen Ihrer Website verursacht werden.
Die gute Nachricht ist, dass Sie diese Informationen nutzen können, um Ihre Website zu verbessern. Achten Sie immer auf eine korrekte Protokollierung, Fehler geben Ihnen sehr wertvolle Informationen!
Beschränkung auf die IP-Adressen IPv4
Das Blockieren von Besuchern anhand der IP-Adresse hat seine Grenzen. Viele Menschen im Internet erhalten ihre IP-Adresse, wenn sie sich über DHCP mit einem Server verbinden. Dies gilt vor allem für Mobiltelefone. Seien Sie also vorsichtig, was zu blockieren ist.
Dann gibt es noch IPv6 , das entwickelt wurde, um die begrenzte Verfügbarkeit von IPv4 -Adressen zu überwinden. Obwohl in einigen Berichten angegeben wird, dass 30 % des Internetverkehrs auf IPv6 entfallen, ist die Anzahl der Server, die IPv6 tatsächlich aktiviert haben, weitaus geringer. Dies bedeutet glücklicherweise, dass es im Moment keinen Grund gibt, Ihren Server auf IPv6 zu migrieren. Das Blockieren von Spam mit IPv6 ist mit dieser Methode möglich, aber es gibt einen Haken.
Verwaltungsvorgänge und Regeln für IP-Adressen auf der Schwarzen Liste
Im Admin möchte ich die IP-Adressen angeben, die ich auf die schwarze Liste setzen möchte. Es gibt eine Tabelle mit Datensätzen von IP-Adressen auf der schwarzen Liste. Für IP-Adressen möchte ich die IP-Adressen wie folgt angeben können:
- Eine einzelne IP-Adresse, Beispiel: 1.2.3.4
- Ein IP-Netzwerk, Beispiel: 1.2.3.0/24
- Einen IP-Adressbereich, Beispiel: 1.2.3.6-1.2.4.2
Ich spezifiziere eine davon in einem einzigen Datensatz und nenne dies eine "Regel für IP-Adressen auf der Schwarzen Liste".
Caching zur Vermeidung von Datenbankzugriffen
Wir wollen sicherlich nicht bei jeder Anfrage auf die Datenbank zugreifen, um zu sehen, ob die Anfrage erlaubt ist. Das würde die Anfragen verlangsamen. Aus diesem Grund verwenden wir Caching. Anstatt die Datenbank abzufragen, überprüfen wir zuerst den Cache, um zu sehen, ob die IP-Adresse zuvor auf die Website zugegriffen hat. Für jede IP-Adresse haben wir ein Flag namens 'erlaubt'. Wenn True, dann ist der Zugriff erlaubt, wenn False, dann wird der Zugriff blockiert.
Wenn sich die IP-Adresse im Cache befindet, sind wir fertig, fahren wir fort oder blockieren wir. Wenn sich die IP-Adresse nicht im Cache befindet, prüfen wir, ob sie in den Regeln für IP-Adressen auf der Schwarzen Liste steht. Das Ergebnis wird dem Cache hinzugefügt, und wenn das nächste Mal eine Anfrage mit dieser IP-Adresse auf unsere Site trifft, befinden sich die Daten im Cache und die Datenbank soll nicht abgefragt werden.
Hinzufügen und Entfernen von Regeln für IP-Adressen auf der Schwarzen Liste
Angenommen, wir haben Hunderte, Tausende von Objekten in unserem Cache. Jetzt wollen wir mit Hilfe des Administrators Änderungen vornehmen, indem wir entweder eine Regel für IP-Adressen auf der Schwarzen Liste hinzufügen oder eine Regel für IP-Adressen auf der Schwarzen Liste entfernen.
Das Hinzufügen oder Entfernen einer Regel ist nicht trivial, da die Regel IP-Adressen enthalten kann, die sich bereits im Cache befinden. Was soll mit unseren zwischengespeicherten Werten geschehen? Am einfachsten ist es, den Cache zu leeren und ihn wieder neu aufbauen zu lassen. Dadurch werden die nächsten Anfragen für kurze Zeit verlangsamt. Die einzige andere Möglichkeit besteht darin, die IP-Adressen im Cache zu scannen und zu prüfen, ob sie mit der hinzugefügten oder entfernten Regel für IP-Adressen auf der Schwarzen Liste übereinstimmen. Wenn sie übereinstimmen, entfernen wir sie aus dem Cache. Ich habe einige Ideen, wie dies zu implementieren ist, habe dies aber noch nicht getan.
Hinzufügen von Zeitstempeln
Für maximale Leistung sind die IP-Adressen-Informationen im Cache schreibgeschützt und verfallen nicht. Das bedeutet, dass sie mit der Zeit riesig werden können, wenn Sie viele Besucher haben. Da die meisten Besucher auf Ihre Website nur wenige Minuten lang zugreifen, können wir den im Cache gespeicherten IP-Adressen einen Zeitstempel hinzufügen, der bei jedem Zugriff aktualisiert wird. Der Zeitstempel macht es einfach, alte Einträge zu entfernen.
Anfragen zum gleichen Zeitpunkt
Angenommen, zwei Anfragen, Anfrage A und Anfrage B, kommen zur gleichen Zeit an, wobei beide die gleiche IP-Adresse verwenden. Wenn sie sich nicht im Cache befinden, prüfen beide, ob ihre IP-Adresse blockiert ist, indem sie die Tabelle der IP-Adressregeln der Blacklist durchsuchen. Dann aktualisieren beide das cached_access-Element. Antrag A erstellt zunächst das zulässige Element. Aber dann erzeugt Antrag B das erlaubte Element und überschreibt das erlaubte Element von Antrag A. Dasselbe gilt, wenn wir den Zeitstempel des zwischengespeicherten Elements aktualisieren wollen. Das mag schlecht aussehen, ist aber nicht wirklich so schlecht. Wir müssen nur sicherstellen, dass die Erstellungsoperation atomar ist.
Verwendung des Dateisystems Linux als Cache
Für den Moment habe ich mich dafür entschieden, den Cache mit Dateien zu implementieren. Das Dateisystem Linux ist schnell genug, um dies für meine Anwendung zu handhaben. Ich möchte nicht etwas wie Redis hinzufügen, ich möchte die Abhängigkeiten minimal halten.
Wenn wir eine 'erlaubte' Datei pro IP-Adresse haben, dann kann die Datei klein sein, der Inhalt ist 0 (blockiert) oder 1 (erlaubt). Um eine große Anzahl von Dateien in einem Verzeichnis und langsames Nachschlagen zu verhindern, erstellen wir Unterverzeichnisse auf der Basis der IP-Adresse. Wir teilen die IP-Adresse durch den Punkt ('.') auf und verwenden diese zur Erstellung von Verzeichnissen. Der Zeitstempel der 'erlaubten' Datei ändert sich automatisch, wenn die Datei gelesen wird. In Linux haben wir die folgenden Zeitstempel:
- mtime (ls -l)
Das letzte Mal, als der Dateiinhalt geändert wurde - ctime
Das letzte Mal, als sich der Dateistatus, z.B. Berechtigungen, geändert hat - atime (ls -lu)
Das letzte Mal, als die Datei gelesen wurde
Für unseren Zweck können wir atime als Zeitstempel verwenden. Wir müssen die Uhrzeit der Datei nicht aktualisieren. Es gibt ein Problem, wenn Sie in der Lage sein wollen, den Inhalt der erlaubten Dateien im Admin anzuzeigen. Dies würde die Dateien lesen und die Zeitstempel ändern. Wir können dies umgehen, indem wir eine Kopie der "erlaubten" Datei erstellen. Das Lesen der Kopie ändert nicht die Zeit der ursprünglichen 'erlaubten' Datei.
Eine Warnung bei Verwendung der Zugriffszeit Linux atime
Es gibt im Internet viele Informationen über Linux -Zeitstempel, aber nur sehr wenige erwähnen, dass dies möglicherweise nicht wie erwartet funktioniert. Ich lade Sie ein, sich dazu die untenstehenden Links anzusehen. Mit diesem Befehl können Sie z.B. überprüfen, ob relatime eine Mount-Option ist:
cat /proc/mounts | grep relatime
Die Zusammenfassung lautet:
- Die Aktualisierung der Zeit bei jedem Lesen ist aus Performance-Gründen standardmäßig deaktiviert.
- Seit Kernel 2.6.30 ist relatime die Standardoption
- Seit Kernel 2.6.30 wird die letzte Zugriffszeit einer Datei immer aktualisiert, wenn sie mehr als 1 Tag alt ist.
Das bedeutet, dass wir immer noch die Zeit nutzen können, aber eine Resolution von einem Tag respektieren müssen. Kein Problem für mich, aber warten Sie, lassen Sie uns testen, ob das wirklich funktioniert.
ls -l
Das Ergebnis ist:
total 8
-rw-r--r-- 1 flaskuser flaskgroup 1 apr 16 15:47 allowed
-rw-r--r-- 1 flaskuser flaskgroup 1 apr 16 15:47 allowed_copy
Als nächstes wollen wir die aZeit oder Zugriffszeit der erlaubten Datei sehen:
stat allowed
Das Ergebnis lautet:
File: allowed
Size: 1 Blocks: 8 IO Block: 4096 regular file
Device: 806h/2054d Inode: 38805116 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 1002/flaskuser) Gid: ( 1002/flaskgroup)
Access: 2020-04-16 15:47:06.024559817 +0200
Modify: 2020-04-16 15:47:06.024559817 +0200
Change: 2020-04-16 15:47:06.024559817 +0200
Birth: -
Jetzt ändern wir die Zugriffszeit auf den Vortag:
sudo touch -a -t 202004151530.02 allowed
Das Ergebnis des Befehls stat zeigt, dass die Zugriffszeit einen Tag früher liegt:
File: allowed
Size: 1 Blocks: 8 IO Block: 4096 regular file
Device: 806h/2054d Inode: 38805116 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 1002/flaskuser) Gid: ( 1002/flaskgroup)
Access: 2020-04-15 15:30:02.000000000 +0200
Modify: 2020-04-16 15:47:06.024559817 +0200
Change: 2020-04-16 15:52:12.472562630 +0200
Birth: -
Nun generieren wir eine Anfrage auf der Website und führen nach der Anfrage erneut den Befehl stat aus:
File: allowed
Size: 1 Blocks: 8 IO Block: 4096 regular file
Device: 806h/2054d Inode: 38805116 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 1002/flaskuser) Gid: ( 1002/flaskgroup)
Access: 2020-04-16 15:56:24.200564941 +0200
Modify: 2020-04-16 15:47:06.024559817 +0200
Change: 2020-04-16 15:52:12.472562630 +0200
Birth: -
Die Zugriffszeit wurde auf heute aktualisiert. Nachfolgende Anfragen aktualisieren die Zugriffszeit nicht mehr. Bei der Arbeit wie erwartet habe ich heute etwas gelernt.
Details zur Implementierung
Ich nannte die Klasse CachedAccess. In Flask's before_request instanziiere ich sie wie folgt:
@app.before_request
def before_request():
...
g.ip_address = get_ip_address()
...
cached_access = CachedAccess()
if not cached_access.is_allowed():
# bye bye
abort(403)
Und hier sind die (wichtigen) Teile der Klasse:
class CachedAccess:
def __init__(self):
...
def log_block(self, reason):
...
def is_allowed_ip_address(self, ip_address_uint):
# check: single ip addresses
access_block_ip_address = db_select(
model_class_list=[AccessBlockIPAddress],
filter_by_list=[
(AccessBlockIPAddress, 'is_active', 'eq', True),
(AccessBlockIPAddress, 'ip_address_type', 'eq', 3),
(AccessBlockIPAddress, 'ip_address_uint', 'eq', ip_address_uint),
],
).first()
if access_block_ip_address is not None:
# found
return False
# check: network and range
access_block_ip_address = db_select(
model_class_list=[AccessBlockIPAddress],
filter_by_list=[
(AccessBlockIPAddress, 'is_active', 'eq', True),
(AccessBlockIPAddress, 'ip_address_type', 'in', [1, 2]),
(AccessBlockIPAddress, 'ip_address_from_uint', 'le', ip_address_uint),
(AccessBlockIPAddress, 'ip_address_to_uint', 'ge', ip_address_uint),
],
).first()
if access_block_ip_address is not None:
# found
return False
return True
def is_allowed(self):
# check if valid ip_address
try:
ip_address_uint = int( ipaddress.ip_address(g.ip_address) )
except Exception as e:
current_app.logger.error(fname + ': not a valid ip address = {}, {}'.format(g.ip_address, str(e)))
return True
# create ip_address_file
app_cached_access_dir = current_app.config['APP_CACHED_ACCESS_DIR']
ip_address_parts = g.ip_address.split('.')
ip_address_file = os.path.join(app_cached_access_dir, *ip_address_parts, 'allowed')
# check if file exists and read its contents
found = True
try:
with open(ip_address_file, 'r') as f:
allowed = f.read()
except:
found = False
if found:
# done
if allowed == '1':
return True
self.log_block(1)
return False
# check if g.ip_address matches a rule in blacklisted IP addresses table
allowed = self.is_allowed_ip_address(ip_address_uint)
# create directories for g.ip_address
ip_address_dir = os.path.dirname(ip_address_file)
try:
pathlib.Path(ip_address_dir).mkdir(parents=True, exist_ok=True)
except Exception as e:
current_app.logger.error(fname + ': error creating directories ip_address_dir = {}, {}'.format(ip_address_dir, str(e)))
return True
# create allowed temp file
temp_name = next(tempfile._get_candidate_names())
ip_address_temp_file = os.path.join(app_cached_access_dir, *ip_address_parts, temp_name)
try:
with open(ip_address_temp_file, 'w') as f:
f.write( '1' if allowed else '0' )
except Exception as e:
current_app.logger.error(fname + ': error writing ip_address_temp_file = {}, {}'.format(ip_address_temp_file, str(e)))
return True
# atomic move ip_address_temp_file to ip_address_file
try:
os.rename(ip_address_temp_file, ip_address_file)
except Exception as e:
current_app.logger.error(fname + ': error renaming ip_address_temp_file = {} to ip_address_temp_file = {}, {}'.format(ip_address_temp_file, ip_address_temp_file, str(e)))
return True
if allowed:
return True
self.log_block(2)
return False
Das ist nicht wirklich sehr schwierig. Ich wandle die IP-Adresse in ein Unsigned Int um, damit wir prüfen können, ob sie in einem IP-Netzwerk oder IP-Adressbereich liegt. Wenn ein unerwarteter Fehler auftritt, protokolliere ich den Fehler und lasse die IP-Adresse zu. Das bedeutet, dass wir unerwartete Anfragen nicht blockieren.
Entwicklung und Produktion
Bei der Entwicklung werden Sie wahrscheinlich viele Anfragen während der Tests blockiert sehen. Der Grund dafür ist, dass Bilder, Javascript -Dateien usw. auch vom Flask -Entwicklungsserver bedient werden. Sie können diese Anfragen in Ihrem Code filtern:
if request_path.startswith( ('/static/') ):
return
Bei der Produktion gehe ich davon aus, dass Sie Ihren gesamten statischen Inhalt über den Webserver, Nginx, Apache, bereitstellen, was bedeutet, dass keine Zeit verschwendet wird. Wir blockieren nur Anfragen an den Code, Bilder usw. werden nicht blockiert.
Blockierung mit Nginx
Ich wollte meinen Webserver Nginx nicht kontrollieren, um es einfach zu halten. Aber es ist nicht so schwierig, ihm zu sagen, dass er Anfragen blockieren soll. Wenn Sie Nginx verwenden, können Sie ein paar Zeilen hinzufügen, um mehrere user -Agenten wie folgt zu blockieren:
if ($http_user_agent ~* (wget|curl|libwww-perl) ) {
return 403;
}
Und um mehrere IP-Adressen zu blockieren, die Sie verwenden können:
location / {
deny 127.0.0.1; # Individual IP Address
deny 1.2.3.0/24; # IP network
}
Aber das ist nicht das, was wir wollen. Wir wollen eine dynamische Blockierung. Dazu gibt es mehrere Möglichkeiten, aber natürlich müssen Sie sich viel mehr mit den Besonderheiten von Nginx befassen. Im Internet gibt es genug Beispiele dafür, wie man dies tun kann.
Zusammenfassung
Ich wollte unbedingt eine fliegende Sperrung von IP-Adressen implementieren, und es schien nicht so schwierig zu sein. Im Moment habe ich noch nicht alles implementiert. Das bedeutet keine intelligenten Aktualisierungen nach dem Hinzufügen oder Entfernen von Regeln für IP-Adressen auf der Schwarzen Liste. Stattdessen habe ich eine Schaltfläche 'Cache leeren', die ich drücken kann, nachdem ich Änderungen an der Blacklist-Tabelle vorgenommen habe. Das ist wie ein 'rm -R' in Python.
Der Zeitstempel für die Zugriffszeit von Linux verzögerte mich beim Schreiben dieses Beitrags, ich habe die Zugriffszeit nie verwendet, aber jetzt kenne ich ihre Eigenheiten. Linux aktualisiert die Zugriffszeit einmal am Tag, das ist für mich in Ordnung.
Ich bezweifle, dass Sie eine bessere Leistung erzielen können, aber Sie sollten sich vielleicht andere Optionen wie die Zwischenspeicherung des Elements im Speicher ansehen. Sie könnten TTLCache aus den Python -Cachetools verwenden.
Links / Impressum
cachetools
https://pypi.org/project/cachetools/
Dynamic Blacklisting of IP Addresses
https://docs.nginx.com/nginx/admin-guide/security-controls/blacklisting-ip-addresses/
flask-ipban
https://github.com/Martlark/flask-ipban
flask-ipblock
https://github.com/closeio/flask-ipblock
flask-limiter
https://github.com/alisaifee/flask-limiter
how to know if noatime or relatime is default mount option in kernel?
https://superuser.com/questions/318293/how-to-know-if-noatime-or-relatime-is-default-mount-option-in-kernel
Why is cat not changing the access time?
https://superuser.com/questions/464290/why-is-cat-not-changing-the-access-time/464737#464737
Mehr erfahren
Flask
Neueste
- Ausblenden der Primärschlüssel der Datenbank UUID Ihrer Webanwendung
- Don't Repeat Yourself (DRY) mit Jinja2
- SQLAlchemy, PostgreSQL, maximale Anzahl von Zeilen pro user
- Anzeige der Werte in den dynamischen Filtern SQLAlchemy
- Sichere Datenübertragung mit Public Key Verschlüsselung und pyNaCl
- rqlite: eine hochverfügbare und distverteilte SQLite -Alternative
Meistgesehen
- Verwendung von Pythons pyOpenSSL zur Überprüfung von SSL-Zertifikaten, die von einem Host heruntergeladen wurden
- Verwendung von UUIDs anstelle von Integer Autoincrement Primary Keys mit SQLAlchemy und MariaDb
- Verbindung zu einem Dienst auf einem Docker -Host von einem Docker -Container aus
- PyInstaller und Cython verwenden, um eine ausführbare Python-Datei zu erstellen
- SQLAlchemy: Verwendung von Cascade Deletes zum Löschen verwandter Objekte
- Flask RESTful API Validierung von Anfrageparametern mit Marshmallow-Schemas