Codificação e decodificação seguras de URLs em Java
Jura Gorohovsky
14 de agosto de 2023
0 minutos de leituraA codificação de URLs é um método que garante que sua URL contenha apenas caracteres válidos, para que o servidor de destino possa interpretá-la corretamente. De acordo com o padrão RFC 3986, os URIs (que são um superconjunto das URLs) contêm apenas um conjunto limitado de caracteres: números, letras e alguns símbolos gráficos, todos pertencentes ao conjunto de caracteres ASCII.
Se uma URL contiver caracteres fora desse conjunto limitado, eles deverão ser codificados com porcentagem. A codificação com porcentagem converte um caractere em uma representação hexadecimal de dois dígitos de oito bits, precedida pelo caractere de escape %. O mesmo processo deve ser aplicado aos delimitadores (por exemplo, &, /, ? ou #) do ASCII quando usados fora de suas posições estruturais previstas na URL.
Já a decodificação de URLs converte uma URL codificada com porcentagem de volta à sua forma original, restaurando os caracteres não padronizados.
É importante entender que codificação não é criptografia. Criptografar significa modificar informações usando uma chave secreta, de modo que ninguém além do destinatário possa acessar as informações originais. Já o objetivo da codificação de URLs não é ocultar partes da URL de quem estiver observando, mas garantir que o servidor de destino consiga interpretá-la com facilidade e sem ambiguidades, além de impedir que o usuário do cliente que está criando e enviando a URL a manipule.
Deixar de codificar uma URL pode causar diversos problemas. Por exemplo, talvez seu aplicativo não consiga montar a URL para enviá-la ao servidor. Além disso, o servidor que recebe a URL pode não conseguir analisá-la corretamente e retornar um erro. Outro risco é a manipulação de uma URL não codificada, que pode expor seu aplicativo a ameaças de segurança.
Cada linguagem de programação oferece uma ou mais APIs para codificar e decodificar URLs. Este artigo aborda Java, explica por que a codificação e a decodificação de URLs são importantes e mostra como fazê-las corretamente.
O que é codificação e decodificação de URLs em Java?
Em Java, a codificação e a decodificação de URLs são importantes nos seguintes casos de uso:
Processar dados de formato livre inseridos por um visitante em um formulário HTML, como um formulário de pesquisa.
Montar chamadas a uma API externa no código, adicionando parâmetros de consulta a uma URL base.
Montar chamadas a um gateway de API usado para encaminhar solicitações a serviços internos.
Uma URL tem a seguinte estrutura:

Em geral, não é necessário codificar a URL inteira. É claro que, às vezes, o caminho pode conter espaços em nomes de arquivos enviados por usuários — e até existe o Punycode para nomes de host. Porém, na maioria dos casos, você controla as seções de host e caminho. Isso significa que basta codificar, uma a uma, as partes da URL que representam dados variáveis (ou seja, os valores dos parâmetros da string de consulta).
Um caso específico em que é preciso codificar a URL inteira é quando ela é passada como parâmetro na string de consulta de outra URL.
Como implementar a codificação e a decodificação seguras de URLs em Java
Para entender melhor a codificação e a decodificação de URLs em Java, confira duas classes comumente usadas em aplicativos Java para codificar e decodificar parâmetros de strings de consulta.
Como codificar URLs em Java
Para aplicar a codificação com porcentagem aos valores dos parâmetros de uma string de consulta em um aplicativo Java, normalmente você usa a classe java.net.URLEncoder e o método encode().
Veja exatamente o que o método encode() faz:
Ele garante que todos os caracteres alfanuméricos — como
aaz,AaZ,0a9,e os caracteres especiais.,-,*e_— sejam mantidos.Ele converte o espaço em um sinal de mais
+.Todos os outros caracteres são codificados com porcentagem.
Esse método foi criado para preparar dados de formulários HTML para envio, convertendo-os para o formato MIME application/x-www-form-urlencoded, adequado para codificar valores de parâmetros de consulta de URLs.
Há três versões sobrecarregadas do método encode():
1. encode(String s, String enc) permite definir explicitamente o esquema de codificação como uma string (recomenda-se UTF-8). Você pode usar essa versão, mas lembre-se de que ela lança uma exceção verificada UnsupportedEncodingException. Isso significa que seu código precisa tratá-la com uma declaração @throws ou um bloco try/catch. Também vale lembrar que usar literais de string traz o risco de erros de digitação:
2. encode(String s, Charset charset) está disponível desde o Java 10 e é a melhor versão até agora. Ela usa uma constante para UTF-8 (StandardCharsets.UTF_8), o que elimina o risco de erros de digitação ao especificar a codificação e não lança exceções verificadas. Assim, você não precisa tratá-las para compilar seu código:
3. encode(String s) é a versão mais antiga e está marcada como obsoleta no OpenJDK 17. Não use essa versão: ela utiliza a codificação padrão da plataforma em que a máquina virtual Java (JVM) está sendo executada, que não é necessariamente UTF-8.
O método URLEncoder.encode() tem uma particularidade: ele codifica um espaço como o caractere de mais, em vez de %20, provavelmente por seguir a descrição de strings de consulta de um padrão antigo. Por isso, às vezes os desenvolvedores alteram a saída de encode() para substituir o sinal de mais por %20, que representa o espaço:
Por exemplo, digamos que você precise pesquisar repositórios usando a API REST do GitHub. O GitHub oferece diversos qualificadores de pesquisa que ajudam a filtrar resultados por linguagem de programação, tamanho do repositório e visibilidade. Por exemplo, a pesquisa por "user:defunkt forks:>100" retorna todos os repositórios do usuário defunkt com cem ou mais forks. Você pode usar esses filtros em chamadas à API, mas precisa colocá-los no parâmetro de consulta q:
Neste código, searchQuery armazena um conjunto de qualificadores de pesquisa que ajuda a encontrar todos os repositórios públicos de Java com entre 100 e 1.000 estrelas, atualizados desde 2018. O valor deste exemplo está definido diretamente no código, mas também poderia vir da leitura de um arquivo ou banco de dados, ou ser inserido diretamente por um usuário em um aplicativo web ou para dispositivos móveis.
Ao montar url, este código concatena três strings:
A URL base da API, o delimitador da string de consulta
?, o parâmetroqe seu delimitador de atribuição=: "https://api.github.com/search/repositories?q=".searchQuery, que é o valor do parâmetroq, é codificado com porcentagem usandoURLEncode().O restante dos parâmetros de consulta não precisa ser codificado, pois está definido diretamente no código e não contém caracteres inválidos.
Ao executar esse código, a URL resultante é 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. O valor de q é codificado como la nguage%3AJava+stars%3A100..1000+pushed%3A%3E2018-01-01+is%3Apublic, em que : é substituído por %3A, os espaços são substituídos por + e > é substituído por %3E.
Como decodificar URLs em Java
A decodificação explícita de parâmetros de consulta de URLs é menos comum, pois muitos frameworks, incluindo o Spring Boot, fazem isso automaticamente.
Se você não estiver usando um framework, o processo deve depender do que pretende fazer em seguida.
É provável que você receba a URL para decidir quais ações executar, como consultar dados ou redirecionar a solicitação para outro serviço. Nesse caso, sua lógica de processamento provavelmente analisará cada parâmetro de consulta separadamente. Você pode começar analisando a URL, extraindo a string de consulta e decodificando cada valor de parâmetro individualmente.
Para decodificar caracteres codificados com porcentagem, use java.net.URLDecoder.decode():
Aqui, encodedUrl contém uma URL de pesquisa do Google com parâmetros de consulta codificados com porcentagem pelo navegador. Este código cria um novo objeto uri do tipo URI para extrair a string de consulta inteira. O objeto oferece o método getRawQuery(), que retorna apenas a string de consulta, mantendo codificados todos os valores dos parâmetros:
Em seguida, o código divide a consulta bruta pelo delimitador &, criando um array de pares individuais de parâmetro e valor. Cada par é transformado: o parâmetro é mantido como está e o valor é decodificado. Por fim, todos os pares transformados são reunidos em uma lista:
Depois de separar todos os parâmetros e associá-los aos respectivos valores, você pode aplicar a lógica necessária para validá-los e definir as próximas etapas do aplicativo.
Práticas recomendadas para lidar com URLs em Java
Ao trabalhar com URLs em Java, vale seguir algumas práticas recomendadas para garantir o tratamento correto e evitar possíveis problemas.
Não deixe de codificar URLs
Se você não codificar suas URLs, prepare-se para o inesperado — desde exceções em tempo de execução até respostas confusas dos servidores que está tentando acessar.
Por exemplo, reveja o cenário da chamada à API do GitHub e veja o que acontece quando os parâmetros de pesquisa não são codificados:
Vale lembrar que, neste exemplo de código, sendGetRequest(url) encapsula a criação de uma solicitação HTTP usando a API de criação de solicitações do Java 11:
Se você executar este código sem codificar searchQuery, haverá uma falha em tempo de execução, porque o construtor de URI não consegue criar um objeto URI a partir de uma string que contém um espaço não codificado:
E se você insistir em enviar essa solicitação mesmo assim? Você pode tentar usar a API Scanner, que aceita um objeto URL (em vez de URI) e o utiliza para ler um fluxo de entrada:
Isso funciona. A solicitação à API chega ao destino, mas retorna o erro 400 Bad Request, indicando uma possível falha do lado do cliente, como uma sintaxe de solicitação malformada:
Rotem Bar mostra muito bem como URLs não codificadas podem ser manipuladas na comunicação entre serviços que usam REST.
Um dos cenários descritos por Rotem em que é possível manipular URLs envolve um serviço que:
Recebe parâmetros de uma URL enviada pelo cliente.
Cria uma nova URL para chamar outro serviço.
Veja um exemplo de código que reproduz esse cenário:
Como você pode ver, diante de um valor complexo como o usado no parâmetro q, deixar de codificar a URL vai acabar causando problemas em algum momento. Mesmo que você tenha a sorte de receber uma resposta válida do servidor, não dá para ter certeza de que ela contém o que você esperava ou se você acabou explorando, sem querer, uma vulnerabilidade do servidor.
Falando em vulnerabilidades, não codificar parâmetros de URL pode facilmente levar a elas — um tema que abordamos melhor no artigo "Desenvolvedores, codifiquem suas URLs".
O serviço recebe dois parâmetros, key e user, e valida corretamente o usuário. No entanto, key não é validado e é simplesmente inserido como está ao criar uma nova URL. Nenhum dos parâmetros de consulta dessa nova URL é codificado.
Quando o serviço recebe uma entrada legítima, cria uma nova URL como esperado:
Mas o que acontece se um agente mal-intencionado enviar uma URL com o parâmetro key adulterado?
Veja o que acontece:
O valor de
keyenviado pelo cliente é55s72502010a&user=admin#. O serviço acrescenta a chave à URL sem codificá-la.O serviço valida o parâmetro
usernormalmente, elegitusercontinua sendo considerado legítimo porque, bem, é legítimo.Como o parâmetro
keyoriginal inclui o caractere&, que funciona como separador de parâmetros na query string da URL, a URL resultante recebe dois parâmetros —key=55s72502010aeuser=admin— que são duas partes da chave enviada pelo cliente. Como você pode ver, embora o serviço tenha validado o usuáriolegituser, a URL resultante se passa por outro usuário, que pode ter permissões elevadas, e essa identidade passou pela validação.Como o parâmetro
keyoriginal termina com o caractere#, que funciona como separador de fragmento em URLs, a parte&user=legituserda URL original enviada pelo cliente é deslocada para a seção de fragmento e, na prática, ignorada.
Como resultado, você obtém uma URL que representa um usuário não relacionado e não verificado, o que caracteriza um ataque de elevação de privilégios.
Então, como se proteger contra esse ataque? Uma opção é validar o valor de key além de validar o user. No entanto, talvez você queira delegar essa validação a outro serviço na cadeia de requisições. Nesse caso, a solução mais simples é codificar os valores dos parâmetros ao construir uma nova URL:
Com essa alteração, veja o que acontece quando alguém tenta enviar um valor malicioso para key:
O valor codificado de key (key=55s72502010a%26user%3Dadmin%23) impede a tentativa de elevação de privilégios porque & é codificado como %26 e # como %23. A URL resultante segue para o próximo serviço com o nome de usuário correto. O próximo serviço pode retornar um erro ao tentar interpretar o parâmetro key adulterado, mas receber uma resposta de erro é sempre melhor do que sofrer um ataque.
Use bibliotecas padrão para codificar e decodificar URLs
Especificamente em Java, para codificar parâmetros de query usando codificação percentual, provavelmente você usa na maioria das vezes URLEncoder.encode() e URLDecoder.decode(), ferramentas confiáveis e disponíveis por padrão em qualquer projeto Java.
Outra opção é usar a biblioteca Open Web Application Security Project (OWASP) Java Encoder. Embora seu objetivo principal seja validar entradas em diferentes contextos, ela também inclui o método Encode.forUriComponent() para codificar valores de parâmetros de URL e caminhos REST. Essa biblioteca é uma boa escolha se seu aplicativo Java precisa exibir com segurança URLs enviadas por clientes e suas partes em diferentes controles de interface.
Dito isso, dependendo do framework que você usa, talvez não seja necessário decodificar explicitamente ou seja preciso fazer isso de outra maneira:
No Spring ou Spring Boot, a anotação
@RequestParamaplicada a um parâmetro do controlador decodifica automaticamente o valor desse parâmetro.Em um aplicativo GWT, provavelmente você usa os métodos definidos na própria classe `com.google.gwt.http.client.URL` do GWT.
O Android tem uma API separada para construir URLs, que inclui a codificação de parâmetros de query.
Se você usa bibliotecas de terceiros para codificar e decodificar URLs, é recomendável verificar se elas têm vulnerabilidades conhecidas e, caso tenham, atualizá-las assim que as correções de segurança estiverem disponíveis.
Valide e higienize URLs antes de interagir com elas no seu aplicativo
A codificação de URLs é apenas parte da solução. Se seu aplicativo web recebe dados de usuários por meio de parâmetros de URL e depois os repassa ao aplicativo, você deve garantir que esses dados sejam inofensivos antes de inseri-los novamente no contexto do navegador.
Isso significa que você deve validar os dados do usuário, inclusive os recebidos por URLs, no servidor, verificando sua sintaxe e semântica. Use listas de permissões, pois listas de bloqueio estão mais sujeitas a erros e omissões.
Além disso, se seu aplicativo permite que usuários enviem código HTML, você deve implementar a higienização de HTML. Uma biblioteca bastante usada para esse fim em Java é a OWASP Java HTML Sanitizer.
Encontre URLs inseguras com a Snyk Security Extension para IntelliJ IDEA
A codificação e a decodificação de URLs devem fazer parte de um conjunto mais amplo de práticas de segurança, como codificar e escapar dados e validar entradas. Escrever código seguro é essencial, e identificar falhas de segurança o mais cedo possível no ciclo de desenvolvimento reduz tanto o custo de corrigi-las quanto o impacto de incidentes de segurança.
Se você desenvolve em Java e trabalha com o IntelliJ IDEA, pode ser útil instalar a extensão Snyk Security.
A Snyk Security encontra problemas, destaca-os no editor de código e ajuda você a corrigir vulnerabilidades de segurança conhecidas em:
Seu próprio código
Dependências de código aberto diretas e transitivas que você adiciona ao projeto
Suas imagens Docker
Seus templates de infraestrutura como código
A extensão Snyk Security ajuda você a identificar problemas como entradas não higienizadas provenientes de parâmetros de URL, que podem levar a cross-site scripting (XSS), injeção de comandos, falsificação de requisições do lado do servidor (SSRF) ou vulnerabilidades de redirecionamento aberto. Para cada vulnerabilidade detectada, ela mostra como diferentes projetos Java de código aberto corrigiram problemas semelhantes:

Confira a lista completa das verificações de segurança que a Snyk Security executa no seu código Java.
Conclusão
Deixar de codificar parâmetros de URL pode parecer uma omissão pequena, mas, como você viu neste artigo, isso pode afetar seriamente a confiabilidade e a segurança dos seus aplicativos Java. A codificação percentual dos parâmetros de URL ajuda a garantir os resultados esperados para usuários legítimos e impede que agentes maliciosos burlem controles de acesso e realizem ataques.
Embora a codificação e a decodificação de URLs sejam apenas parte de um conjunto mais amplo de boas práticas de segurança, usar APIs estáveis e ferramentas inteligentes para desenvolvedores, como a extensão Snyk Security para IntelliJ IDEA, ajuda você a acertar sempre, entregar código seguro de forma consistente e evitar horas exaustivas resolvendo incidentes de segurança em produção e acompanhando seus desdobramentos.
Comece a jogar Capture the Flag
Aprenda a resolver desafios de Capture the Flag assistindo à gravação sob demanda do nosso workshop virtual introdutório.



