Warum sind böse Regexe ein Problem?
Weil Computer genau das tun, was Sie ihnen sagen, auch wenn es nicht das ist, was Sie gemeint haben oder völlig unvernünftig sind. Wenn Sie eine Regex-Engine bitten, zu beweisen, dass für eine bestimmte Eingabe eine Übereinstimmung mit einem bestimmten Muster vorliegt oder nicht, versucht die Engine, dies zu tun, unabhängig davon, wie viele verschiedene Kombinationen getestet werden müssen.
Hier ist ein einfaches Muster, das vom ersten Beispiel im Beitrag des OP inspiriert wurde:
^((ab)*)+$
Angesichts der Eingabe:
abababababababababababab
Die Regex-Engine versucht so etwas wie (abababababababababababab)
und beim ersten Versuch wird eine Übereinstimmung gefunden.
Aber dann werfen wir den Schraubenschlüssel hinein:
abababababababababababab a
Der Motor wird es zuerst versuchen, (abababababababababababab)
aber das scheitert an diesem Extra a
. Dies führt zu einem katastrophalen Bracktracking, da unser Muster (ab)*
in gutem Glauben eine seiner Aufnahmen freigibt (es wird "zurückverfolgen") und das äußere Muster erneut versuchen lässt. Für unsere Regex-Engine sieht das ungefähr so aus:
(abababababababababababab)
- Nein
(ababababababababababab)(ab)
- Nein
(abababababababababab)(abab)
- Nein
(abababababababababab)(ab)(ab)
- Nein
(ababababababababab)(ababab)
- Nein
(ababababababababab)(abab)(ab)
- Nein
(ababababababababab)(ab)(abab)
- Nein
(ababababababababab)(ab)(ab)(ab)
- Nein
(abababababababab)(abababab)
- Nein
(abababababababab)(ababab)(ab)
- Nein
(abababababababab)(abab)(abab)
- Nein
(abababababababab)(abab)(ab)(ab)
- Nein
(abababababababab)(ab)(ababab)
-
(abababababababab)(ab)(abab)(ab)
Nein
(abababababababab)(ab)(ab)(abab)
- Nein
(abababababababab)(ab)(ab)(ab)(ab)
- Nein
(ababababababab)(ababababab)
- Nein
(ababababababab)(abababab)(ab)
- Nein
(ababababababab)(ababab)(abab)
- Nein
(ababababababab)(ababab)(ab)(ab)
- Nein
(ababababababab)(abab)(abab)(ab)
- Nein
(ababababababab)(abab)(ab)(abab)
- Nein
(ababababababab)(abab)(ab)(ab)(ab)
- Nein
(ababababababab)(ab)(abababab)
- Nein
(ababababababab)(ab)(ababab)(ab)
- Nein
(ababababababab)(ab)(abab)(abab)
- Nope
(ababababababab)(ab)(abab)(ab)(ab)
- Nope
(ababababababab)(ab)(ab)(ababab)
- Nope
(ababababababab)(ab)(ab)(abab)(ab)
- Nope
(ababababababab)(ab)(ab)(ab)(abab)
- Nope
(ababababababab)(ab)(ab)(ab)(ab)(ab)
- Nope
...
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abababab)
- Nope
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ababab)(ab)
- Nope
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abab)(abab)
- Nope
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abab)(ab)(ab)
- Nope
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ababab)
- Nope
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abab)(ab)
- Nope
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abab)
- Nope
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)
- Nope
Die Anzahl der möglichen Kombinationen skaliert exponentiell mit der Länge der Eingabe, und bevor Sie es wissen, verbraucht die Regex-Engine alle Ihre Systemressourcen, um dieses Problem zu lösen, bis sie nach Erschöpfung aller möglichen Kombinationen von Begriffen schließlich aufgibt und meldet "Es gibt keine Übereinstimmung." Inzwischen hat sich Ihr Server in einen brennenden Haufen geschmolzenen Metalls verwandelt.
Wie man böse Regexe erkennt
Es ist eigentlich sehr schwierig. Ich habe selbst ein Paar geschrieben, obwohl ich weiß, was sie sind und wie ich sie generell vermeiden kann. Sehen Sie, wie Regex überraschend lange dauert . Wenn Sie alles, was Sie können, in eine Atomgruppe einschließen, können Sie das Backtracking-Problem vermeiden. Grundsätzlich weist es die Regex-Engine an, einen bestimmten Ausdruck nicht erneut aufzurufen - "Sperren Sie alles, was Sie beim ersten Versuch gefunden haben". Beachten Sie jedoch, dass atomare Ausdrücke das Zurückverfolgen innerhalb des Ausdrucks nicht verhindern , also ^(?>((ab)*)+)$
immer noch gefährlich, aber ^(?>(ab)*)+$
sicher sind (es stimmt überein (abababababababababababab)
und weigert sich dann, übereinstimmende Zeichen aufzugeben, wodurch ein katastrophales Zurückverfolgen verhindert wird).
Leider ist es nach dem Schreiben sehr schwierig, sofort oder schnell einen regulären Regex zu finden. Am Ende ist das Erkennen eines fehlerhaften regulären Ausdrucks wie das Erkennen eines anderen fehlerhaften Codes - es erfordert viel Zeit und Erfahrung und / oder ein einzelnes katastrophales Ereignis.
Interessanterweise veröffentlichte ein Team der University of Texas in Austin seit der ersten Abfassung dieser Antwort einen Artikel, in dem die Entwicklung eines Tools beschrieben wurde, mit dem reguläre Ausdrücke statisch analysiert werden können, um diese "bösen" Muster zu finden. Das Tool wurde entwickelt, um Java-Programme zu analysieren, aber ich vermute, dass in den kommenden Jahren weitere Tools entwickelt werden, die problematische Muster in JavaScript und anderen Sprachen analysieren und erkennen, insbesondere wenn die Rate der ReDoS-Angriffe weiter steigt .
Statische Erkennung von DoS-Schwachstellen in Programmen, die reguläre Ausdrücke verwenden
Valentin Wüstholz, Oswaldo Olivo, Marijn JH Heule und Isil Dillig von
der University of Texas in Austin