De nos jours, de nombreux incidents de sécurité sur le Web impliquent des processus automatisés. Les attaques par extraction de données Web, réutilisation de mots de passe et fraude au clic sont menées par des acteurs malveillants qui tentent d’imiter de véritables utilisateurs et cherchent donc à donner l’impression que leurs requêtes proviennent d’un navigateur. En tant que propriétaire de site Web, vous voulez vous assurer de servir des utilisateurs humains. En tant que fournisseur de services Web, vous souhaitez que l’accès automatisé à votre contenu passe par votre API plutôt que par l’extraction de données depuis votre interface Web, plus lourde et moins stable.
En supposant que vous ayez mis en place des vérifications de base pour les visiteurs utilisant des outils similaires à cURL, l’étape suivante consiste logiquement à vous assurer que les visiteurs utilisent de véritables navigateurs dotés d’une interface utilisateur, et non des navigateurs sans interface graphique tels que PhantomJS et SlimerJS.
Dans cet article, nous allons présenter quelques techniques permettant d’identifier les visites effectuées avec PhantomJS. Nous avons choisi de nous concentrer sur PhantomJS, car il s’agit de l’environnement de navigateur sans interface graphique le plus populaire, mais bon nombre des concepts que nous aborderons s’appliquent également à SlimerJS et à d’autres outils.
REMARQUE : sauf mention explicite, les techniques présentées dans cet article s’appliquent aussi bien à PhantomJS 1.x qu’à PhantomJS 2.x. Première question : est-il possible de détecter PhantomJS sans même lui répondre ?
Pile HTTP
Comme vous le savez peut-être, PhantomJS repose sur le framework Qt. La manière dont Qt implémente la pile HTTP le distingue des autres navigateurs modernes.
Commençons par examiner Chrome, qui envoie les en-têtes suivants :

Dans PhantomJS, en revanche, la même requête HTTP se présente comme suit :

Vous remarquerez que les en-têtes de PhantomJS diffèrent subtilement de ceux de Chrome (et, en fait, de ceux de tous les autres navigateurs modernes) sur quelques points :
- L’en-tête Host apparaît en dernier
- La valeur de l’en-tête Connection combine majuscules et minuscules
- La seule valeur d’Accept-Encoding est gzip
- Le champ User-Agent contient « PhantomJS »
En recherchant ces anomalies dans les en-têtes HTTP côté serveur, il devrait être possible d’identifier un navigateur PhantomJS.
Mais peut-on vraiment se fier à ces valeurs ? Si un attaquant utilise un proxy pour réécrire les en-têtes en amont du navigateur sans interface graphique, il pourrait les modifier afin de le faire passer pour un navigateur moderne classique.
Il semble que traiter ce problème uniquement côté serveur ne soit pas une solution miracle. Voyons donc ce qu’il est possible de faire côté client, en utilisant l’environnement JavaScript de PhantomJS.
Vérification du User-Agent côté client
Nous ne pouvons peut-être pas nous fier à la valeur User-Agent transmise via HTTP, mais qu’en est-il côté client ?

Malheureusement, il est tout aussi facile de modifier l’en-tête User-Agent et les valeurs de navigator.userAgent dans PhantomJS ; cela pourrait donc ne pas suffire.
Extensions
navigator.plugins contient un tableau des plugins présents dans le navigateur. Les plugins courants incluent Flash, ActiveX, la prise en charge des applets Java et « Assistant de navigateur par défaut », un plugin qui indique si ce navigateur est le navigateur par défaut sous OS X. D’après nos recherches, la plupart des nouvelles installations de navigateurs courants incluent au moins un plugin par défaut, même sur mobile.
Ce n’est pas le cas de PhantomJS, qui ne prend en charge aucun plug-in et ne permet pas non plus d’en ajouter (à l’aide de l’API PhantomJS).
La vérification suivante peut alors s’avérer utile :

En revanche, il est assez facile de falsifier ce tableau de plugins en modifiant l’environnement JavaScript de PhantomJS avant le chargement de la page.
Il n’est pas non plus difficile d’imaginer une version personnalisée de PhantomJS intégrant de véritables plugins fonctionnels. C’est plus simple qu’il n’y paraît, car le framework Qt sur lequel repose PhantomJS fournit une API native permettant d’implémenter des plugins.
Synchronisation
Un autre point intéressant concerne la manière dont PhantomJS supprime les boîtes de dialogue JavaScript :

Après plusieurs mesures, il semble que si la boîte de dialogue d’alerte est fermée en moins de 15 millisecondes, le navigateur n’est probablement pas contrôlé par un humain. Mais cette approche implique de déranger les vrais utilisateurs avec une alerte qu’ils devront fermer manuellement.
Propriétés globales
PhantomJS 1.x expose deux propriétés sur l’objet global :

Cependant, ces propriétés font partie d’une fonctionnalité expérimentale et sont susceptibles d’être modifiées à l’avenir.
Fonctionnalités manquantes du moteur JavaScript
PhantomJS 1.x et 2.x utilisent actuellement des moteurs WebKit obsolètes, ce qui signifie que certaines fonctionnalités disponibles dans les navigateurs plus récents n’existent pas dans PhantomJS. Cela concerne également le moteur JavaScript : certaines propriétés et méthodes natives sont différentes ou absentes dans PhantomJS.
L’une de ces méthodes est Function.prototype.bind, qui est absente de PhantomJS 1.x et des versions antérieures. L’exemple suivant vérifie que bind est présente et qu’elle n’a pas été falsifiée dans l’environnement d’exécution.

Ce code est un peu trop complexe pour être expliqué en détail ici, mais vous pouvez en savoir plus en consultant notre présentation.
Traces de pile
Les erreurs générées par le code JavaScript exécuté par PhantomJS via la commande evaluate contiennent une trace de pile identifiable de manière unique, qui nous permet de reconnaître le navigateur sans interface graphique.
Par exemple, supposons que PhantomJS appelle evaluate sur le code suivant :

Notez que cet exemple utilise une fonction personnalisée indexOfString(), dont l’implémentation est laissée en exercice au lecteur, car PhantomJS peut falsifier le comportement de la méthode native String.prototype.indexOf afin qu’elle renvoie toujours un résultat négatif.
Maintenant, comment faire pour qu’un script PhantomJS évalue ce code ? Une technique consiste à redéfinir certaines fonctions fréquemment utilisées de l’API DOM, qui sont susceptibles d’être appelées. Par exemple, le code ci-dessous redéfinit document.querySelectorAll afin d’examiner la trace de la pile d’appels du navigateur :

Résumé
Dans cet article, nous avons examiné 7 techniques différentes permettant d’identifier PhantomJS, aussi bien côté serveur qu’en exécutant du code dans l’environnement JavaScript client de PhantomJS. En combinant les résultats de la détection à un mécanisme de réaction efficace — par exemple, en rendant une page dynamique inopérante ou en invalidant le cookie de session actuel — vous pouvez créer un obstacle de taille pour les visiteurs utilisant PhantomJS. Gardez toutefois à l’esprit que ces techniques ne sont pas infaillibles et qu’un adversaire expérimenté finira par les contourner.
Pour en savoir plus, nous vous recommandons de regarder cet enregistrement de notre présentation à l’AppSec USA 2014 (diapositives). Nous avons également créé un dépôt GitHub regroupant des exemples d’implémentation — ainsi que des moyens de contournement possibles — des techniques présentées ici.
Merci de nous avoir lus, et bonne chasse !
Contributeurs
Sergey Shekyan – @sshekyan
Ben Vinegar – @bentlegen
Bei Zhang – @ikarienator
À propos de l’auteur
Articles de blog associés

F5 NGINXaaS pour Google Cloud : des applications résilientes et évolutives
Découvrez comment NGINXaaS s’intègre à Google Cloud pour renforcer les performances et la sécurité, et préparer vos applications critiques à un avenir propulsé par l’IA.
L’intégration de F5 Web Application Firewall (WAF) à AWS est disponible dès aujourd’hui
En faisant appel à un partenaire de déploiement AWS WAF tel que F5, vous pouvez renforcer la sécurité de vos applications web grâce à des règles personnalisées qui bloquent les schémas d’attaque courants, comme les injections SQL ou les attaques par scripts intersites (XSS).
Appliquer le principe de Peter Parker à l’inspection TLS
« Un grand pouvoir implique de grandes responsabilités. » Si l’on en trouve des échos chez Voltaire et Churchill, cette citation est surtout connue grâce aux bandes dessinées Spider-Man, où elle est attribuée à l’oncle Ben. Bien entendu, si cette phrase est si répandue dans la culture populaire, c’est notamment parce qu’elle peut s’appliquer à une multitude de situations et de sujets, y compris à l’inspection TLS.
Le NetOps a besoin de défenseurs, pas d’adversaires
Il est important de reconnaître que les équipes NetOps ne sont pas toujours celles qui font obstacle au déploiement de la dernière nouveauté, application ou service. Les freins à la rapidité sont souvent dus au fait que les organisations n’adoptent pas tous les principes du DevOps lorsqu’elles cherchent à transformer leurs opérations informatiques.
Mise à jour des services applicatifs : sécurité, rapidité et visiteurs suspects
Il est de nouveau temps de nous pencher sur les services applicatifs que les entreprises utilisent réellement pour rendre leurs applications plus rapides et plus sûres. On constate notamment une hausse du recours aux services de protection contre les bots, ainsi que la progression lente mais régulière de HTTP/2. Bien qu’il ne s’agisse pas d’une application…
Les templates prennent le contrôle du cloud
Les modèles s’imposent dans le cloud. Lisez le blog de F5 pour découvrir comment l’infrastructure en tant que code et les API déclaratives automatisent la mise à disposition des applications.