BLOG

Erkennung von PhantomJS-basierten Besuchern

F5 Miniaturansicht
F5
Veröffentlicht am 22. Januar 2015


Heutzutage sind viele Web-Sicherheit auf Automatisierung zurückzuführen. Angreifer, die Web-Scraping, die Wiederverwendung von Passwörtern und Klickbetrug begehen, versuchen, echte Benutzer zu imitieren und so den Anschein zu erwecken, als kämen sie von einem Browser. Als Websitebesitzer möchten Sie sicherstellen, dass Sie Menschen dienen, und als Webdienstanbieter möchten Sie, dass der programmgesteuerte Zugriff auf Ihre Inhalte über Ihre API erfolgt und nicht über Ihre schwerere und weniger stabile Weboberfläche.

Vorausgesetzt, Sie verfügen über grundlegende Prüfungen für cURL-ähnliche Besucher, besteht der nächste sinnvolle Schritt darin, sicherzustellen, dass die Besucher echte, UI-gesteuerte Browser verwenden – und keine Headless-Browser wie PhantomJS und SlimerJS .

In diesem Artikel demonstrieren wir einige Techniken zur Identifizierung von Besuchen durch PhantomJS. Wir haben beschlossen, uns auf PhantomJS zu konzentrieren, da es die beliebteste Headless-Browserumgebung ist, aber viele der Konzepte, die wir behandeln, sind auch auf SlimerJS und andere Tools anwendbar.

NOTIZ: Die in diesem Artikel vorgestellten Techniken sind, sofern nicht ausdrücklich anders erwähnt, sowohl auf PhantomJS 1.x als auch auf 2.x anwendbar. Zunächst: Ist es möglich, PhantomJS zu erkennen, ohne darauf zu reagieren?

HTTP-Stapel

Wie Sie vielleicht wissen, basiert PhantomJS auf dem Qt-Framework . Die Art und Weise, wie Qt den HTTP-Stapel implementiert, hebt es von anderen modernen Browsern ab.

Schauen wir uns zunächst Chrome an, das die folgenden Header sendet:

PhantomJS

In PhantomJS sieht die gleiche HTTP-Anfrage jedoch folgendermaßen aus:

 
PhantomJS

Sie werden feststellen, dass sich die PhantomJS-Header in einigen subtilen Punkten von denen von Chrome (und, wie sich herausstellt, von allen anderen modernen Browsern) unterscheiden:

  • Der Host-Header erscheint zuletzt
  • Der Wert des Connection-Headers weist eine gemischte Groß- und Kleinschreibung auf.
  • Der einzige Accept-Encoding-Wert ist gzip
  • Der User-Agent enthält „PhantomJS“

Durch die Überprüfung dieser Abweichungen im HTTP-Header auf dem Server sollte es möglich sein, einen PhantomJS-Browser zu identifizieren.

Aber ist es sicher, an diese Werte zu glauben? Wenn ein Angreifer einen Proxy verwendet, um Header vor dem Headless-Browser umzuschreiben, könnte er diese Header so ändern, dass sie stattdessen wie ein normaler moderner Browser aussehen.

Es scheint, als wäre es kein Allheilmittel, dieses Problem ausschließlich auf dem Server zu lösen. Sehen wir uns also an, was mit der JavaScript-Umgebung von PhantomJS auf dem Client getan werden kann.

Clientseitige User-Agent-Prüfung

Wir können dem über HTTP übermittelten User-Agent-Wert möglicherweise nicht vertrauen, aber wie sieht es auf dem Client aus?

PhantomJS

Leider ist es ähnlich trivial, den User-Agent-Header und die Navigator.userAgent-Werte in PhantomJS zu ändern, sodass dies möglicherweise nicht ausreicht.

Plugins

navigator.plugins enthält ein Array von Plugins, die im Browser vorhanden sind. Typische Plugin-Werte sind Flash, ActiveX, Unterstützung für Java-Applets und der „ Default Browser Helper “, ein Plugin, das angibt, ob dieser Browser der Standardbrowser in OS X ist. Unseren Untersuchungen zufolge enthalten die meisten Neuinstallationen gängiger Browser mindestens ein Standard-Plugin – sogar auf Mobilgeräten.

Dies ist anders als bei PhantomJS, das keine Plugins implementiert und auch keine Möglichkeit bietet, eines hinzuzufügen (mithilfe der PhantomJS-API ).

Dann kann folgende Prüfung sinnvoll sein:

PhantomJS

Andererseits ist es ziemlich einfach, dieses Plugin-Array zu fälschen, indem man die PhantomJS-JavaScript-Umgebung ändert , bevor die Seite geladen wird .

Es ist auch nicht schwer, sich eine benutzerdefinierte Version von PhantomJS mit echten, implementierten Plug-Ins vorzustellen. Dies ist einfacher als es klingt, da das Qt-Framework, auf dem PhantomJS basiert, eine native API zur Implementierung von Plug-Ins bereitstellt.

Timing

Ein weiterer interessanter Punkt ist, wie PhantomJS JavaScript-Dialoge unterdrückt:

PhantomJS

Nach mehreren Messungen scheint es, dass, wenn der Warndialog innerhalb von 15 Millisekunden unterdrückt wird, der Browser wahrscheinlich nicht von einem Menschen gesteuert wird. Dieser Ansatz bedeutet jedoch, dass echte Benutzer mit einer Warnung belästigt werden, die sie manuell schließen müssen.

Globale Eigenschaften

PhantomJS 1.x stellt zwei Eigenschaften für das globale Objekt bereit:

PhantomJS

Diese Eigenschaften sind jedoch Teil einer experimentellen Funktion und können sich in Zukunft ändern.

Fehlende Funktionen der JavaScript-Engine

PhantomJS 1.x und 2.x verwenden derzeit veraltete WebKit-Engines. Dies bedeutet, dass es Browserfunktionen gibt, die in neueren Browsern vorhanden sind, in PhantomJS jedoch nicht. Dies erstreckt sich auf die JavaScript-Engine, wobei einige native Eigenschaften und Methoden in PhantomJS anders sind oder fehlen.

Eine solche Methode ist Function.prototype.bind, die in PhantomJS 1.x und älter fehlt. Das folgende Beispiel überprüft, ob Bind vorhanden ist und dass es in der Ausführungsumgebung nicht gefälscht wurde.

PhantomJS

Dieser Code ist etwas zu schwierig, um ihn hier im Detail zu erklären, aber Sie können in unserer Präsentation mehr darüber erfahren.

Stapeltraces

Von JavaScript-Code ausgelöste Fehler, die von PhantomJS über den Befehl „evaluieren“ ausgewertet werden, enthalten einen eindeutig identifizierbaren Stacktrace, anhand dessen wir den Headless-Browser identifizieren können.

Angenommen, PhantomJS ruft die Auswertung für den folgenden Code auf:

PhantomJS

Beachten Sie, dass in diesem Beispiel eine benutzerdefinierte Funktion „indexOfString()“ verwendet wird. Dies soll dem Leser als Übung überlassen werden, da die native Funktion „String.prototype.indexOf“ von PhantomJS so gefälscht werden kann, dass sie immer ein negatives Ergebnis zurückgibt.

Wie bringen Sie nun ein PhantomJS-Skript dazu, diesen Code auszuwerten? Eine Technik besteht darin, einige häufig verwendete DOM-API-Funktionen zu überschreiben, die wahrscheinlich aufgerufen werden. Beispielsweise überschreibt der folgende Code document.querySelectorAll, um den Stacktrace des Browsers zu überprüfen:

Zusammenfassung

In diesem Artikel haben wir uns 7 verschiedene Techniken zur Identifizierung von PhantomJS angesehen, sowohl auf dem Server als auch durch Ausführen von Code in der JavaScript-Clientumgebung von PhantomJS. Durch die Kombination der Erkennungsergebnisse mit einem starken Feedback-Mechanismus – beispielsweise durch Inaktivität einer dynamischen Seite oder Ungültigkeitserklärung des aktuellen Sitzungscookies – können Sie eine solide Hürde für PhantomJS-Besucher einführen. Bedenken Sie jedoch immer, dass diese Techniken nicht unfehlbar sind und ein erfahrener Gegner sie früher oder später überwinden wird.

Um mehr zu erfahren, empfehlen wir Ihnen, sich diese Aufzeichnung unserer Präsentation von AppSec USA 2014 ( Folien ) anzusehen. Wir haben außerdem ein GitHub-Repository mit Beispielimplementierungen – und möglichen Umgehungen – der hier vorgestellten Techniken zusammengestellt.

Danke fürs Lesen und viel Spaß bei der Jagd.

Mitwirkende

Sergey Shekyan @sshekyan
Ben Vinegar @bentlegen
Bei Zhang @ikarienator