Développez une première extension pour détecter les services qui vous traquent en ligne

Les pisteurs pistés 16
Accès libre
image dediée
Crédits : alphaspirit/iStock
Développeurs TUTO
David Legrand

Après vous avoir fait développer votre première application en C#, nous avons décidé de nous intéresser à une solution plus orientée « web ». Ainsi, nous allons vous guider dans la création d'une petite extension fonctionnant sur différents navigateurs, détectant d'éventuels « trackers ».

Comme nous avons pu le voir dans un précédent article, il existe de nombreuses manières pour vous pister en ligne. Nombre d'entre elles reposent sur une solution invisible pour l'internaute : les requêtes effectuées par son navigateur. 

En effet, lorsque vous vous rendez sur un site, de nombreux éléments sont chargés : la page HTML et son contenu, des images, des feuilles de style (CSS), du JavaScript, et tout un tas de ressources qui peuvent venir de l'extérieur. Presque tous ces éléments peuvent permettre de vous pister, d'une manière ou d'une autre.

Mais que le site sur lequel vous vous rendez sache ce que vous faites est une chose, que d'autres services puissent le faire sans que vous en ayez connaissance en est une autre. Et pourtant, ils sont parfois des dizaines, voire des centaines à le faire à travers la quasi-totalité des sites que vous visitez au quotidien. 

Pour vous aider à mieux comprendre ces mécaniques et cerner l'ampleur du phénomène, voici un petit guide dans lequel nous allons développer une extension qui listera les requêtes effectuées par des tiers à travers les sites que vous visitez.

Notre dossier sur le pistage des internautes en ligne :

Une extension de navigateur, comment ça se compose ?

La plupart des navigateurs se reposent désormais sur le trio HTML / CSS / JS pour la composition de leurs extensions. Ainsi, il suffit d'en connaître les rudiments et de savoir composer une page web basique avec un peu de JavaScript pour disposer d'un module fonctionnel. Quelques minutes suffisent à obtenir un premier résultat.

Chrome a été l'un des premiers à faire ce choix, payant, qui a depuis été normalisé à travers les WebExtensions. L'objectif est surtout d'offrir une base commune pour les API qui permettent de gérer la communication entre les différents éléments composant l'extension avec le navigateur, les icônes, les notifications, etc.

Firefox vient de migrer vers les WebExtensions dans sa version 57 Quantum après un long travail auprès de sa base de développeurs. Microsoft Edge se dirige vers ce choix, mais propose pour le moment assez peu d'extensions. 

Dans ce premier guide, nous allons donc nous focaliser sur Chrome sur lequel nous avons un peu plus de recul, ce qui aura l'avantage de produire une extension également fonctionnelle pour Opera et Vivaldi. Firefox ayant fait le choix d'assurer une compatibilité avec les appels spécifiques à Chrome, notre code y fonctionnera, avec quelques bugs graphiques.

Pour suivre ce guide, vous n'aurez pas forcément besoin de savoir développer ou de connaître un langage en particulier, seulement de savoir manipuler des fichiers et utiliser un éditeur de texte. Quelques rudiments en JavaScript peuvent vous aider. Mozilla dispose d'un guide assez complet sur la question.

Aucun outil spécifique n'est nécessaire si ce n'est un navigateur à jour (Chrome pour nos captures et explications) et un éditeur de texte comme Atom, Notepad++Sublime Text ou Visual Studio Code.

Création et installation de votre première extension

Pour commencer à développer une extension, il nous faut créer un répertoire dans lequel nous allons placer un fichier de manifeste. Celui-ci sert à déclarer le nom de l'extension, sa version, son icône, ses autorisations, etc. Il est au format JSON (JavaScript Object Notation), un standard qui a l'avantage d'être assez facile à mettre en forme.

Pour cette première étape, nous allons faire simple et déclarer l'extension « Tracking Trackers » en version 0.1. Pour l'icône, nous utiliserons celle-ci qui est accessible gratuitement. Attention néanmoins à bien penser à créditer son auteur si vous venez à distribuer votre création, comme l'exige Flaticon (et le respect du travail d'autrui).

Téléchargez l'ensemble des images au format PNG (16, 24, 32, 64, 128, 256 et 512 pixels) dans un répertoire nommé « ico » au sein de celui votre extension. Renommez-les sous la forme radar-16.png, radar-32.png, etc. Créez ensuite le fichier manifest.json à la racine, avec le contenu suivant :

Pour le moment, notre extension n'aura aucun effet, si ce n'est celui d'afficher son badge. Pour le vérifier vous pouvez l'installer en vous rendant dans l'outil de gestion des extensions via un clic droit dans la zone qui leur est dédiée ou à travers le menu : Plus d'outils > Extensions. Il est également accessible directement via l'adresse chrome://extensions/.

Ici, cochez la case Mode développeur pour faire apparaître de nouvelles options, puis cliquez sur Charger l'extension non-empaquetée. Vous pourrez alors indiquer l'emplacement de votre extension. Une fois ceci fait, elle apparaîtra avec les différents paramètres du manifeste. 

D'un clic vous pourrez également la désactiver, la supprimer, accéder à ses détails, ouvrir le répertoire qui la contient ou même l'actualiser si jamais vous mettez à jour les fichiers qui la compose. Cette zone contiendra par la suite des éléments importants dans la phase de debug, comme nous le verrons un peu plus loin. 

Extension Chrome Tracking Trackers

L'icône sera visible à droite de la barre d'adresse, vous pourrez la déplacer et cliquer dessus, même si cela fera pour le moment apparaître un menu assez peu utile. Voilà, vous avez développé votre première extension !

Background et Badge

Découvrons maintenant un élément qui va nous être utile : le concept du Background. Pour faire simple, lorsqu'une extension est lancée, elle peut être composée de plusieurs éléments, donc de fichiers exécutés en tâche de fond de manière constante. Celles-ci doivent être déclarées dans le manifeste dans un élément background.

Il en existe deux types : les Background Pages et les Event Pages qui peuvent prendre la forme de pages HTML classiques ou de fichiers JavaScript seuls. La différence se situe dans un élément de leur déclaration : la variable persistent qui est false dans le cas d'une Event Page.

Cela signifie qu'elle ne sera utilisée que lorsque cela est nécessaire plutôt que de manière constante, Google recommande d'ailleurs d'utiliser le plus possible cette forme désormais. Cela n'est pas toujours possible, notamment avec certaines API dont nous aurons besoin plus tard, nous opterons donc pour une Background Page nommée background.js. Créez ce fichier dans un répertoire « js » situé à la racine de celui de l'extension.

Nous allons l'utiliser pour modifier le badge. C'est le nom donné à un petit élément qui peut être ajouté par-dessus l'icône de l'extension pour afficher du texte. Il dépend de l'API browserAction, qui nécessite elle aussi une déclaration dans le manifeste. Tous les éléments évoqués dans la documentation sont optionnels, nous nous en passerons donc.

Le fichier manifest.json comporte désormais le contenu suivant :

Le badge se compose de deux éléments qui peuvent être modifiés ou récupérés : son texte et sa couleur. Cela passe par quatre méthodes (fonctions) JavaScript :

chrome.browserAction.setBadgeText()
chrome.browserAction.getBadgeText()

chrome.browserAction.setBadgeBackgroundColor()
chrome.browserAction.getBadgeBackgroundColor()

La documentation précise que ces actions peuvent être spécifiques à un onglet en particulier et que la couleur est précisée sous la forme d'une variable de type string (du texte) comprenant le nom d'une couleur en anglais ou un code hexadécimal. Un tableau d'entiers peut également être utilisé pour préciser un code RGBA (voir cet outil). 

Commençons par un premier essai, où nous allons simplement afficher « 42 » lors du chargement de l'extension, sur un fond de couleur noire :

Une fois tous les fichiers enregistrés, retournez dans le gestionnaire d'extension et cliquez sur Actualiser (CTRL+R). Vous verrez bien apparaître le badge de couleur noire avec « 42 » d'affiché.

Comment savoir s'il y a un problème ?

Dans toute conception d'application, fût-elle composée de fichiers HTML / CSS / JS, il est important de pouvoir détecter un éventuel problème afin de le corriger. Pour cela, Chrome et les autres navigateurs proposent des outils pensés pour simplifier la vie des développeurs. 

Dans le cas d'une extension comme celle que nous venons de créer, le problème est que l'élément principal n'est pas visible. Il existe néanmoins un raccourci permettant d'accéder aux outils de développement. Pour en tester le fonctionnement, il suffit de remplacer chrome par chrme dans la première ligne de background.js

Dans le gestionnaire d'extensions, une fois l'actualisation effectuée, on voit que le badge ne s'affiche pas. Pour voir un message d'alerte correspondant à l'erreur rencontrée, il faut cliquer sur le lien Page en arrière-plan dans la section Examiner les vues. Les outils de développement s'ouvrent alors et dans l'onglet Console on voit ce message :

Uncaught ReferenceError: chrme is not defined
at background.js:1

Cela signifie que le navigateur a rencontré un élément nommé chrme à la première ligne du fichier background.js qui ne correspond à rien qu'il connaisse. Cela nous permet d'identifier précisément où se trouve l'erreur dans le code afin de corriger le bug. Notez que cette console peut également être intéressante pour afficher des informations.

L'accès aux requêtes de l'utilisateur : permission nécessaire

Nous allons d'ailleurs le vérifier via notre prochaine étape : afficher toutes les requêtes qui passent à travers le navigateur via l'API webRequest. Cela va nous permettre de voir celles qui sont effectuées depuis un site pour le compte d'autres services et donc identifier des sources de pistage potentielles.

Mais voilà, accéder à l'ensemble des requêtes d'un utilisateur est un élément sensible qui nécessite une demande de permission spécifique. Attention d'ailleurs en tant qu'utilisateur car un simple message s'affiche lors de l'installation d'une extension, et rare sont ceux qui comprennent le niveau d'information que cela permet de récupérer. 

Avec une autorisation sur webRequest, une extension peut potentiellement avoir accès à l'ensemble de votre navigation, et même des requêtes qui sont effectuées par l'ensemble des sites que vous visitez. Heureusement, certaines protections sont en place pour éviter les plus gros abus comme le fait de récupérer votre mot de passe lors d'une requête sur un site qui n'utiliserait pas HTTPS par exemple.

La demande de permission peut se faire sur certaines URL en particulier, dans notre cas il nous faudra accès à l'ensemble de la navigation. La permission déclarée dans le manifeste prendra alors la forme suivante :

Réagir à un évènement

La documentation de webRequest laisse plusieurs possibilités concernant les requêtes à surveiller. Elles peuvent l'être au moment où elles s'apprêtent à être envoyées, lorsqu'elles le sont, après une authentification, la réponse, en cas d'erreur ou seulement au moment où elles sont complétées. C'est ce dernier cas qui nous intéresse.

L'API met à notre disposition un évènement onCompleted, c'est-à-dire un élément qui ne sera activé (fired) que lorsqu'une condition bien spécifique sera rencontrée. Ici, lorsqu'une requête est complétée par le navigateur. Dans ce cas, une méthode de rappel (callback) sera exécutée, contenant un objet qui pourra être utilisé.

Dans le cas de l'évènement qui nous intéresse cet objet contiendra de nombreuses informations sur la requête qui sont détaillées dans la documentation : URL, numéro d'onglet, type, IP du serveur, etc. Pour l'afficher, nous utiliserons la console à travers la méthode console.log(), ce qui aura l'avantage de faciliter l'analyse du contenu de chaque requête, avec la possibilité de les filtrer via un petit moteur de recherche.

Là aussi nous avons la possibilité d'effectuer un tri directement dans le code, mais nous récupèrerons l'ensemble des requêtes, le tout tenant sur quatre lignes à ajouter à notre fichier background.js :

Une fois les fichiers enregistrés et l'extension actualisée, il suffit d'ouvrir la console dans les outils de développement pour voir de nombreuses requêtes être effectuées.

En effet, un onglet n'a pas besoin d'être actif pour qu'un site le soit. En y regardant de plus près, on verra d'ailleurs que ces requêtes sont rarement pour le site en question, mais plutôt pour des services tiers. C'est là que le pistage potentiel se trouve :

Requête Console Kimetrak

S'attarder sur les requêtes de domaines tiers

Justement, nous allons maintenant ne relever que les requêtes qui concernent un (sous-)domaine qui n'est pas identique à celui du site qui l'a initié. Comme on peut le voir dans la capture ci-dessus, cela est assez simple à identifier puisque l'on dispose de deux champs : initiator et url qui permettent la comparaison.

Nous utiliserons pour cela le constructeur URL de JavaScript qui nous permet de convertir un simple champ texte (string) en URL manipulable. Nous pourrons ainsi récupérer que le domaine/sous-domaine (host). Attention, comme on peut le voir en analysant les différentes requêtes relevées, certaines n'ont pas de champ initiator, une spécificité qu'il faudra prendre en compte. Dans ce cas, cette variable sera alors considérée comme undefined.

Cela donne le code suivant :

Ici on vérifie que thisRequest.initiator est définie avant de créer deux variables initHost et urlHost qui contiennent seulement la partie de l'URL qui nous intéresse (host). Si elles sont différentes (!=) on affiche la requête dans la console.

Comme on peut le voir, cela concerne de très nombreuses requêtes. On peut aisément les comptabiliser et afficher le résultat dans le badge à travers une variable initialisée au lancement de l'extension qui sera incrémentée à chaque requête détectée. Cela demande assez peu de modifications de notre code :

Comme on peut le voir, outre l'initialisation de la variable counter à 0, nous avons ajouté son incrément via l'opérateur ++ et nous modifions le texte du badge comme nous l'avons vu précédemment. La valeur counter étant un nombre entier, il nous faut la convertir sous la forme d'un texte (string) via la méthode toString().

Quels sites participent le plus à votre pistage potentiel ?

Comme on peut le voir, cela va vite dès que l'on a quelques onglets ouverts, que l'on navigue ou non sur de nouveaux sites. Lors de nos essais, il ne nous a fallu que quelques minutes pour atteindre le millier de requêtes vers des domaines tiers détectés, certains sites ayant une certaine frénésie en la matière.

On peut alors chercher à distinguer ceux qui abusent plus ou moins avec un test simple : pendant cinq minutes après le lancement de l'extension on relève l'ensemble des trackers potentiels, et on effectue un décompte site par site. Une fois les cinq minutes dépassées, on affiche le résultat dans la console.

Notez que ce n'est pas parce qu'une requête est effectuée vers un domaine tiers qu'elle pose problème. Par exemple un site qui stocke des éléments dans le service S3 d'Amazon, ou intègre des modules sur un domaine qui lui appartient mais n'est pas identique au sien ne le fait pas pour vous pister en ligne. Néanmoins, cela nous permet d'obtenir ici un premier indicateur intéressant. 

Ici, les changements sont plus importants. Tout d'abord on intègre deux nouvelles variables : result qui prend la forme d'un objet et isUpdatable qui nous permet de savoir si le délai de cinq minutes, soit 300 000 millisecondes, est terminé ou non. Il s'agit cette fois d'un booléen qui est à true par défaut.

Ensuite on déclare timer, qui nous permettra de déclencher une série d'actions une fois le délai de cinq minutes dépassé. On affichera le résultat final dans la console et on mettra isUpdatable sur false afin d'empêcher la mise à jour du résultat.

Ainsi, isUpdatable devient l'une des conditions à vérifier pour que le badge soit incrémenté et qu'une requête soit affichée dans la console. Le résultat étant un objet, il peut être utilisé comme un tableau. Ainsi, on l'utilise pour stocker un entier pour chaque domaine initiateur, correspondant au nombre de domaines tiers rencontrés.

La première fois, la valeur n'est pas initialisée (!result[initHost]), on lui attribue donc la valeur 1. Ensuite, comme il s'agit d'un entier on peut simplement l'incrémenter.

Finalisation et pistes d'améliorations

Pour finir ce petit exercice, nous allons ajouter une dernière fonctionnalité : la possibilité de réinitialiser le compteur afin d'effectuer un nouveau test sans avoir à redémarrer l'extension. Pour cela nous allons utiliser le clic sur l'icône, qui peut être détecté avec un évènement : browserAction.onClicked.

Il faut rajouter la portion de code suivante à la fin de Background.js :

Ainsi, lorsqu'un clic sur l'icône est détecté, on réinitialise timer, counter, result et isUpdatable puis on relance un décompte de cinq minutes avant qu'un nouveau résultat soit affiché. On en profite pour remettre à zéro le contenu de la console avec la méthode console.clear(), avec un message indiquant que l'on a lancé une nouvelle session.

Bien entendu, ce premier outil est encore largement perfectible. Outre un allègement du code, on pourrait par exemple effectuer une comparaison sans prendre une requête quand seul le sous-domaine diffère. On pourrait également récupérer une liste des domaines tiers détectés pour chaque site plutôt qu'un simple décompte, ne les comptabiliser que la première fois qu'ils sont détectés pour un domaine, etc. 

Vous retrouverez le code source de cet exercice sur GitHub sous licence GPLv3. Le site vous permet de récupérer l'ensemble des fichiers, et donc de tester simplement l'extension dans votre navigateur si vous le souhaitez.


chargement
Chargement des commentaires...