OSINT - Was Fehlermeldungen einem Angreifer verraten können
Als User sind Fehlermeldungen bei der Anmeldung eher etwas Unerfreuliches. Man hat sich vermutlich irgendwo vertippt und muss jetzt noch mal Zeichen für Zeichen sein Passwort eingeben. Für einen Angreifer können Fehlermeldungen dagegen sehr wertvoll sein, da sie, je nach Inhalt, mehr über den Aufbau eines Systems verraten können.
Sehen wir uns dazu direkt ein Beispiel an: Eine Login Oberfläche einer Webanwendung, bei der man sich mit seiner Emailadresse und seinem Passwort anmelden kann.
Was passiert wohl, wenn wir versuchen, uns mit etwas anderem als einer Emailadresse anzumelden? Um das zu überprüfen, geben wir etwas in das Emailfeld ein, das von der Form her keiner validen Emailadresse entspricht. In diesem Fall hallo@hallo. Sofort erhalten wir die Meldung, dass diese Adresse ungültig ist. Eine Anmeldung ist nicht möglich, der Button ist ausgegraut. Ok, nächster Versuch. Dieses Mal verwenden wir eine reguläre Emailadresse, von der wir aber sicher wissen, dass sie bei dieser Webseite nicht verwendet wird.
Wir geben die Adresse ein und wählen ein beliebiges Passwort, in diesem Fall einfach eine 1. Das Ergebnis scheint auf den ersten Blick nicht weiter interessant zu sein: Die Webseite teilt uns mit, dass kein Konto existiert, bei dem diese Emailadresse verwendet wird. Also nichts was wir nicht schon vorher wussten.
Aber nun wollen wir einen Schritt weitergehen. Wir registrieren uns ganz regulär auf der Plattform, damit wir anschließend mit einer Adresse testen können, zu der ein Konto gehört.
Anschließend geben wir die korrekte Emailadresse ein, allerdings mit einem falschen Passwort (wieder die 1). Auf den ersten Blick scheint auch hier die Fehlermeldung nicht interessant zu sein, sie teilt uns mit, dass unser Passwort nicht korrekt ist, aber das wussten wir bereits vorher.
Interessant wird es auf den zweiten Blick: Bemängelt wird nur das Passwort, zur Emailadresse steht da nichts mehr (logisch, die existiert dieses Mal ja auch). Wir erinnern uns an den vorherigen Versuch: Dabei wurde bemängelt, dass die Emailadresse nicht existiert.
Das bedeutet: Aufgrund der Fehlermeldungen sind wir in der Lage zu überprüfen, welche Emailadressen bei der besagten Plattform angemeldet sind.
Angriffsvektoren
User enumerieren
Die Tatsache, dass wir feststellen können, welche Emailadressen auf der Plattform angemeldet sind, kann je nach Art der Plattform schon zu ernsten Problemen führen. So ist es für die meisten Menschen sicher nicht schlimm, wenn ihre Mitgliedschaft bei Ebay oder Facebook bekannt wird. Ganz anders sieht die Sache aber vermutlich bei Webseiten aus, die Seitensprünge vermitteln oder dem Austausch über einen ausgefallenen Fetisch dienen.
Brute force
Das wir zuerst die Existenz einer Emailadresse prüfen können, erleichtert auch Brute Force Angriffe massiv. Dazu ein kleines Beispiel:
Nehmen wir an, ein Angreifer hat eine Liste mit 1000 Emailadressen und 1000 Passwörtern (in der Realität wären das eine sehr kleine Liste, aber es reicht, um das Prinzip deutlich zu machen).
Wenn die Fehlermeldung keinen Hinweis darauf liefert, ob eine Adresse korrekt ist, müsste er für jede der 1000 Adressen alle 1000 Passwörter durchprobieren. Also 1000×1000 = 1.000.000 Versuche.
Ganz anders sieht die Situation aus, wenn er zuerst die Emailadressen überprüfen kann. In diesem Fall wären das zunächst 1000 Versuche. Anschließend muss er nur noch für die Emailadressen, deren Existenz bestätigt wurde, jeweils die 1000 Passwörter ausprobieren. Nehmen wir an, dass von den 1000 Emailadressen 10 tatsächlich registriert sind. Das macht dann also 1000 + 10×1000 = 11.000 Versuche insgesamt.
Das bedeutet, aufgrund der Fehlermeldung reduziert sich die Zahl der nötigen Brute Force Versuche um etwa 99%!
Natürlich sind Brute Force Angriffe auf die Login Oberflächen von Webseiten meist keine sonderlich gute Idee, da diese relativ leicht erschwert werden können. Beispielsweise durch das Lösen eines Captchas nach einer gewissen Anzahl gescheiterter Versuche oder einer Begrenzung auf X Versuche pro Minute. Aber dennoch: Wenn ein Angreifer genügend große Delays in seinen Code einbaut und eventuell über verschiedene Proxys auf die Seite zugreift, stehen die Chancen nicht so schlecht, dass zumindest die Enumeration der User erfolgreich verläuft.
Proof of Concept
Als kleiner Proof of Concept soll an dieser Stelle noch mit einem kleinen Python Skript gezeigt werden, wie leicht sich die Enumeration von Usern basierend auf einer Fehlermeldung automatisieren ließe.
Um das Beispiel nachvollziehen zu können, benötigen Sie eine Python 3.X Installation (dabei wird die einfache IDE Idle gleich mitgeliefert) sowie den Chrome Browser und den Chrome Driver.
Chrome Browser: https://www.google.com/intl/de_de/chrome/
Chrome Driver: https://chromedriver.chromium.org/downloads
Achten Sie darauf, dass die Version des Browsers und des Drivers übereinstimmen. Außerdem muss der Driver nach dem Download entzippt werden und die .exe Datei muss im selben Verzeichnis liegen wie das Skript (alternativ können Sie auch im Skript den Pfad zum Chrome Driver angeben).
Der Chrome Driver ermöglicht das Steuern des Chrome Browsers, etwa um Webseiten zu testen oder, wie in diesem Fall, die Sicherheit von Webseiten. Über das Skript können dann Befehle (Tastatureingaben, Tastendrücke) an den Browser gesendet werden.
Im folgenden wird der Code für ein simples Python Skript vorgestellt und seine Bestandteile erklärt. Das komplette Skript finden Sie auf unserem GitHub Account.
Zuerst importieren wir die nötigen Module. Time ist dabei bereits im Standardumfang von Python enthalten, Selenium muss zunächst noch installiert werden. Dazu einfach eine Kommandozeile öffnen (bei Windows cmd in das Suchfeld eingeben und den Vorschlag „Eingabeaufforderung“ auswählen) und pip install selenium eingeben und mit der Enter Taste bestätigen. Die nötigen Module werden dann heruntergeladen, installiert und können zukünftig verwendet werden.
Anschließend legen wir eine Liste mit Usernamen an. Für diesen kurzen Proof of Concept reichen uns zwei, wer gerne tausende von Namen testen möchte, könnte diese an dieser Stelle auch aus einem Textfile auslesen. Das Passwort setzen wir auf 1, da es uns im ersten Schritt nur darum geht, die gültigen Mailadressen herauszufinden. Bzw. die eine gültige Mailadresse, da wir nach dieser abbrechen. Wer mehrere gültige Adressen finden möchte, sollte valid_mail statt als String als Liste anlegen und dann innerhalb der Schleife alle gültigen Adressen in diese Liste einfügen.
Nun kommt die erste Schleife, die für jede Emailadresse in unserer Usernames Liste durchlaufen wird. Zuerst rufen wir mit dem Browser die Login Seite der Webseite auf, die wir untersuchen wollen. Zu Testzwecken ist diese hartcodiert, mittels der input() Funktion könnte man die Adresse aber auch vom User eingeben lassen, sodass das Skript variabler einsetzbar wird.
Anschließend muss der Browser das Feld für die Emailadresse/Username finden. Dazu müssen wir auf der betreffenden Seite mit der rechten Maustaste auf das Element klicken und „Untersuchen“ auswählen, um uns den HTML Code der Seite anzeigen zu lassen. Dort wählen wir die ID aus, die das Element identifiziert. Alternativ bietet Selenium auch die Möglichkeit, Elemente per XPath oder CSS Selektor zu identifizieren.
Ist das Element gefunden, geben wir den Usernamen in das Feld ein. Anschließend folgt eine kurze Pause, um weniger Verdacht zu erregen, bevor wir das Ganze für das Passwort Feld wiederholen. Durch das Senden der Enter Taste bestätigen wir unsere Eingabe und warten 10 Sekunden ab, damit die Fehlermeldung genug Zeit hat um aufzutauchen.
Auch hier ist die Wartezeit bewusst hoch gewählt, um keinen Verdacht zu erregen. Unter realen Bedingungen müsste man natürlich versuchen, einen guten Kompromiss zu finden, um einerseits nicht lang zu warten und andererseits dennoch so gut wie möglich unter dem Radar zu fliegen.
Ist die Wartezeit vorbei, suchen wir die Fehlermeldung anhand ihrer ID. Anmerkung: Da wir als Passwort die 1 verwenden, gehen wir davon aus, dass wir in jedem Fall eine Fehlermeldung erhalten. Sollte aber tatsächlich ein User, dessen Emailadresse wir prüfen, die 1 als Passwort verwendet haben, würde unser Skript an dieser Stelle abbrechen. Wer diesen unwahrscheinlichen Fall noch abfangen möchte, kann mit einem try/except Block arbeiten.
Im letzten Teil der Schleife untersuchen wir den Inhalt der Fehlermeldung (zu den Texten siehe die Screenshots weiter oben). Kommt das Wort „Konto“ vor, ist die Emailadresse nicht registriert und wir geben dies auf der Konsole aus. Kommt dagegen das Wort „Passwort“ vor, haben wir eine korrekte Adresse gefunden und speichern diese in unserem valid_mail String. Anschließend verlassen wir die Schleife.
Mit diesem Teil des Skripts haben wir die Möglichkeit, eine Reihe von Emailadressen darauf zu prüfen, ob sie bei der betreffenden Webseite registriert sind. Wem das schon reicht, der kann den folgenden Code einfach auskommentieren, wer möchte kann sich nun aber auch an einer einfachen Brute Force Attacke auf die identifizierte Emailadresse versuchen. Hinweis: Dies dient lediglich Demonstrationszwecken, weder kann man einfach so Brute Force Angriffe starten, noch ist dieser Angriff bei einer Weboberfläche sonderlich erfolgsversprechend, da es recht einfach ist, verschiedene Beschränkungen einzubauen.
Für den Brute Force Teil können wir weitgehend den Code der ersten Schleife wiederverwenden. Vor der Schleife ergänzen wir eine Liste mit Passwörtern, wie auch bei den Emailadressen sind diese zu Testzwecken hier direkt im Code eingegeben, in einer realen Situation mit deutlich größeren Passwort Listen würde man diese dagegen eher aus einem Textfile auslesen.
Der Rest bleibt weitgehend gleich, bei der Fehlermeldung arbeiten wir nun mit einem try/except Block. Dabei wird zuerst der Code im Try Block ausgeführt, der versucht die Fehlermeldung zu finden. Wird diese gefunden, war das Passwort falsch und wir springen mit continue zum Beginn der Schleife. Die Überprüfung, ob das Wort „Passwort“ im Text enthalten ist, könnte man sich an dieser Stelle auch sparen, um den Code weiter zu verkürzen. Kann die Fehlermeldung nicht gefunden werden, kommt der Code im Except Block zur Ausführung. In diesem Fall ist davon auszugehen, dass die Kombination aus Username und Passwort korrekt war. Wir speichern also das Passwort und verlassen die Schleife.
Auch hier versuchen wir also nur eine einzige gültige Kombination zu finden, wobei sich das Verhalten auch leicht anpassen ließe, man müsste hier nur mit einer zusätzlichen Liste für valide Passwörter arbeiten.
Am Schluss geben wir noch die gefundene Kombination aus Emailadresse und Passwort aus, die zu einem erfolgreichen Login geführt hat.
Insgesamt sind das weniger als 90 Zeilen Code. Natürlich sind viele Teile des Skripts recht roh und müssten vor einer Verwendung auf anderen Webseiten angepasst werden (IDs, Login URL etc.) es zeigt allerdings, wie man in sehr kurzer Zeit einen kleinen Proof of Concept erstellen kann, um die Informationspreisgabe der Fehlermeldungen auszunutzen.
Gegenmaßnahmen
Um die beschriebenen Angriffe zu unterbinden, genügt es, den Text der Login Fehlermeldung so abzuändern, dass er keinen Hinweis mehr darauf liefert, welches Input Feld fehlerhaft ist. Ein möglicher Text wäre etwa „Login Informationen nicht korrekt“, dabei wird nicht verraten, ob das Problem nun bei der Emailadresse oder beim Passwort liegt.
Klar ist allerdings auch, dass hier ein Trade Off zwischen Security und Usability vorliegt. Aus Sicht des Users ist es natürlich angenehmer, wenn er genau gesagt bekommt, wo das Problem liegt, dann muss er immerhin nur ein Feld überprüfen statt zwei. Aus diesem Grund werden solche Fehlermeldungen auch von vielen großen Webseiten und Plattformen verwendet.
Aus unserer Sicht stellt dies aber dennoch ein unnötiges Risiko dar. Wir raten aus diesem Grund zu Fehlermeldungen, die die Existenz von Usernamen nicht bestätigen. Insbesondere dann, wenn der Inhalt der Seite ein wenig heikel ist.