Modifier le paramètre de demande avec le filtre de servlet

Une application Web existante s’exécute sur Tomcat 4.1. Il y a un problème XSS avec une page, mais je ne peux pas modifier la source. J’ai décidé d’écrire un filtre de servlet pour assainir le paramètre avant qu’il ne soit vu par la page.

Je voudrais écrire une classe de filtre comme ceci:

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

Mais ServletRequest.setParameter n’existe pas.

Comment puis-je changer la valeur du paramètre de requête avant de passer la requête en chaîne?

Comme vous l’avez noté, HttpServletRequest n’a pas de méthode setParameter. Ceci est délibéré, car la classe représente la requête telle qu’elle provient du client et la modification du paramètre ne représenterait pas cela.

Une solution consiste à utiliser la classe HttpServletRequestWrapper , qui vous permet d’envelopper une requête avec une autre. Vous pouvez sous-classer cela et remplacer la méthode getParameter pour renvoyer votre valeur assainie. Vous pouvez ensuite transmettre cette requête chain.doFilter à chain.doFilter au lieu de la demande d’origine.

C’est un peu moche, mais c’est ce que dit l’API de servlet. Si vous essayez de transmettre autre chose à doFilter , certains conteneurs de servlets se plaindront que vous avez violé les spécifications et refuseront de les gérer.

Une solution plus élégante est plus de travail – modifiez le servlet / JSP d’origine qui traite le paramètre, de sorte qu’il attend un atsortingbut de requête au lieu d’un paramètre. Le filtre examine le paramètre, l’assainit et définit l’atsortingbut (à l’aide de request.setAtsortingbute ) avec la valeur assainie. Aucun sous-classement, aucune usurpation d’identité, mais vous oblige à modifier d’autres parties de votre application.

Pour mémoire, voici le cours que j’ai fini par écrire:

 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 Ssortingng allowedChars = "+-0123456789#*"; public FilteredRequest(ServletRequest request) { super((HttpServletRequest)request); } public Ssortingng sanitize(Ssortingng input) { Ssortingng result = ""; for (int i = 0; i < input.length(); i++) { if (allowedChars.indexOf(input.charAt(i)) >= 0) { result += input.charAt(i); } } return result; } public Ssortingng getParameter(Ssortingng paramName) { Ssortingng value = super.getParameter(paramName); if ("dangerousParamName".equals(paramName)) { value = sanitize(value); } return value; } public Ssortingng[] getParameterValues(Ssortingng paramName) { Ssortingng 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) { } } 

Ecrivez une classe simple qui subdivise HttpServletRequestWrapper avec une méthode getParameter () qui renvoie la version assainie de l’entrée. Puis passez une instance de votre HttpServletRequestWrapper à Filter.doChain() au lieu de l’object de requête directement.

Essayez request.setAtsortingbute("param",value); . Ça a bien marché pour moi.

Veuillez trouver cet exemple de code:

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

Vous pouvez utiliser l’ expression régulière pour la désinfection. Dans le filtre avant d’appeler la méthode chain.doFilter (request, response) , appelez ce code. Voici l’exemple de code:

 for (Enumeration en = request.getParameterNames(); en.hasMoreElements(); ) { Ssortingng name = (Ssortingng)en.nextElement(); Ssortingng 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(); } } 

J’ai eu le même problème (changer un paramètre de la requête HTTP dans le filtre). J’ai fini par utiliser un ThreadLocal . Dans le Filter j’ai:

 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); } } 

Dans mon processeur de requêtes ( HttpServlet , contrôleur JSF ou tout autre processeur de requête HTTP), je récupère la valeur de thread actuelle:

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

Avantages:

  • plus polyvalent que de passer des parameters HTTP (vous pouvez passer des objects POJO)
  • légèrement plus rapide (pas besoin d’parsingr l’URL pour extraire la valeur de la variable)
  • plus élégant que le passe-partout HttpServletRequestWrapper
  • La scope de la variable est plus large que la requête HTTP (la scope que vous avez lorsque vous faites request.setAtsortingbute(Ssortingng,Object) , c.-à-d. que vous pouvez accéder à la variable dans d’autres filtreurs.

Désavantages:

  • Vous pouvez utiliser cette méthode uniquement lorsque le thread qui traite le filtre est le même que celui qui traite la requête HTTP (c’est le cas dans tous les serveurs Java que je connais). Par conséquent, cela ne fonctionnera pas lorsque
    • faire une redirection HTTP (parce que le navigateur effectue une nouvelle requête HTTP et qu’il n’y a aucun moyen de garantir qu’il sera traité par le même thread)
    • traiter les données dans des threads distincts , par exemple lors de l’utilisation de java.util.stream.Stream.parallel , java.util.concurrent.Future , java.lang.Thread .
  • Vous devez pouvoir modifier le processeur / l’application de la demande

Quelques notes secondaires:

  • Le serveur dispose d’un pool de threads pour traiter les requêtes HTTP. Puisque c’est pool:

    1. un thread de ce pool traitera de nombreuses requêtes HTTP, mais une seule à la fois (vous devez donc soit nettoyer votre variable après utilisation, soit la définir pour chaque requête HTTP = faire attention au code tel que if (value!=null) { THREAD_VARIABLE.set(value);} car vous allez réutiliser la valeur de la requête HTTP précédente lorsque la value est null: les effets secondaires sont garantis).
    2. Il n’y a aucune garantie que deux requêtes seront traitées par le même thread (cela peut être le cas mais vous n’avez aucune garantie). Si vous devez conserver les données utilisateur d’une requête à une autre, il serait préférable d’utiliser HttpSession.setAtsortingbute()
  • Le JEE @RequestScoped utilise en interne un ThreadLocal , mais l’utilisation de ThreadLocal est plus polyvalente: vous pouvez l’utiliser dans des conteneurs non JEE / CDI (par exemple, dans des applications JRE multithread)