Pourquoi les URL doublement encodées apparaissent dans les systèmes réels
Une URL doit être encodée au bon moment et uniquement pour la partie pertinente. Les problèmes commencent lorsque les développeurs encodent la chaîne complète manuellement puis la passent dans un outil qui l’encode à nouveau.
Les causes courantes incluent :
- encoder un paramètre de requête avant de le donner à URLSearchParams
- encoder une URL de redirection complète puis l’intégrer dans une autre URL
- le middleware du framework réécrire des valeurs déjà traitées
- le code frontend envoyer des entrées encodées à un backend qui les encode à nouveau
- les règles de proxy ou WAF transformant les composants de requête de manière inattendue
Un exemple visuel simple
Supposons que la valeur originale soit :
/docs/My File.pdf
Encodage unique correct pour un fragment de chemin :
/docs/My%20File.pdf
Après un deuxième passage, cela devient :
/docs/My%2520File.pdf
Le serveur peut maintenant chercher un fichier contenant littéralement %20 dans son nom plutôt qu’un espace.
Comment se présente le problème de double encodage d’URL
La partie la plus difficile est que l’URL semble toujours « encodée », donc les équipes déboguent d’abord la mauvaise couche. Les symptômes dépendent du contexte.
Symptômes typiques en production
|
Symptôme |
Ce que l’on voit |
Cause probable |
|
Redirection cassée |
L’utilisateur arrive sur une page d’erreur ou une mauvaise route |
La cible de redirection est encodée deux fois |
|
Requête API invalide |
Le backend reçoit une valeur de filtre corrompue |
Le client et le serveur encodent tous les deux |
|
Non-correspondance de signature |
La vérification HMAC ou URL signée échoue |
L’encodage a changé après la signature |
|
Fichier ou actif manquant |
Le chemin se résout incorrectement |
Le segment de chemin encodé a été ré-encodé |
|
Contournement de règle de sécurité ou faux positif |
Le filtre se comporte de manière incohérente |
Différentes étapes de décodage entre les couches |
Pourquoi les URL imbriquées sont risquées
Un piège classique apparaît avec les URL de retour :
https://app.example.com/login?next=https://site.example.com/account?tab=settings
Si l’URL interne doit devenir une valeur de paramètre, elle doit être encodée correctement une seule fois. Mais beaucoup d’applications l’encodent d’abord manuellement, puis un routeur ou un client HTTP l’encode à nouveau. Cela crée des flux de callback malformés et des journaux difficiles à lire.
Erreurs de double encodage URL que font les développeurs
L’erreur la plus courante est de ne pas comprendre quelle API attend des données brutes et laquelle attend des données encodées. Les bonnes bibliothèques veulent généralement des valeurs non encodées et gèrent l’échappement en interne.
Exemple en JavaScript
Incorrect :
const next = encodeURIComponent("https://site.example.com/account?tab=settings");
const url = "/login?next=" + encodeURIComponent(next);
Cela produit une valeur transformée deux fois.
Mieux :
const next = "https://site.example.com/account?tab=settings";
const url = "/login?next=" + encodeURIComponent(next);
Exemple avec des constructeurs de requêtes
Mauvais modèle :
- encoder manuellement name=John Doe
- passer le résultat dans un constructeur de requête
- le constructeur de requête encode % à nouveau
Bon modèle :
- passer John Doe brut
- laisser une seule couche de confiance construire la chaîne de requête finale
Comment décoder les URL doublement encodées en toute sécurité
Le décodage est simple seulement lorsqu’on sait combien de fois la valeur a été transformée. Le décodage répété aveugle est dangereux, surtout dans le code sensible à la sécurité, car il peut modifier des littéraux intentionnels ou aider des charges utiles à contourner la validation.
Approche pratique de décodage
- Inspecter la valeur de requête brute depuis les journaux ou un proxy de débogage.
- Décoder une fois et comparer le résultat.
- Si des séquences de pourcentage subsistent là où des caractères simples devraient être, vérifier si un second décodage est justifié.
- Normaliser uniquement à une limite contrôlée.
- Valider après normalisation, pas avant.
Exemple :
Original : hello%2520world
Décoder une fois : hello%20world
Décoder deux fois : hello world
Cela montre une valeur encodée deux fois. Mais ne pas en faire une règle aveugle de « décoder deux fois » pour chaque requête.
Liste de contrôle de débogage plus sûre
- capturer l’URL brute exacte
- séparer le chemin, la requête et le fragment
- tester une étape de décodage à la fois
- vérifier quelle couche a effectué chaque transformation
- confirmer si la valeur était censée rester encodée
Prévenir le double encodage URL par conception
Le meilleur correctif est la discipline architecturale, pas le patching.
Règles qui fonctionnent en pratique
- encoder à la limite où l’URL finale est assemblée
- conserver les valeurs internes brutes aussi longtemps que possible
- ne jamais encoder manuellement avant de passer des données à un constructeur d’URL de confiance
- ne pas décoder et ré-encoder sans raison spécifique
- documenter si les helpers attendent des entrées brutes ou encodées
- signer les URL uniquement après que la forme canonique finale est prête
Bonne convention d’équipe
Choisir un seul endroit responsable de la transformation des URL :
- routeur frontend
- helper d’URL du backend
- bibliothèque cliente API
- couche de normalisation de la passerelle
Une fois la responsabilité claire, le double échappement diminue fortement.
Cas limites de double encodage dans les paramètres imbriqués
Certains cas ressemblent à des bugs mais sont en réalité intentionnels. Par exemple, si une URL est intégrée dans une autre comme donnée, l’encodage doit préserver les séparateurs comme ?, = et & dans la valeur imbriquée. L’essentiel est que l’URL imbriquée ne soit encodée qu’une seule fois pour son rôle de valeur de paramètre, pas répétitivement à chaque saut.
Exemple : paramètre de redirection
/auth?return_to=https%3A%2F%2Fapp.example.com%2Fdashboard%3Fpage%3D2
C’est normal. Cela devient un problème uniquement si la même valeur se transforme ensuite en :
/auth?return_to=https%253A%252F%252Fapp.example.com%252Fdashboard%253Fpage%253D2
Cette deuxième version indique généralement un traitement supplémentaire.
Gérer les valeurs déjà transformées dans les API et le middleware
Les piles de frameworks mélangent souvent la gestion automatique et manuelle. Un proxy inverse peut normaliser une fois, le framework peut décoder dans les paramètres de route, et un middleware personnalisé peut appliquer un autre passage. Le résultat est une incohérence entre les journaux, l’entrée du gestionnaire et les appels de services en aval.
Où auditer en premier
- règles de réécriture du proxy inverse
- paramètres de normalisation CDN ou WAF
- extraction des paramètres de route
- helpers de redirection
- constructeurs de requêtes des SDK tiers
- filtres de sécurité personnalisés
Un court audit sur ces couches révèle généralement pourquoi une valeur a changé plus que prévu.
Conclusion
Le double encodage est rarement un mystère de bas niveau. C’est généralement un échec de coordination entre des couches qui essaient toutes d’aider avec la même URL. Le correctif consiste à traiter l’encodage comme une étape à responsabilité unique, à conserver les valeurs brutes en interne et à normaliser soigneusement en périphérie.
Lorsqu’une URL semble incorrecte, ne pas se contenter de la corriger avec un décodage supplémentaire. Retracer le chemin complet de la valeur : où elle a commencé, qui l’a encodée, qui l’a encodée à nouveau, et si le composant final était un chemin, une valeur de requête ou une URL imbriquée. Cette approche résout le bug sans créer un système plus fragile.