Prévisualiser des sites externes en toute sécurité — implémentation d'un proxy, d'une sanitisation et d'un sandbox iframe
- engineering
- security
- nextjs
- iframe
Résumé de cet article
- Charger un site externe dans une iframe sur notre propre origine expose au risque que le JS externe manipule notre session
- La parade consiste en une défense en profondeur : « proxy avec protection SSRF + suppression du JS externe + CSP restrictive + sandbox iframe »
- Un sandbox trop restrictif peut aussi bloquer nos propres scripts. Oublier
allow-scriptsest un piège fréquent qui empêche la fonctionnalité de marcher
Les tests A/B de HeatMapX incluent une fonctionnalité permettant de « prévisualiser l'apparence après modification (Variant B), appliquée à la page cible ». Techniquement, il s'agit d'afficher en toute sécurité le site externe d'un utilisateur dans une iframe sur notre propre domaine — une opération étonnamment délicate. Nous partageons ici la conception ainsi que le piège dans lequel nous sommes réellement tombés.
Pourquoi une implémentation naïve est dangereuse
Si l'on récupère telle quelle le HTML d'un site externe et qu'on le sert depuis notre propre origine (par exemple heatmapx.com), les balises <script> ou attributs onclick= contenus dans ce HTML seraient exécutés avec les privilèges de notre propre origine. Cela pourrait permettre de lire les cookies ou la session de connexion. Il faut absolument l'éviter.
Structure de la défense en profondeur
Nous superposons donc les couches suivantes.
1. Proxy avec protection anti-SSRF
Nous validons l'URL de destination et rejetons tout ce qui n'est pas http/https. Nous bloquons également localhost, les adresses IP privées et les points de terminaison de métadonnées cloud (comme 169.254.169.254). Les redirections ne sont pas suivies automatiquement : avec redirect: 'manual', chaque saut est revalidé individuellement afin d'éviter tout renvoi vers le réseau interne.
2. Suppression du JS externe (sanitisation)
Nous supprimons du HTML récupéré les balises <script>, les gestionnaires en ligne on*= ainsi que le schéma javascript:. La prévisualisation n'a en effet pas besoin d'exécuter le JS du site client (nous acceptons sciemment que les SPA ne puissent pas être reproduites à l'identique).
3. CSP restrictive et base href
Nous insérons une balise <base href> juste après <head> afin de résoudre les chemins relatifs des images et du CSS, tout en ajoutant à la réponse une CSP restrictive qui « n'exécute pas le JS externe ». Les images et le CSS restent autorisés, car nécessaires à l'affichage.
4. Sandbox iframe
Enfin, nous isolons l'affichage en ajoutant l'attribut sandbox à l'iframe.
Le piège rencontré : un sandbox trop restrictif
L'aperçu fonctionne en injectant dans le HTML récupéré un petit script maison qui applique les modifications (le changeSet), puis en l'exécutant dans l'iframe pour changer l'apparence. Or, le sandbox de l'iframe de prévisualisation ne comportait que allow-same-origin, sans allow-scripts.
En conséquence, notre script maison ne s'exécutait pas non plus, ce qui provoquait un bug où la page d'origine s'affichait sans qu'aucune modification ne soit appliquée. L'iframe de l'éditeur, elle, fonctionnait correctement avec allow-scripts allow-same-origin : la cause venait bien d'une incohérence de configuration entre les deux.
Le correctif était simple : aligner le côté prévisualisation sur allow-scripts allow-same-origin. Nous en avons profité pour ajouter un test de non-régression qui vérifie l'attribut sandbox de l'iframe.
sandbox="allow-same-origin" → sandbox="allow-scripts allow-same-origin"
Ce que nous en retenons
- L'intégration de contenu externe doit se penser en « défense en profondeur » : ne jamais se reposer sur une seule mesure.
- Le
sandboxn'est pas sûr du simple fait d'être présent : l'essentiel est de n'autoriser précisément que les permissions nécessaires. Trop restrictif, il bloque jusqu'à nos propres fonctionnalités. - Lorsque « deux chemins effectuent le même traitement » (éditeur et prévisualisation), il faut toujours vérifier que leurs attributs de sécurité sont alignés. Corriger l'un sans l'autre casse l'autre.
Conclusion
La prévisualisation sécurisée d'un site externe s'obtient grâce à une défense en profondeur combinant protection SSRF, suppression du JS externe, CSP restrictive et sandbox iframe. Et les permissions du sandbox posent problème qu'elles soient insuffisantes ou excessives. L'oubli du allow-scripts dans ce cas en est un exemple typique.