Regex Regel - identifiziere IP-Adresse NUR mit min einem Wildcard Zeichen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
chris_adnap
User
Beiträge: 32
Registriert: Freitag 23. September 2022, 09:36

Hallo,

ich brauche einmal Unterstützung zum Aufbau eines Regex Regelsatzes.
Ich bin gerade dabei IP Regeln zu identifizieren. Also...

einzelne IP-Adresse z.b. 10.10.1.10

Code: Alles auswählen

re.compile(r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$")
oder eine Angabe als Range wie 10.10.1.1-10.10.1.100

Code: Alles auswählen

re.compile(r"^((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))-((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$")
als Angabe mit Netzwerkmaske etc... alles soweit so gut.


Einen letzten Punkt habe ich noch offen. Eine Angabe mit Platzhalterzeichen "*" (egal wo) 10.10.*.10, sowie auch 10.10.10.*, etc...
In dieser Regex Regel möchte aber "vollständige" IP-Adressen sowie eine Angabe von *.*.*.* ausschließen.

In der Theorie im Kopf kein Problem :D Ich muss "nur" Prüfen, ob es mindestens einen bis max. 3 Platzhalterzeichen gibt...
Mein bisher bestes Ergebnis sieht folgendermaßen aus...

Code: Alles auswählen

^((\*|25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(\*|25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(\*|25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(\*|25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?<=(.*\*|.*\*.*|\*.*){1,3}))$
... dies Funktioniert insofern, das diese Regel "vollständige" Adresse wie 1.2.3.4 ignoriert. Hier war meine Idee einfach am Ende des normalen regex die erste Gruppe auf * Zeichen zu zählen.
Dies war aber auch nur ein Zufallstreffer, das diese so funktioniert. Es wird aber noch trotzdem noch *.*.*.* erkannt.

ich weiß nicht ob/wie mir ein lookahead hier hilft, oder ob es möglich ist ein backreference am Ende nochmal auf die Anzahl der Platzhalterzeichen auszuwerten.

Wenn mir da jemand mal auf die Sprünge helfen könnte wär dies genial. :)
Sollte ich zwischenzeitlich eine Lösung finden, so ergänze ich dies natürlich hier.


Edit:
Zwar noch keine Lösung. Aber gerade bin ich auf die Idee gekommen. Ich geh den anderen Weg. Benutze einen Negativ Lookahead wie...

Code: Alles auswählen

(?!\d\.\d\.\d\.\d|\*\.\*\.\*\.\*)
...damit würde ich, an der richtigen Stelle eingebaut, genau das gewünschte erreichen.


Viele Grüße
Chris
chris_adnap
User
Beiträge: 32
Registriert: Freitag 23. September 2022, 09:36

Hi,

ich habe eine Lösung gefunden.
Wie ich in meinem Ausgangs Posting als kleines Edit schon eingefügt habe, habe ich es nun folgendermaßen aufgebaut.

Code: Alles auswählen

^((?!\d+\.\d+\.\d+\.\d+|\*\.\*\.\*\.\*)(\*|25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(\*|25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(\*|25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(\*|25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$
Damit schließe ich vollständige IP-Adressen, sowie eine vollständige Platzhalter Angabe aus, erst dann arbeite ich mit dem anderen Regelsatz.

Spricht von diesem Aufbau etwas dagegen?


Viele Grüße
Chris
imonbln
User
Beiträge: 191
Registriert: Freitag 3. Dezember 2021, 17:07

Warum muss alles immer mit einer Regexp erreicht werden?

Ich würde hier, klassisch Divide et impera machen, schon die Ranges mit den minus hätte ich nicht in einer regexp gemacht, sondern
wenn es ein Minus gibt geteilt und dann beide Seiten überprüft, ob es IP-Adressen sind. Die IPs hätte ich auch mit den Module https://docs.python.org/3/library/ipaddress.html überprüft und selbst wenn das Modul intern auch regexp nutzen würde, IPv6 gibt es dafür frei Haus, während die Regexp auf IPv4 festgelegt ist.

Für dein Problem mit den Wildcards würde ich einfach den String an den Punkten zerhacken (vielleicht auch mit einer einfachen Regexp) und dann die Teilstrings zählen und die eine Wildcard enthalten separat zählen. Wenn es genau 4 Chunks sind ( IPv4) und maximal eine Wildcard. Die nicht Wildcards, dann einfach in einen Integer wandeln und dann sehen, ob sie nicht kleiner 0 und nicht größer 255 sind.

Aber wie gesagt ich würde einen nicht Regexpvalidator schreiben und versuchen das zu verwenden, was schon da ist, das macht es auch übersichtlicher für die Wartung.
chris_adnap
User
Beiträge: 32
Registriert: Freitag 23. September 2022, 09:36

Hallo imonbln,

natürlich gibt es auch andere Wege. String teilen, etc...
Aber so "dachte ich mir", kann ich mit einer Regel gleich erkennen, um was es sich dabei handelt und passend als Gruppe die IP ausgeben.
Natürlich nutze ich im Nachgang och ipaddress um z.b. zu schauen ob from-range <= to-range auch stimmt.

Aber nur ein "teilen/split" reicht nicht aus, eben u.a. das größer/kleiner 0/255 prüfen etc. So dachte ich mir ist dies mit einem Aufwasch/Regel erledigt.

Ich nutze sicherlich nicht Regex an jeder Stelle. Ist seit sicherlich einem Jahr mal jetzt wieder in Benutzung. Darum hatte ich ja auch mit meinem Versuch erst mal etwas Probleme. :)

Aber warum siehst du hier z.B. Probleme regex zu nutzen? Wegen der Performance?

Viele Grüße
Chris
imonbln
User
Beiträge: 191
Registriert: Freitag 3. Dezember 2021, 17:07

Zum einen wegen der Lesbarkeit, Code wird öfters gelesen als geschrieben, daher sollte er auf Verständnis optimiert sein und seien wir mal ehrlich jede nicht triviale Regexp wird durch regex101.com oder ähnliches gejagt, bis der Entwickler meint dieses Konstrukt verstanden zu haben.

Zum Anderen gibt es bei Regexp immer noch das Damoklesschwert, des catastrophic backtracking, das gerade bei der Validierung einer Eingabe (also ungeprüften Content eines Benutzers) über dem Programm schwebt und einen DoS begünstigt. Meine persönliche Erfahrung sagt vielen ist das nicht bewusst und sie wissen auch nicht wie man die Regexp, dagegen robust macht. Ich selbst weiß, dass des das gibt, aber müsste auch erst Forschen was denn gegen catastrophic backtracking zu tun ist.

Last but not least, das Filtern von IPs wird zwar gerne als Regexp Beispiel herangezogen, aber ich denke, das ist ein Wenig wie die Fakultät bei der Rekursion, ein schöner Showcase um die Mächtigkeit des Werkzeugs zu beweisen, aber bei näherem Hinsehen nicht die beste Wahl oder anders gesagt versuch das mal so zu erweitern das IPv4 & IPv6 geht, mit einer Regexp.

Von daher ist meine Meinung, Regexp sind mächtiges Werkzeuge ist, das richtig eingesetzt eine Menge leisten kann, aber diese Macht wird teuer erkauft und sollte daher eine besonnen eingesetzt werden. Denn meisten ist es auch eine technische Schuld, wenn das Programm an der Regexp erweitert werden soll/muss ist nicht selten ein großer Aufwand oder Redesign nötig.
nezzcarth
User
Beiträge: 1762
Registriert: Samstag 16. April 2011, 12:47

Bei IPv4 Adressen gibt es auch eine Reihe alternativer Schreibweisen (z.B. Hexadezimalnotation oder Oktalnotation). Zudem kann man auch Oktette weglassen und dann Werte > 255 haben. 127.999 ist zum Beispiel eine IP, die von ping unter Linux problemlos verstanden wird (entspricht 127.0.3.231). Das alles sauber in RegEx zu fassen (auch nur, um es ggf. ablehnen zu können), ist nicht so leicht. Daher ggf. besser eine Library, die das sauber implementiert, nehmen, falls möglich.

S.a: https://www.rfc-editor.org/rfc/rfc6943# ... nderfällen
Benutzeravatar
DeaD_EyE
User
Beiträge: 1239
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Ist zwar langsamer, aber ist für den Programmierer verständlicher.

Code: Alles auswählen

import ipaddress


def in_range(ip, start, end):
    return ipaddress.ip_address(start) <= ipaddress.ip_address(ip) <= ipaddress.ip_address(end)

Code: Alles auswählen

>>> in_range("192.168.0.20", "192.168.0.0", "192.168.0.255")
True
>>>
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
chris_adnap
User
Beiträge: 32
Registriert: Freitag 23. September 2022, 09:36

Danke euch beiden,

ich kann eure Gründe vollkommen nachvollziehen.

Diese Regex nutze ich nur zur einfachen Bestimmung/Validierung um was für ein Regelsatz es sich für die "spätere" Bearbeitung/Überprüfung handelt. Diese Regeln sind in einer DB einem Array Feld hinterlegt. {x.x.x.x;x.x.x.x-x.x.x.x}.
Ist es eine einzelne IP-Adresse, eine Range 192.168.0.1 - 192.168.0.100, oder 192.168.0.1-100, 192.168.0.0/24, 192.168.*.1 etc ...
Für die weitere Bearbeitung/Validierung hatte ich nie vor regex herzunehmen.

Bezüglich "catastrophic backtracking". Hatte ich zuvor noch nie gehört hatte. :) Ich gebe vor was übergeben/angegeben wird, und auch die Regeln sind einfach gehalten und nutze kein pauschales .* in der regex, auch sind die übergebenen Strings minimal und keine x MB an Daten, etc.
Die einzige "externe" Angabe ist die IP des Users und die wird nicht vom User selbst angegeben, damit schließe ich ein DoS aus.
Natürlich nur solange ich selbst kein Misst bei den Regeln mache. :D Aber auch dann sollte es zu keinem Problem kommen, wird einfach nur geloggt, dass es sich um eine falsche/unbekannte Regel handelt.

Natürlich hätte ich dies auch mit Split etc. lösen können (ich hab mal nachgeschaut, so geht auch ipaddress intern um).
Dort wird nicht mit Regex gearbeitet. Aber dies kann z.b. mit anderen Angaben wie integer, Oktal, umgehen. In diesem Fall wäre Regex wirklich die komplett falsche Lösung.

Für mich habe ich den Vorteil, ich weiß was angegeben wird/werden kann, und durch Regex kann ich mit einem Aufwasch die Daten Validieren und durch Gruppen gleich (z.b. Range VON-BIS, CIDR-Maske /xx, ...) extrahieren. Sollte mal eine Regel nicht zutreffen, so logge ich dies und kann darauf später reagieren.


Sobald der erste Schritt (Bestimmung der Regel) durch ist, nutze ich natürlich die vorhandene Module wie ipaddress. Z.b. um zu prüfen, ob eine Range Angabe auch Valide ist ... VON <= BIS, liegt die gesuchte IP innerhalb der Range, etc...
Muss ja auch nicht jedes Rad neu erfinden. :) Da ist kein Regex mehr im Spiel.
Eigentlich bin ich auch schon mit fast allem durch. Die komplette Validierung läuft mit/über ipaddress.
Nur für eine Wildcard "*" angegebene Regel muss ich mir noch etwas überlegen, da für solch einen Fall ipaddress nichts vorsieht. Aber vielleicht kann ich mit Split in kombination mit ipaddress dies irgendwie realisieren.

Bezüglich "anderer" schreibweißen (Hexadezimalnotation ...) mache ich mir keine Sorgen, da ich die Regeln selbst eintrage, bzw. vorgeben, was eingetragen werden kann.
Dies ist übrigens für ein Login System, womit ich bestimmte Accounts/Logins auf bestimmte IPs beschränken kann, das diese z.b. sich nur aus dem Intranet, oder nur von bestimmten stellen (meist statische Adressen) sich einloggen können.

Für mich hätte sicherlich Single und Range IP Angabe völlig gereicht, aber ich dachte mir, wenn ich mal schon dabei bin, nehme ich "übliche" alternative schreibweißen mal mit auf.
Etwas Spaß und Denksport nebenbei. :D

Viele Grüße
Chris
Benutzeravatar
DeaD_EyE
User
Beiträge: 1239
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Noch ein Beispiel:

Code: Alles auswählen

from ipaddress import ip_address, ip_network, ip_interface


def is_range4(ip: str, network: str) -> bool:
    ip = ip_address(ip)
    network = ip_network(network)
    return ip in network


def is_range4_wildcard(ip: str, network: str) -> bool:
    ip = ip_address(ip)
    network = ip_network((network.replace("*", "0"), 32 - network.count("*") * 8))
    return ip in network


def is_range4i(ip: str, interface: str) -> bool:
    return ip_address(ip) in ip_interface(interface).network


ip = "192.168.0.1"
net = "192.168.0.0/24"  # Netzwerk ohne gesetzte Hostbits
wildcard = "192.168.*.*"
# wildcard = "*.168.*.*" <<< ValueError
interface = "192.168.0.10/24"  # IP mit gesetzen Hostbits und der Netzmaske

print(is_range4(ip, net))
print(is_range4_wildcard(ip, wildcard))
print(is_range4i(ip, interface))

PS: mit Regex kann man Primzahlen berechnen und das geht aufgrund des Backtrackings.

Code: Alles auswählen

not re.search(r"^.?$|^(..+?)\1$", "🤮" * 5)
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
chris_adnap
User
Beiträge: 32
Registriert: Freitag 23. September 2022, 09:36

Hi,

super vielen dank für die Beispiele. Ich nehme diese mal mit auf.
Evtl. ändere ich doch noch ein paar schon vorhandene Teile.

Auch zu dem Wildcard, an jeder beliebigen Stelle, habe ich eine Lösung gefunden.
Ein einfaches Split mit evtl führenden Nullen entfernen und eine kleine Schleife, schon ein fertiges Ergebnis.

Code: Alles auswählen

def is_range4_wildcard(ip: str, network: str) -> bool:
    split_network = [s.lstrip("0") or '0' for s in network.split('.')]
    split_ip = [s.lstrip("0") or '0' for s in ip.split('.')]
        for x, x_network in enumerate(split_network):
            if x_network != '*' and x_network != split_ip[x]:
                return False
        return True
Zuvor hatte ich viel zu komplex gedacht.
Hatte nicht an so eine einfache Lösung gedacht.

Viele Grüße
Chris
Antworten