Sécuriser l’encodage et le décodage des URL en Java
Jura Gorohovsky
14 août 2023
0 minutes de lectureL’encodage des URL permet de s’assurer qu’une URL ne contient que des caractères valides, afin que le serveur destinataire puisse l’interpréter correctement. Selon la norme RFC 3986, les URI (qui englobent les URL) ne contiennent qu’un ensemble limité de caractères : des chiffres, des lettres et quelques symboles graphiques, tous issus du jeu de caractères ASCII.
Si une URL contient des caractères qui ne font pas partie de cet ensemble limité, ils doivent être encodés en pourcentage. L’encodage en pourcentage consiste à convertir un caractère en une représentation hexadécimale de huit bits sur deux chiffres, précédée du caractère d’échappement %. Le même traitement doit être appliqué aux délimiteurs (par exemple &, /, ? ou #) présents en ASCII lorsqu’ils sont utilisés hors de leur position structurelle prévue dans l’URL.
À l’inverse, le décodage d’URL consiste à convertir une URL encodée en pourcentage pour lui rendre sa forme d’origine et rétablir les éventuels caractères non standard.
Il est important de comprendre que l’encodage n’est pas du chiffrement. Le chiffrement consiste à modifier des informations à l’aide d’une clé secrète, afin que seules les parties concernées puissent accéder aux informations d’origine. L’encodage d’URL, lui, ne vise pas à dissimuler des parties de l’URL à un observateur extérieur. Il permet plutôt au serveur destinataire de l’interpréter facilement et sans ambiguïté, et évite que l’utilisateur du client qui construit et envoie l’URL puisse la manipuler.
Ne pas encoder une URL peut entraîner divers problèmes. Par exemple, votre application peut ne pas parvenir à construire l’URL à envoyer au serveur. Le serveur destinataire peut également ne pas réussir à l’analyser correctement et renvoyer une erreur. Autre risque : une URL non encodée peut être altérée, exposant votre application à des menaces potentielles pour sa sécurité.
Chaque langage de programmation fournit une ou plusieurs API pour encoder et décoder les URL. Cet article aborde Java, l’importance de l’encodage et du décodage des URL, ainsi que les bonnes pratiques à adopter.
Que sont l’encodage et le décodage d’URL en Java ?
En Java, l’encodage et le décodage des URL sont importants dans les cas d’utilisation suivants :
Traiter des données libres saisies par un visiteur dans un formulaire HTML, par exemple un formulaire de recherche.
Construire, à partir du code, des appels à une API externe en ajoutant des paramètres de requête à une URL de base.
Construire des appels à une passerelle d’API qui achemine ensuite les requêtes vers des services internes.
Une URL a la structure suivante :

En général, il n’est pas nécessaire d’encoder l’URL entière. Bien sûr, le chemin peut contenir des espaces, par exemple dans des noms de fichiers importés par les utilisateurs. Il existe même le Punycode pour les noms d’hôte. Toutefois, dans la plupart des cas, vous contrôlez l’hôte et le chemin : il vous suffit donc d’encoder individuellement les parties de l’URL qui contiennent des données variables (c’est-à-dire les valeurs des paramètres d’une chaîne de requête).
Il existe un cas particulier où vous devez encoder l’URL entière : lorsqu’elle est transmise comme paramètre dans la chaîne de requête d’une autre URL.
Mettre en œuvre un encodage et un décodage d’URL fiables et sécurisés en Java
Pour mieux comprendre l’encodage et le décodage des URL en Java, examinons deux classes couramment utilisées dans les applications Java pour encoder et décoder les paramètres de chaîne de requête.
Comment encoder des URL en Java
Pour appliquer l’encodage en pourcentage aux valeurs des paramètres de chaîne de requête dans une application Java, vous utilisez généralement la classe java.net.URLEncoder et sa méthode encode().
Voici précisément ce que fait la méthode encode() :
Elle conserve tous les caractères alphanumériques, tels que
aàz,AàZ,0à9,ainsi que les caractères spéciaux.,-,*et_.Elle convertit le caractère espace en signe plus
+.Tous les autres caractères sont encodés en pourcentage.
Cette méthode a été conçue pour préparer les données issues de formulaires HTML avant leur envoi, en les convertissant au format MIME application/x-www-form-urlencoded, adapté à l’encodage des valeurs des paramètres de requête d’URL.
La méthode encode() existe sous trois formes surchargées :
1. encode(String s, String enc) vous permet de définir explicitement le schéma d’encodage sous forme de chaîne (UTF-8 est recommandé). Vous pouvez utiliser cette surcharge, mais notez qu’elle peut déclencher une exception vérifiée UnsupportedEncodingException. Votre code doit donc la gérer à l’aide d’une déclaration @throws ou d’un bloc try/catch. Notez également que les littéraux de chaîne présentent un risque de faute de frappe :
2. encode(String s, Charset charset) est disponible depuis Java 10 et constitue la meilleure surcharge à ce jour. Vous utilisez une constante pour UTF-8 (StandardCharsets.UTF_8), ce qui élimine les risques de faute de frappe lors de la spécification de l’encodage et ne déclenche aucune exception vérifiée. Vous n’avez donc pas à les gérer pour compiler votre code :
3. encode(String s) est la surcharge la plus ancienne et elle est obsolète dans OpenJDK 17. Évitez de l’utiliser : elle recourt à l’encodage par défaut de la plateforme sur laquelle s’exécute la machine virtuelle Java (JVM), qui n’est pas nécessairement UTF-8.
La méthode URLEncoder.encode() présente une particularité : elle encode une espace avec le signe plus plutôt qu’avec %20, probablement parce qu’elle suit la description des chaînes de requête d’une ancienne norme. C’est pourquoi les développeurs modifient parfois le résultat de encode() pour remplacer le signe plus par %20 afin de représenter une espace :
Par exemple, supposons que vous deviez rechercher des dépôts à l’aide de l’API REST GitHub. GitHub propose de nombreux filtres de recherche pour affiner les résultats par langage, taille du dépôt et visibilité. Par exemple, une recherche sur "user:defunkt forks:>100" renvoie tous les dépôts de l’utilisateur defunkt qui comptent au moins cent forks. Vous pouvez utiliser ces filtres dans les appels d’API, mais vous devez les inclure dans le paramètre de requête q :
Dans ce code, searchQuery contient des filtres de recherche qui permettent de trouver tous les dépôts Java publics ayant entre 100 et 1 000 étoiles et ayant été mis à jour depuis 2018. La valeur de cet exemple est codée en dur, mais elle pourrait aussi provenir d’un fichier, d’une base de données ou d’une saisie directe de l’utilisateur dans une application web ou mobile.
Pour construire url, ce code concatène trois chaînes :
L’URL de base de l’API, le délimiteur de chaîne de requête
?, le paramètreqet son délimiteur d’affectation=: "https://api.github.com/search/repositories?q=".searchQuery, qui correspond à la valeur du paramètreq, est encodé en pourcentage avecURLEncode().Les autres paramètres de la chaîne de requête n’ont pas besoin d’être encodés, car ils sont codés en dur et ne contiennent aucun caractère non autorisé.
Lors de l’exécution de ce code, l’URL obtenue est https://api.github.com/search/repositories?q=language%3AJava+stars%3A100..1000+pushed%3A%3E2018-01-01+is%3Apublic&per_page=10&sort=stars&order=desc. La valeur de q est encodée en la nguage%3AJava+stars%3A100..1000+pushed%3A%3E2018-01-01+is%3Apublic : : est remplacé par %3A, les espaces par + et > par %3E.
Comment décoder des URL en Java
Le décodage explicite des paramètres de requête d’URL est moins fréquent, car de nombreux frameworks, dont Spring Boot, le prennent en charge automatiquement.
Si vous n’utilisez pas de framework, la méthode à suivre dépend de ce que vous souhaitez faire ensuite.
Vous recevez probablement l’URL pour déterminer les actions à effectuer, par exemple interroger des données ou réacheminer la requête vers un autre service. Dans ce cas, votre logique de traitement consiste sans doute à analyser chaque paramètre de requête séparément. Vous pouvez alors commencer par analyser l’URL, extraire la chaîne de requête, puis décoder chaque valeur de paramètre individuellement.
Pour le décodage, vous pouvez utiliser java.net.URLDecoder.decode() afin de décoder les caractères encodés en pourcentage :
Ici, encodedUrl contient une URL de recherche Google dont les paramètres de requête ont été encodés en pourcentage par le navigateur. Ce code crée un nouvel objet uri de type URI afin d’extraire la chaîne de requête complète. Cet objet fournit une méthode appelée getRawQuery(), qui renvoie uniquement la chaîne de requête, avec toutes les valeurs de paramètre encore encodées :
Le code divise ensuite la chaîne de requête brute au niveau du délimiteur &, ce qui produit un tableau de paires paramètre/valeur. Chaque paire est transformée : le paramètre reste inchangé tandis que la valeur est décodée. Enfin, toutes les paires transformées sont regroupées dans une liste :
Une fois les paramètres séparés et associés à leurs valeurs respectives, vous pouvez appliquer la logique nécessaire pour les valider et définir les étapes suivantes de votre application.
Bonnes pratiques pour la gestion des URL en Java
Lorsque vous manipulez des URL en Java, gardez à l’esprit plusieurs bonnes pratiques pour assurer leur traitement correct et éviter les problèmes.
N’omettez pas l’encodage des URL
Si vous omettez d’encoder vos URL, attendez-vous à l’imprévu : exceptions à l’exécution ou réponses incompréhensibles des serveurs que vous essayez de joindre.
Reprenons l’exemple de l’appel à l’API GitHub et voyons ce qui se passe si vous n’encodez pas les paramètres de recherche :
Notez que, dans cet exemple de code, sendGetRequest(url) encapsule la construction d’une requête HTTP à l’aide de l’API de création de requêtes de Java 11 :
Si vous exécutez ce code sans encoder searchQuery, il échoue à l’exécution, car le constructeur URI ne peut pas créer d’objet URI à partir d’une chaîne contenant une espace non encodée :
Mais si vous tenez absolument à envoyer cette requête, que pouvez-vous faire ? Vous pouvez essayer l’API Scanner, qui accepte un objet URL (plutôt qu’un objet URI) et l’utilise pour lire un flux d’entrée :
Cette méthode fonctionne. La requête d’API atteint sa destination, mais renvoie l’erreur 400 Bad Request, qui indique probablement une erreur côté client, par exemple une syntaxe de requête mal formée :
Rotem Bar montre très bien comment des URL non encodées peuvent être altérées lors d’échanges REST entre services.
Parmi les scénarios décrits par Rotem où une altération d’URL est possible, il y a le cas où un service :
récupère des paramètres dans une URL provenant du client ;
crée une nouvelle URL pour appeler un autre service.
Voici un exemple de code qui reproduit ce scénario :
Comme vous pouvez le constater, avec une valeur non triviale telle que celle utilisée pour le paramètre q, l’absence d’encodage de l’URL finit tôt ou tard par vous poser problème. Même si vous avez la chance d’obtenir une réponse favorable du serveur, rien ne garantit qu’elle corresponde à vos attentes : vous avez peut-être simplement exploité, sans le vouloir, une vulnérabilité du serveur.
Puisque nous parlons de vulnérabilités, l’absence d’encodage des paramètres d’URL peut facilement en provoquer. Nous abordons ce sujet plus en détail dans l’article "Développeurs, veuillez encoder vos URL".
Le service prend deux paramètres, key et user, et valide correctement l’utilisateur. En revanche, key n’est pas validé et est repris tel quel lors de la création d’une nouvelle URL. Aucun des paramètres de requête de cette nouvelle URL n’est encodé.
Lorsque ce service reçoit une entrée légitime, il crée une nouvelle URL comme prévu :
Mais que se passe-t-il si un acteur malveillant envoie une URL dont le paramètre key a été altéré ?
Voici ce qui se passe :
La valeur de
keytransmise par le client est55s72502010a&user=admin#. Le service ajoute cette clé à l’URL sans l’encoder.Le service valide le paramètre
usercomme d’habitude, etlegituserest toujours considéré comme légitime, parce qu’il l’est effectivement.Comme le paramètre
keyd’origine contient un caractère&qui sert de séparateur entre les paramètres dans la chaîne de requête de l’URL, l’URL obtenue contient deux paramètres —key=55s72502010aetuser=admin— qui sont deux sous-chaînes de la clé envoyée par le client. Comme vous pouvez le constater, même si le service a validé l’utilisateurlegituser, l’URL obtenue usurpe l’identité d’un autre utilisateur, potentiellement doté de privilèges plus élevés, qui a ainsi contourné la validation.Comme le paramètre
keyd’origine se termine par le caractère#, qui sert de séparateur de fragment dans les URL, la partie&user=legituserde l’URL d’origine envoyée par le client est reléguée dans la section fragment et effectivement ignorée.
Vous obtenez ainsi une URL qui représente un utilisateur sans rapport avec la requête et non vérifié : il s’agit d’une attaque par élévation de privilèges.
Comment vous défendre contre cette attaque ? Vous pourriez valider la valeur de key en plus de valider l’utilisateur user. Toutefois, vous souhaiterez peut-être déléguer cette validation à un autre service dans la chaîne de traitement de la requête. Dans ce cas, le plus simple consiste à encoder les valeurs des paramètres lors de la construction d’une nouvelle URL :
Une fois cette modification apportée, voyons ce qui se passe si quelqu’un tente d’envoyer une valeur key malveillante :
La valeur encodée de key (key=55s72502010a%26user%3Dadmin%23) empêche la tentative d’élévation de privilèges, car & est encodé en pourcentage sous la forme %26 et # sous la forme %23. L’URL obtenue est transmise au service suivant avec le nom d’utilisateur correct. Le service suivant peut renvoyer une erreur après avoir tenté, sans succès, d’interpréter le paramètre key altéré, mais une réponse d’erreur vaut toujours mieux qu’une attaque.
Utilisez des bibliothèques standard pour encoder et décoder les URL
En Java, pour l’encodage en pourcentage des paramètres de requête, vous utilisez probablement la plupart du temps URLEncoder.encode() et URLDecoder.decode(), des outils fiables. Ils sont disponibles par défaut dans tous les projets Java.
Vous pouvez également utiliser la bibliothèque Open Web Application Security Project (OWASP) Java Encoder. Bien que son objectif principal soit la validation des entrées dans différents contextes, elle inclut également la méthode Encode.forUriComponent() pour encoder les valeurs des paramètres d’URL et les chemins REST. Cette bibliothèque est un bon choix si votre application Java doit afficher en toute sécurité des URL envoyées par les clients, ainsi que leurs composants, dans différents contrôles d’interface utilisateur.
Cela dit, selon le framework que vous utilisez, il se peut que vous n’ayez pas besoin de décoder explicitement les données, ou que vous deviez procéder autrement :
Dans Spring ou Spring Boot, l’annotation
@RequestParamappliquée à un paramètre de contrôleur permet le décodage automatique de sa valeur.Dans une application GWT, vous utilisez probablement les méthodes définies dans la classe `com.google.gwt.http.client.URL` de GWT.
Android dispose d’une API distincte de création d’URL qui inclut l’encodage des paramètres de requête.
Si vous utilisez des bibliothèques tierces pour encoder et décoder des URL, pensez à rechercher les vulnérabilités connues. Si vous en trouvez, mettez ces bibliothèques à jour dès que des correctifs de sécurité sont disponibles.
Validez et assainissez les URL avant de les utiliser dans votre application
L’encodage des URL n’est qu’une partie de la solution. Si votre application web récupère des données auprès des utilisateurs via des paramètres d’URL, puis les renvoie à l’application web, vous devez vous assurer que ces données sont inoffensives avant de les réinjecter dans le contexte du navigateur.
Cela signifie que vous devez valider les données utilisateur, y compris celles transmises via des URL, côté serveur, en vérifiant leur syntaxe et leur sémantique. Utilisez des listes d’autorisation : les listes de blocage sont plus sujettes aux erreurs et aux oublis.
De plus, si votre application permet aux utilisateurs d’envoyer du code HTML, vous devez mettre en place un assainissement du HTML. Une bibliothèque couramment utilisée à cette fin en Java est OWASP Java HTML Sanitizer.
Détectez les URL non sécurisées avec Snyk Security Extension for IntelliJ IDEA
L’encodage et le décodage des URL doivent s’inscrire dans un ensemble plus large de pratiques de sécurité, notamment l’encodage et l’échappement des données et la validation des entrées. Écrire du code sécurisé est essentiel. Détecter les failles de sécurité le plus tôt possible dans le cycle de développement réduit à la fois le coût de leur correction et l’impact des incidents de sécurité.
Si vous développez en Java avec IntelliJ IDEA, vous pouvez installer l’extension Snyk Security.
Snyk Security détecte les problèmes, les met en évidence dans l’éditeur de code et vous aide à corriger les vulnérabilités de sécurité connues dans :
Votre propre code
Les dépendances open source directes et transitives que vous ajoutez à votre projet
Vos images Docker
Vos modèles d’infrastructure as code
L’extension Snyk Security vous aide à repérer des problèmes comme les entrées non assainies provenant de paramètres d’URL, susceptibles d’entraîner des vulnérabilités de script intersites (XSS), d’injection de commandes, de falsification de requêtes côté serveur (SSRF) ou de redirection ouverte. Pour chaque vulnérabilité détectée, elle montre comment différents projets Java open source ont corrigé des problèmes similaires par le passé :

Voici la liste complète des contrôles de sécurité effectués par Snyk Security sur votre code Java.
Conclusion
Omettre l’encodage des paramètres d’URL peut sembler anodin, mais comme cet article l’a montré, cela peut avoir de graves conséquences sur la fiabilité et la sécurité de vos applications Java. L’encodage en pourcentage des paramètres d’URL permet d’obtenir les résultats attendus pour les utilisateurs légitimes et empêche les acteurs malveillants de contourner les contrôles d’accès et de mener des attaques.
L’encodage et le décodage des URL ne sont que quelques-unes des nombreuses bonnes pratiques de sécurité. En vous appuyant sur des API éprouvées et des outils de développement adaptés, comme l’extension Snyk Security pour IntelliJ IDEA, vous pouvez travailler correctement, livrer du code sécurisé de façon constante et éviter de passer des heures pénibles à résoudre des incidents de sécurité en production et à en assurer le suivi.
Lancez-vous dans les compétitions Capture The Flag
Apprenez à résoudre des défis Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.



