Table des matières de l'article :
Introduction
Pixel Your Site (PYS) est l'une des extensions WordPress les plus populaires pour la gestion des pixels de suivi et l'intégration avec les principales plateformes publicitaires et analytiques. Elle permet de configurer et de gérer facilement des outils tels que Facebook Pixel, Google Analytics, TikTok Pixel, Google Ads et d'autres plateformes marketing, sans modifier manuellement le code de votre site. Grâce à sa configuration simple et à sa capacité à centraliser plusieurs systèmes de suivi, l'extension a acquis une grande popularité au sein de l'écosystème WordPress, avec plus de 500 000 installations actives et une présence importante, notamment sur les sites e-commerce et les projets de marketing digital.
Cette utilisation très répandue rend tout problème de sécurité dans le code du plugin particulièrement pertinent. Lors d'une analyse technique approfondie de son fonctionnement interne, une vulnérabilité a été découverte permettant à un attaquant d'injecter des domaines ou des chaînes de caractères arbitraires dans le code HTML généré par le site. Le plus préoccupant est que cette faille peut être exploitée par n'importe qui, sans authentification ni accès privilégié au site cible. Autrement dit, vous n'avez pas besoin d'être administrateur, utilisateur enregistré ou d'avoir un quelconque accès à la plateforme WordPress pour exploiter cette faille.
La situation devient encore plus problématique lorsque le site utilise un système de cache, une configuration extrêmement courante sur les sites WordPress en production. Les extensions de mise en cache comme WP Super Cache, W3 Total Cache, WP Rocket ou LiteSpeed Cache, ou encore les systèmes de mise en cache au niveau d'un CDN comme Cloudflare, stockent les pages HTML générées par le serveur afin de réduire la charge et d'améliorer les performances. Si une vulnérabilité existe, ce mécanisme peut transformer une entrée contrôlée par un attaquant, normalement limitée à une seule requête HTTP, en contenu persistant qui sera servi à tous les visiteurs suivants jusqu'à l'expiration du cache. Concrètement, une simple requête malveillante peut « empoisonner » la version en cache d'une page, rendant ainsi l'injection visible à des milliers d'utilisateurs, voire aux robots d'exploration des moteurs de recherche.
Comme l'a souligné Michele Genito, expert italien renommé de WordPress qui a mis le problème en lumière et divulgué ses détails techniques, cette vulnérabilité a des implications bien plus graves qu'il n'y paraît au premier abord. Bien que le rapport ait été transmis aux services de sécurité compétents, l'équipe Wordfence l'a rejeté, le jugeant non pertinent du point de vue de la sécurité. Selon cette évaluation, le comportement observé serait dû au fait que le champ Referer est intrinsèquement contrôlable par le client et ne constitue donc pas en soi une vulnérabilité.
Notre analyse technique démontre pourquoi cette interprétation est, à notre avis, incomplète et superficielle. Le problème ne réside pas simplement dans la possibilité de manipuler le référent — un fait connu depuis toujours dans le contexte HTTP — mais dans la combinaison de plusieurs facteurs architecturaux : l’utilisation directe d’entrées non fiables, leur exposition dans le balisage HTML servi à tous les utilisateurs via JavaScript intégré et, surtout, l’effet multiplicateur introduit par les systèmes de cache. C’est précisément cette combinaison qui transforme les données temporaires liées à chaque requête en contenu persistant, potentiellement exploitable dans des scénarios d’empoisonnement du cache et de manipulation du référencement.
Le mécanisme : comment PYS expose la TrafficSource dans le DOM
Le plugin génère une variable JavaScript globale pysOptions moyens wp_localize_script(), qui est rendu directement dans le code source HTML de chaque page :
<script id="pys-js-extra">
var pysOptions = {
...
"tracking_analytics": {
"TrafficSource": "google.com",
"TrafficLanding": "https://esempio.it/pagina/",
"TrafficUtms": { ... },
"TrafficUtmsId": { ... }
},
...
};
</script>
Le champ Source de trafic Contient le domaine référent du visiteur. Cette valeur est déterminée côté serveur par la fonction getTrafficSource() défini dans includes/functions-common.php et inséré dans le tableau des options dans includes/class-events-manager.php à la ligne 188 :
$options['tracking_analytics'] = array(
"TrafficSource" => getTrafficSource(),
"TrafficLanding" => sanitize_url($_COOKIE['pys_landing_page'] ?? $_SESSION['LandingPage'] ?? 'undefined'),
"TrafficUtms" => getUtms(),
"TrafficUtmsId" => getUtmsId(),
);
Le tableau est ensuite sérialisé dans le DOM :
wp_localize_script('pys', 'pysOptions', $data);
Analyse des fonctions vulnérables
Voici la fonction getTrafficSource() dans son intégralité :
function getTrafficSource () {
$referrer = "";
$source = "";
try {
if (isset($_SERVER['HTTP_REFERER'])) {
$referrer = $_SERVER['HTTP_REFERER']; // [1] Input non sanitizzato
}
$direct = empty($referrer);
$internal = $direct ? false : (substr($referrer, 0, strlen(site_url())) === site_url());
$external = !$direct && !$internal;
$cookie = sanitize_text_field(
!isset($_COOKIE['pysTrafficSource']) ? null : $_COOKIE['pysTrafficSource']
); // [2] Cookie controllabile dall'utente
$session = sanitize_text_field(
!isset($_SESSION['TrafficSource']) ? null : $_SESSION['TrafficSource']
);
if (!$external) {
$source = $cookie || $session ? $cookie ?? $session : 'direct';
} else {
$source = ($cookie && $cookie === $referrer)
|| ($session && $session === $referrer)
? $cookie ?? $session
: $referrer; // [3] Referrer usato direttamente
}
if ($source !== 'direct') {
$parse = parse_url($source);
if (isset($parse['host'])) {
return $parse['host']; // [4] Protezione parziale
} elseif ($source == $cookie || $source == $session) {
return $source; // [5] BYPASS: valore restituito senza parse_url
} else {
return defined('REST_REQUEST') && REST_REQUEST ? 'REST API' : "direct";
}
} else {
return defined('REST_REQUEST') && REST_REQUEST ? 'REST API' : $source;
}
} catch (Exception $e) {
return "direct";
}
}
Les points critiques
- [1] $_SERVER['HTTP_REFERER'] non nettoyé. L'en-tête HTTP Referer est entièrement contrôlable par le client. N'importe quelle valeur peut être envoyée via une simple requête curl.
- [2] Cookie pysTrafficSource contrôlable. Le cookie est défini côté client par le JavaScript propre au plugin, donc un attaquant peut lui attribuer n'importe quelle valeur.
sanitize_text_field()Supprime les balises HTML mais n'empêche pas l'insertion de domaines arbitraires ou de texte non HTML. - [3] Référent utilisé comme source. Lorsque le référent est externe et ne correspond pas aux valeurs du cookie/de la session, il est utilisé directement comme
$source. - [4] parse_url() comme protection partielle. Le code tente d'extraire uniquement l'hôte de la valeur, mais cette protection est facilement contournable.
- [5] Le contournement critique. Quand
$sourceLa valeur correspondant au cookie ou à la session est renvoyée intégralement sans transiter par celui-ci.parse_url()Cela signifie qu'une valeur arbitraire est stockée dans le cookie.pysTrafficSourcepeut se retrouver directement dans le code HTML.
L'attaque : Empoisonnement du cache + Empoisonnement SEO
Empoisonnement du cache + Empoisonnement SEO Il s'agit d'une technique d'attaque exploitant les systèmes de cache des sites web pour insérer du contenu malveillant ou manipulé dans les pages mises en cache. Un attaquant envoie une requête spécialement conçue qui modifie le code HTML généré par le serveur ; si la page est en cache à ce moment-là, la version manipulée sera servie à tous les visiteurs suivants. Lorsque le contenu injecté inclut des domaines indésirables ou des références externes, l'attaque peut devenir une attaque de type Intoxication SEO, influençant la façon dont les moteurs de recherche interprètent la page et associant le site victime à des domaines de spam, à des logiciels malveillants ou à du contenu indésirable.
Conditions préalables
- Le site cible utilise Pixel Your Site (toute version actuelle).
- Le site dispose d'un système de cache actif (WP Super Cache, W3 Total Cache, WP Rocket, LiteSpeed Cache, Cloudflare Page Cache, Varnish Cache, etc.).
- La page cible n'est pas encore en cache (ou le cache vient d'expirer).
Scénario d'attaque de base
# L'attaccante visita la pagina nel momento in cui la cache viene rigenerata
curl -s -H "Referer: https://sito-pornografico.xxx"
https://sitovittima.it/pagina-importante/ > /dev/null
Désormais, la réponse HTML est mise en cache avec le Source de trafic configuré sur le domaine de l'attaquant. Tous les visiteurs suivants, y compris les robots des moteurs de recherche, verront :
var pysOptions = {
"tracking_analytics": {
"TrafficSource": "sito-pornografico.xxx",
...
}
};
Scénario avancé : Injection de cookies
# L'attaccante imposta il cookie con testo arbitrario
curl -s -b "pysTrafficSource=testo-offensivo-qualsiasi"
-H "Referer: testo-offensivo-qualsiasi"
https://sitovittima.it/ > /dev/null
Étant donné que le référent correspond au cookie, le code entre dans la branche [5] et renvoie la valeur sans validation du format de domaine. sanitize_text_field() Il supprime les balises HTML, mais les chaînes de texte non HTML passent sans être modifiées.
Impacts concrets
- Référencement négatif. Un concurrent ou un attaquant peut associer des domaines blacklistés (pornographie, logiciels malveillants, jeux d'argent) au site de la victime. Les robots des moteurs de recherche analysent l'intégralité du contenu de la page, y compris le JavaScript intégré.
- Sabotage de la concurrence. Le fait d'entrer dans le domaine d'un concurrent direct crée de fausses associations, que les moteurs de recherche pourraient potentiellement interpréter comme des relations entre les deux sites.
- Dégradation légère. Même s'il ne s'agit pas d'une faille XSS (parce que
wp_localize_script(valeurs d'échappement), le contenu est visible dans le code source HTML. - Persistance. L'attaque persiste pendant toute la durée de vie du cache. Avec les configurations courantes (cache de 12 à 24 heures), une seule requête malveillante peut corrompre la page pendant une journée entière.
Pourquoi ce n'est pas « juste un référent »
La réponse habituelle à ce type de problèmes est : « Le référent est toujours contrôlable, ce n’est pas une vulnérabilité. » Cette affirmation est correcte prise isolément, mais elle ignore le contexte.
- Le référent est conservé dans le code HTML généré.
- Le cache amplifie l'impact.
- Aucune authentification requise.
- Le coût de l'attaque est nul.
Solution proposée
if ($source !== 'direct') {
$parse = parse_url($source);
if (isset($parse['host'])) {
$host = $parse['host'];
} elseif ($source == $cookie || $source == $session) {
$parse_stored = parse_url($source);
$host = isset($parse_stored['host']) ? $parse_stored['host'] : $source;
} else {
return defined('REST_REQUEST') && REST_REQUEST ? 'REST API' : "direct";
}
$host = preg_replace('/[^a-zA-Z0-9.-]/', '', $host);
if (empty($host) || !preg_match(
'/^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?.)+[a-zA-Z]{2,}$/',
$host
)) {
return "direct";
}
return $host;
}
Quels changements
- Suppression du pontage. Les valeurs issues des cookies et des sessions sont également traitées via
parse_url(). - Assainir les personnages. Un
preg_replaceSupprime les caractères invalides d'un nom de domaine. - Validation du format. Une expression régulière vérifie si le résultat est un domaine syntaxiquement valide.
Conclusions
La vulnérabilité réside dans la combinaison de trois facteurs : des entrées non fiables (Referer et cookie) persistantes dans la sortie HTML via wp_localize_script(), l'absence de validation de format et d'amplification de la mise en cache qui rend l'injection persistante.
Il ne s'agit pas d'une attaque XSS traditionnelle, mais classer une vulnérabilité uniquement sur la base de la taxonomie classique de l'OWASP revient à ignorer les véritables vecteurs d'attaque. L’empoisonnement du cache combiné à l’empoisonnement SEO est un risque réel, documenté et, dans ce cas précis, facilement exploitable.
Un plugin installé sur des centaines de milliers de sites devrait considérer toutes les données provenant du client comme potentiellement malveillantes, en particulier lorsque ces données se retrouvent dans le balisage servi à tous les visiteurs.