Modificar o parâmetro de solicitação com filtro de servlet

Um aplicativo da web existente está sendo executado no Tomcat 4.1. Há um problema de XSS com uma página, mas não posso modificar a origem. Eu decidi escrever um filtro de servlet para limpar o parâmetro antes que ele seja visto pela página.

Eu gostaria de escrever uma class Filter assim:

import java.io.*; import javax.servlet.*; public final class XssFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String badValue = request.getParameter("dangerousParamName"); String goodValue = sanitize(badValue); request.setParameter("dangerousParamName", goodValue); chain.doFilter(request, response); } public void destroy() { } public void init(FilterConfig filterConfig) { } } 

Mas o ServletRequest.setParameter não existe.

Como posso alterar o valor do parâmetro de solicitação antes de passar a solicitação pela cadeia?

Como você observou, o HttpServletRequest não possui um método setParameter. Isso é deliberado, uma vez que a class representa a solicitação como ela veio do cliente, e modificar o parâmetro não representaria isso.

Uma solução é usar a class HttpServletRequestWrapper , que permite que você envolva uma solicitação com outra. Você pode criar uma subclass e replace o método getParameter para retornar seu valor higienizado. Você pode então passar essa requisição empacotada para chain.doFilter ao invés do pedido original.

É um pouco feio, mas é o que a API do servlet diz que você deve fazer. Se você tentar passar qualquer outra coisa para doFilter , alguns contêineres do servlet irão reclamar que você violou a especificação e se recusará a lidar com isso.

Uma solução mais elegante é mais trabalho – modifique o servlet / JSP original que processa o parâmetro, para que ele espere um atributo de solicitação em vez de um parâmetro. O filtro examina o parâmetro, higieniza e define o atributo (usando request.setAttribute ) com o valor higienizado. Nenhuma subsorting, nenhum spoofing, mas exige que você modifique outras partes do seu aplicativo.

Para o registro, aqui está a class que acabei escrevendo:

 import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; public final class XssFilter implements Filter { static class FilteredRequest extends HttpServletRequestWrapper { /* These are the characters allowed by the Javascript validation */ static String allowedChars = "+-0123456789#*"; public FilteredRequest(ServletRequest request) { super((HttpServletRequest)request); } public String sanitize(String input) { String result = ""; for (int i = 0; i < input.length(); i++) { if (allowedChars.indexOf(input.charAt(i)) >= 0) { result += input.charAt(i); } } return result; } public String getParameter(String paramName) { String value = super.getParameter(paramName); if ("dangerousParamName".equals(paramName)) { value = sanitize(value); } return value; } public String[] getParameterValues(String paramName) { String values[] = super.getParameterValues(paramName); if ("dangerousParamName".equals(paramName)) { for (int index = 0; index < values.length; index++) { values[index] = sanitize(values[index]); } } return values; } } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(new FilteredRequest(request), response); } public void destroy() { } public void init(FilterConfig filterConfig) { } } 

Escreva uma class simples que subcalsses HttpServletRequestWrapper com um método getParameter () que retorna a versão sanitizada da input. Em seguida, passe uma instância do seu HttpServletRequestWrapper para Filter.doChain() vez do object de solicitação diretamente.

Tente request.setAttribute("param",value); . Funcionou bem para mim.

Por favor, encontrar este exemplo de código:

 private void sanitizePrice(ServletRequest request){ if(request.getParameterValues ("price") != null){ String price[] = request.getParameterValues ("price"); for(int i=0;i 

Você pode usar a expressão regular para sanitização. Dentro do filtro antes de chamar o método chain.doFilter (request, response) , chame este código. Aqui está o código de exemplo:

 for (Enumeration en = request.getParameterNames(); en.hasMoreElements(); ) { String name = (String)en.nextElement(); String values[] = request.getParameterValues(name); int n = values.length; for(int i=0; i < n; i++) { values[i] = values[i].replaceAll("[^\\dA-Za-z ]","").replaceAll("\\s+","+").trim(); } } 

Eu tive o mesmo problema (alterar um parâmetro da solicitação HTTP no filtro). Acabei usando um ThreadLocal . No Filter eu tenho:

 class MyFilter extends Filter { public static final ThreadLocal THREAD_VARIABLE = new ThreadLocal<>(); public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { THREAD_VARIABLE.set("myVariableValue"); chain.doFilter(request, response); } } 

No meu processador de solicitação ( HttpServlet , controlador JSF ou qualquer outro processador de solicitação HTTP), recupero o valor do encadeamento atual:

 ... String myVariable = MyFilter.THREAD_VARIABLE.get(); ... 

Vantagens:

  • mais versátil do que passar parâmetros HTTP (você pode passar objects POJO)
  • ligeiramente mais rápido (não é necessário analisar a URL para extrair o valor da variável)
  • mais elegante do que o boilerplate HttpServletRequestWrapper
  • o escopo da variável é mais amplo do que apenas a solicitação HTTP (o escopo que você tem ao fazer request.setAttribute(String,Object) , ou seja, você pode acessar a variável em outros filtros.

Desvantagens:

  • Você pode usar esse método somente quando o encadeamento que processa o filtro for o mesmo que o que processa a solicitação HTTP (esse é o caso em todos os servidores baseados em Java que eu conheço). Conseqüentemente, isso não funcionará quando
    • fazendo um redirecionamento HTTP (porque o navegador faz um novo pedido HTTP e não há como garantir que ele será processado pelo mesmo thread)
    • processando dados em encadeamentos separados , por exemplo, ao usar java.util.stream.Stream.parallel , java.util.concurrent.Future , java.lang.Thread .
  • Você deve poder modificar o processador / aplicativo da solicitação

Algumas notas secundárias:

  • O servidor tem um pool de segmentos para processar as solicitações HTTP. Como isso é pool:

    1. um Thread deste conjunto de encadeamentos processará muitas solicitações HTTP, mas apenas uma por vez (para que você precise limpar sua variável após o uso ou defini-la para cada solicitação HTTP = preste atenção ao código, como if (value!=null) { THREAD_VARIABLE.set(value);} porque você reutilizará o valor da solicitação HTTP anterior quando o value for null: efeitos colaterais são garantidos).
    2. Não há garantia de que dois pedidos serão processados ​​pelo mesmo thread (pode ser o caso, mas você não tem garantia). Se você precisar manter os dados do usuário de uma solicitação para outra, seria melhor usar HttpSession.setAttribute()
  • O JEE @RequestScoped usa internamente um ThreadLocal , mas o uso do ThreadLocal é mais versátil: você pode usá-lo em contêineres não JEE / CDI (por exemplo, em aplicativos JRE multithreaded)