Escalade dans la traque en ligne, le cas Eulerian


This time it's war

Récemment, le quotidien Libération a annoncé en fanfare que ce serait le premier média à supprimer les traqueurs publicitaires de son site web pour ses abonné·e·s (ce que je ne suis pas), oubliant que d'autres le font déjà, comme Next INpact ou Canard PC, voire pire que certains médias dangereusement radicalisés comme Reflets ne déposent pas de mouchards que vous possédiez un abonnement ou pas. Mais surtout, cette annonce est totalement fausse.

Ce que ce bidonnage a laissé apparaître, c'est que Libération passe par les services d'Eulerian, une sympathique société proposant une solution de mesure publicitaire, dont le PDG bullshiteur en chef déclare sans ambages :

La collecte des données marketing est de plus en plus difficile à réaliser en raison du déploiement des adblockers, d’ITP ou encore du traitement dégradé des cookies tiers. Les choix technologiques d’Eulerian permettent à nos clients de disposer de données exhaustives et de qualité pour mener à bien leurs projets d’attribution marketing et de segmentation de données dans le respect total des législations en vigueur.

Et de fait, Libération a mis les traqueurs sous le cyber-tapis pour reprendre l'expression de Reflets. Ils (et d'autres, comme la SNCF – voir la liste des domaines passant par les services d'Eulerian ou bien une archive en date du 11 novembre 2019) passent par le DNS afin de masquer leurs cochonneries. Prenons l'exemple de la SNCF. L'importun script est chargé depuis le domaine v.oui.sncf, comme nous l'apprend le code source de la page web en https://oui.sncf/ :

<!--begineulerian-->
		<script type="text/javascript">
		(fuction(){var d=document,l=d.location;if(!l.protocol.indexOf('http')){var o=d.createElement('script'),a=d.getElementsByTagName('script')[0],cn=parseInt((new Date()).getTime()/3600000);o.type='text/javascript';o.async='async';o.defer='defer';
		o.sc='//v.oui.sncf/content/vsc-fr/8lL.QlYVeQ7BL6AqQORYg_FeHeIQMaObMRxsXxGG0g--/'+cn+'.js';
		a.prentNode.insertBefore(o,a);}})();
		</script>
		<!--endeulerian-->

Si l'on regarde ce que donne une requête DNS pour ce domaine, on obtient :

		$ dig v.oui.sncf

		; <<>> DiG 9.11.5-P4-5.1+b1-Debian <<>> v.oui.sncf
		;; global options: +cmd
		;; Got answer:
		;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28329
		;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1

		;; OPT PSEUDOSECTION:
		; EDNS: version: 0, flags:; udp: 4096
		;; QUESTION SECTION:
		;v.oui.sncf.                    IN      A

		;; ANSWER SECTION:
		v.oui.sncf.             3600    IN      CNAME   voyages-sncf.eulerian.net.
		voyages-sncf.eulerian.net. 7200 IN      CNAME   vsc.eulerian.net.
		vsc.eulerian.net.       7200    IN      A       109.232.194.20
		vsc.eulerian.net.       7200    IN      A       109.232.194.10

		;; Query time: 521 msec
		;; SERVER: 127.0.0.1#53(127.0.0.1)
		;; WHEN: dim. nov. 10 16:55:46 CET 2019
		;; MSG SIZE  rcvd: 128

La fourberie est de passer par un système de nom canonique (CNAME) : v.oui.sncf est l'alias de voyages-sncf.eulerian.net, lui-même alias de vsc.eulerian.net. Le but est de neutraliser les bloqueurs publicitaires habituels. Typiquement uBlock ou uMatrix ne bloquent pas par défaut les sous-domaines du domaine du site visité ou bien le système de blocage via Unbound que j'utilise est également impuissant face à ces CNAME.

Comment faire pour bloquer ces mouchards ? Une première solution est de passer par un pare-feu. La deuxième est de muscler la configuration d'Unbound.

Pare-feu

Par chance, Eulerian ne possède que peu d'adresses IP : 2048 adresses IPv4 en tout et pour tout contenues dans le préfixe: 109.232.192.0/21 soit les adresses de 109.232.192.0 à 109.232.199.255.

Il est donc possible de facilement bloquer tout ça via le pare-feu de votre système ou mieux de votre routeur en y indiquant soit le préfixe, soit la plage d'adresses. Étant donné le nombre de pare-feu existants, je vous laisse consulter le manuel du votre. À noter que le désavantage de cette méthode est de laisser fuiter des requêtes DNS et de nécessiter de surveiller les IPs annoncées par Eulerian, si jamais cela devait changer. La technique est également plus laborieuse à mettre en place pour les sociétés annonçant plus de préfixes (typiquement Critéo annonce 10 préfixes IPv4 et 5 IPv6 au moment de l'écriture de ce billet).

Unbound

Le blocage habituel par Unbound n'est pas fonctionnel, il va donc falloir passer par une autre méthode. La documentation suggère de passer par des stub-zone, c'est à dire une zone pour laquelle l'on va indiquer le serveur faisant autorité à utiliser. Le problème étant que nous n'avons pas un tel serveur et qu'Unbound n'interroge pas par défaut le localhost (s'il le faisait, il ne serait plus un résolveur récursif, ce que nous souhaitons éviter). Heureusement d'autres options sont disponibles, en particulier auth-zone, qui permet de fournir un fichier de zone, exactement comme si nous étions les gestionnaires des domaines d'Eulerian. Du coup, l'idée est de configurer Unbound pour se servir des données d'un fichier de zone en lieu et place du mécanisme de résolution habituel.

Eulerian possède au moins 3 domaines : eulerian.com, eulerian.fr et eulerian.net. Les mouchards semblent tous servis via eulerian.net, nous allons donc nous concentrer sur ce domaine. Nous bloquerons par précaution les autres via une habile manœuvre de copier/coller. À noter que le site web corporate de la société ne sera par conséquent plus disponible sur les machines utilisant le résolveur.

Création du fichier de zone

Créer, dans le répertoire de votre choix, un fichier eulerian.net.zone. Le remplir avec :

$TTL 10800
		eulerian.net.	IN SOA localhost. nobody.invalid. (
			1
			3600
			1200
			604800
			10800
			)

Dans le détail, on indique la durée de vie par défaut des enregistrements ($TTL 10800) puis le début de l'autorité pour la zone (avec le nom du serveur faisant autorité (localhost.), le mail de l'admin (nobody@invalid. – le @ est un caractère particulier dans le DNS, on le remplace donc par un point), puis plein de nombres (le 1er est le numéro de série de la zone, le reste, je vous laisse le découvrir 🙂) et c'est tout, le reste du domaine sera vide. On peut s'amuser à ajouter des enregistrements, par un exemple un TXT qui explique que le domaine est bloqué, en ajoutant à la suite du fichier de zone :

*.eulerian.net. IN TXT "Domaine bloqué"

Ne reste plus qu'à créer 2 copies de ce fichier nommées eulerian.com.zone et eulerian.fr.zone. et d'y changer le domaine.

Configuration d'Unbound

À la suite de votre fichier unbound.conf ou dans un fichier .conf spécifique, typiquement dans /etc/unbound/unbound.conf.d/ sous Debian et dérivés, ajouter :

auth-zone:
			name: "eulerian.net."
			zonefile: "/chemin/vers/eulerian.net.zone"
		auth-zone:
			name: "eulerian.fr."
			zonefile: "/chemin/vers/eulerian.fr.zone"
		auth-zone:
			name: "eulerian.com."
			zonefile: "/chemin/vers/eulerian.com.zone"

On définit donc 3 zones sur lesquelles nous décrétons avoir l'autorité et pour chacune d'entre elles, on dit à Unbound d'utiliser les fichiers correspondants. On sauvegarde et on relance Unbound et normalement, le blocage est effectif :

		$ dig v.oui.sncf

		; <<>> DiG 9.11.5-P4-5.1+b1-Raspbian <<>> v.oui.sncf
		;; global options: +cmd
		;; Got answer:
		;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 1153
		;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1

		;; OPT PSEUDOSECTION:
		; EDNS: version: 0, flags:; udp: 4096
		;; QUESTION SECTION:
		;v.oui.sncf.                    IN      A

		;; ANSWER SECTION:
		v.oui.sncf.             3600    IN      CNAME   voyages-sncf.eulerian.net.

		;; AUTHORITY SECTION:
		eulerian.net.           10800    IN      SOA     localhost. nobody.invalid. 1 3600 1200 604800 10800

		;; Query time: 250 msec
		;; SERVER: 127.0.0.1#53(127.0.0.1)
		;; WHEN: dim. nov. 10 23:42:11 CET 2019
		;; MSG SIZE  rcvd: 137

Et un domaine au hasard :

		$ dig le.rgpd.pour.les.nuls.eulerian.net

		; <<>> DiG 9.11.5-P4-5.1+b1-Raspbian <<>> le.rgpd.pour.les.nuls.eulerian.net
		;; global options: +cmd
		;; Got answer:
		;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 30147
		;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1

		;; OPT PSEUDOSECTION:
		; EDNS: version: 0, flags:; udp: 4096
		;; QUESTION SECTION:
		;le.rgpd.pour.les.nuls.eulerian.net. IN A

		;; AUTHORITY SECTION:
		eulerian.net.           10800   IN      SOA     localhost. nobody.invalid. 1 3600 1200 604800 10800

		;; Query time: 0 msec
		;; SERVER: 127.0.0.1#53(127.0.0.1)
		;; WHEN: dim. nov. 10 23:43:31 CET 2019
		;; MSG SIZE  rcvd: 122

Dans ce deuxième exemple, on voit bien le drapeau aa dans la réponse, ce qui signifie que le serveur qui a répondu (le localhost) fait autorité (aa signifiant Authoritative Answer). Il n'est pas présent dans la précédente requête car la question porte sur v.oui.sncf. et que la réponse a été fournie par les serveurs de noms de la SNCF : voyages-sncf.eulerian.net. est le nom canonique de ce domaine. Le reste a été intercepté par nos soins.

Eulerian ne sont pas les seuls à pratiquer cette mise sous tapis des mouchards. Critéo pratique également la chose (via le domaine dnsdelegation.io, que je vous invite à bloquer la même façon) et d'autres boîtes s'y mettent aussi. En clair, les vendeurs de traqueurs semblent avoir déclaré la guerre aux bloqueurs de publicités et les développeurs de navigateurs – qui commencent à hausser la voix sur le sujet et agir en conséquence (voir notamment Mozilla qui travaille dans ce sens) – le tout bien sûr en s'essuyant le derrière avec le RGPD (on avait l'habitude sur ce point, la CNIL fait un peu pareil.). Cela reste inquiétant, car cette escalade dans la dégueulasserie laisse pour l'instant une majorité de personnes démunies pour bloquer ces traqueurs.

Addendum

Premièrement, sous Debian et dérivés, où Unbound à un profil AppArmor actif, il est préférable de placer les fichiers de zone dans un répertoire accessible au résolveur, typiquement /var/lib/unbound/.

Ensuite, il est possible de simplifier la configuration d'Unbound en ne créant qu'un seul fichier de zone et le donnant au résolveur pour chaque domaine à bloquer. Cela donne pour le fichier de zone :

$TTL 10800
		@	IN SOA localhost. nobody.invalid. (
			1
			3600
			1200
			604800
			10800
			)

Et la configuration d'Unbound :

auth-zone:
			name: "eulerian.net."
			zonefile: "/var/lib/unbound/blocked.zone"
		auth-zone:
			name: "eulerian.fr."
			zonefile: "/var/lib/unbound/blocked.zone"
		auth-zone:
			name: "eulerian.com."
			zonefile: "/var/lib/unbound/blocked.zone"

Enfin, auth-zone est un ajout récent d'Unbound. Il a été ajouté dans la version 1.7.0 (afin de pouvoir mettre en œuvre le RFC 7706). Pour les versions plus anciennes, il va falloir passer par le paramètre stub-zone. On oublie alors le fichier de zone et on met dans la configuration d'Unbound :

stub-zone:
			name: "eulerian.net."
			stub-addr: 127.0.0.1
		stub-zone:
			name: "eulerian.fr."
			stub-addr: 127.0.0.1
		stub-zone:
			name: "eulerian.com."
			stub-addr: 127.0.0.1

Si jamais Unbound sort une erreur ou un avertissement indiquant qu'il n'est pas content de l'emploi de l'adresse 127.0.0.1, changez là par une adresse privée inutilisée, par exemple dans 192.168.0.0/16.

Merci à Aeris, Yanik Cawidrone et Lapineige pour leurs remarques.