web-dev-qa-db-fra.com

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

Une application Web existante est en cours d'exécution sur Tomcat 4.1. Il y a un problème XSS avec une page, mais je ne peux pas modifier le 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
  {
    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) {
  }
}

Mais ServletRequest.setParameter n'existe pas.

Comment puis-je modifier la valeur du paramètre de demande avant de transmettre la demande en aval de la chaîne?

100
Jeremy Stein

Comme vous l'avez noté, HttpServletRequest n'a pas de méthode setParameter. C'est délibéré, car la classe représente la requête telle qu'elle est venue du client, et modifier le paramètre ne le représenterait pas.

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

C'est un peu moche, mais c'est ce que l'API de servlet vous recommande de faire. Si vous essayez de passer quelque chose d'autre à doFilter, certains conteneurs de servlets se plaindront de votre violation des spécifications et refuseront de les gérer.

Une solution plus élégante, c'est plus de travail - modifiez le servlet/JSP d'origine qui traite le paramètre, de sorte qu'il attend une requête attribut au lieu d'un paramètre. Le filtre examine le paramètre, l'assainit et définit l'attribut (à l'aide de request.setAttribute) avec la valeur assainie. Pas de sous-classement, pas de spoofing, mais vous oblige à modifier d'autres parties de votre application.

117
skaffman

Pour mémoire, voici le cours auquel 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 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) {
    }
}
67
Jeremy Stein

Ecrivez une classe simple qui sous-analyse 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’objet request directement.

10
Asaph

C'est ce que j'ai fini par faire

//import ../../Constants;

public class RequestFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(RequestFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
        throws IOException, ServletException {
        try {
            CustomHttpServletRequest customHttpServletRequest = new CustomHttpServletRequest((HttpServletRequest) servletRequest);
            filterChain.doFilter(customHttpServletRequest, servletResponse);
        } finally {
            //do something here
        }
    }



    @Override
    public void destroy() {

    }

     public static Map<String, String[]> ADMIN_QUERY_PARAMS = new HashMap<String, String[]>() {
        {
            put("diagnostics", new String[]{"false"});
            put("skipCache", new String[]{"false"});
        }
    };

    /*
        This is a custom wrapper over the `HttpServletRequestWrapper` which 
        overrides the various header getter methods and query param getter methods.
        Changes to the request pojo are
        => A custom header is added whose value is a unique id
        => Admin query params are set to default values in the url
    */
    private class CustomHttpServletRequest extends HttpServletRequestWrapper {
        public CustomHttpServletRequest(HttpServletRequest request) {
            super(request);
            //create custom id (to be returned) when the value for a
            //particular header is asked for
            internalRequestId = RandomStringUtils.random(10, true, true) + "-local";
        }

        public String getHeader(String name) {
            String value = super.getHeader(name);
            if(Strings.isNullOrEmpty(value) && isRequestIdHeaderName(name)) {
                value = internalRequestId;
            }
            return value;
        }

        private boolean isRequestIdHeaderName(String name) {
            return Constants.RID_HEADER.equalsIgnoreCase(name) || Constants.X_REQUEST_ID_HEADER.equalsIgnoreCase(name);
        }

        public Enumeration<String> getHeaders(String name) {
            List<String> values = Collections.list(super.getHeaders(name));
            if(values.size()==0 && isRequestIdHeaderName(name)) {
                values.add(internalRequestId);
            }
            return Collections.enumeration(values);
        }

        public Enumeration<String> getHeaderNames() {
            List<String> names = Collections.list(super.getHeaderNames());
            names.add(Constants.RID_HEADER);
            names.add(Constants.X_REQUEST_ID_HEADER);
            return Collections.enumeration(names);
        }

        public String getParameter(String name) {
            if (ADMIN_QUERY_PARAMS.get(name) != null) {
                return ADMIN_QUERY_PARAMS.get(name)[0];
            }
            return super.getParameter(name);
        }

        public Map<String, String[]> getParameterMap() {
            Map<String, String[]> paramsMap = new HashMap<>(super.getParameterMap());
            for (String paramName : ADMIN_QUERY_PARAMS.keySet()) {
                if (paramsMap.get(paramName) != null) {
                    paramsMap.put(paramName, ADMIN_QUERY_PARAMS.get(paramName));
                }
            }
            return paramsMap;
        }

        public String[] getParameterValues(String name) {
            if (ADMIN_QUERY_PARAMS.get(name) != null) {
                return ADMIN_QUERY_PARAMS.get(name);
            }
            return super.getParameterValues(name);
        }

        public String getQueryString() {
            Map<String, String[]> map = getParameterMap();
            StringBuilder builder = new StringBuilder();
            for (String param: map.keySet()) {
                for (String value: map.get(param)) {
                    builder.append(param).append("=").append(value).append("&");
                }
            }
            builder.deleteCharAt(builder.length() - 1);
            return builder.toString();
        }
    }
}
0
agent47

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<String>. Dans la Filter j'ai:

class MyFilter extends Filter {
    public static final ThreadLocal<String> 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ête (HttpServlet, contrôleur JSF ou tout autre processeur de requête HTTP), je récupère la valeur de thread actuelle:

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

Avantages: 

  • plus polyvalent que la transmission de paramètres HTTP (vous pouvez transmettre des objets POJO)
  • légèrement plus rapide (pas besoin d'analyser l'URL pour extraire la valeur de la variable)
  • plus élégant que le HttpServletRequestWrapper passe-partout
  • l'étendue de la variable est plus large que la requête HTTP (l'étendue que vous avez lorsque vous faites request.setAttribute(String,Object), c'est-à-dire que vous pouvez accéder à la variable dans d'autres filtres.

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 demande HTTP (c'est le cas de tous les serveurs Java que je connais). Par conséquent, ceci ne fonctionnera pas quand
    • faisant une redirection HTTP} _ (car le navigateur envoie une nouvelle requête HTTP et qu'il est impossible de garantir qu'elle sera traitée par le même thread)
    • traitement des données dans des threads distincts, par exemple. en utilisant Java.util.stream.Stream.parallel, Java.util.concurrent.Future, Java.lang.Thread.
  • Vous devez pouvoir modifier la requête processeur/application

Quelques notes de côté:

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

    1. un thread de ce pool de threads traitera de nombreuses requêtes HTTP, mais une seule à la fois (vous devez donc nettoyer votre variable après utilisation ou la définir pour chaque requête HTTP = faites attention au code tel que if (value!=null) { THREAD_VARIABLE.set(value);} car vous réutiliserez le valeur de la requête HTTP précédente lorsque value est null (les effets secondaires sont garantis). 
    2. Il n'y a aucune garantie que deux demandes seront traitées par le même thread (ce 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 est préférable d'utiliser HttpSession.setAttribute()
  • JEE @RequestScoped utilise en interne une 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 multithreads).
0
Julien Kronegg

Essayez request.setAttribute("param",value);. Cela a bien fonctionné pour moi.

Veuillez trouver cet exemple de code:

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

            for(int i=0;i<price.length;i++){
                price[i] = price[i].replaceAll("[^\\dA-Za-z0-9- ]", "").trim();
                System.out.println(price[i]);
            }
            request.setAttribute("price", price);
            //request.getParameter("numOfBooks").re
        }
    }
0
Ankur Bag

Vous pouvez utiliser Expression régulière pour la désinfection. Filtre interne avant d'appeler chain.doFilter (demande, réponse) méthode, appelez ce code . Voici un exemple de code:

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();   
    }
}
0
Ajinkya Peshave