it-swarm.com.de

Anforderungsparameter mit Servlet-Filter ändern

Eine vorhandene Webanwendung läuft auf Tomcat 4.1. Es gibt ein XSS-Problem mit einer Seite, aber ich kann die Quelle nicht ändern. Ich habe mich entschlossen, einen Servlet-Filter zu schreiben, um den Parameter zu bereinigen, bevor er von der Seite gesehen wird.

Ich möchte so eine Filterklasse schreiben:

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

ServletRequest.setParameter existiert jedoch nicht.

Wie kann ich den Wert des Anforderungsparameters ändern, bevor die Anforderung in der Kette weitergegeben wird?

100
Jeremy Stein

Wie bereits erwähnt, hat HttpServletRequest keine setParameter-Methode. Dies ist absichtlich, da die Klasse die Anforderung so darstellt, wie sie vom Client stammt, und das Ändern des Parameters würde dies nicht darstellen.

Eine Lösung ist die Verwendung der HttpServletRequestWrapper-Klasse, mit der Sie eine Anforderung mit einer anderen zusammenfassen können. Sie können dies subclassieren und die getParameter-Methode überschreiben, um Ihren bereinigten Wert zurückzugeben. Sie können diese umschlossene Anforderung dann anstelle der ursprünglichen Anforderung an chain.doFilter übergeben.

Es ist ein bisschen hässlich, aber laut Servlet-API sollten Sie das tun. Wenn Sie versuchen, etwas anderes an doFilter zu übergeben, beschweren sich einige Servlet-Container, dass Sie gegen die Spezifikation verstoßen haben, und verweigern die Verarbeitung.

Eine elegantere Lösung ist mehr Arbeit - ändern Sie das ursprüngliche Servlet/die ursprüngliche JSP, die den Parameter verarbeitet, so dass anstelle eines Parameters ein request attribute erwartet wird. Der Filter überprüft den Parameter, bereinigt ihn und setzt das Attribut (mit request.setAttribute) auf den bereinigten Wert. Keine Unterklassen, kein Spoofing, Sie müssen jedoch andere Teile Ihrer Anwendung ändern.

117
skaffman

Hier ist die Klasse, die ich am Ende geschrieben habe:

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

Schreiben Sie eine einfache Klasse, die HttpServletRequestWrapper mit einer getParameter () - Methode unterzieht, die die bereinigte Version der Eingabe zurückgibt. Übergeben Sie dann eine Instanz Ihrer HttpServletRequestWrapper an Filter.doChain() anstelle des Anforderungsobjekts direkt.

10
Asaph

Das habe ich am Ende getan

//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

Ich hatte das gleiche Problem (Ändern eines Parameters von der HTTP-Anforderung im Filter). Am Ende habe ich einen ThreadLocal<String> verwendet. In der Filter habe ich:

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

In meinem Anforderungsprozessor (HttpServlet, JSF-Controller oder einem anderen HTTP-Anforderungsprozessor) erhalte ich den aktuellen Thread-Wert zurück:

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

Vorteile: 

  • vielseitiger als das Übergeben von HTTP-Parametern (Sie können POJO-Objekte übergeben)
  • etwas schneller (die URL muss nicht analysiert werden, um den Variablenwert zu extrahieren)
  • eleganter als die HttpServletRequestWrapper-Heizplatte
  • der variable Gültigkeitsbereich ist breiter als nur die HTTP-Anforderung (der Gültigkeitsbereich, den Sie beim Ausführen von request.setAttribute(String,Object) haben), d. h. Sie können auf die Variable in anderen Filtrierern zugreifen.

Nachteile:

  • Sie können diese Methode nur verwenden, wenn der Thread, der den Filter verarbeitet, mit dem Thread identisch ist, der die HTTP-Anforderung verarbeitet (dies ist bei allen mir bekannten Java-basierten Servern der Fall). Folglich funktioniert dieses nicht, wenn
    • Durchführen einer HTTP-Umleitung (da der Browser eine neue HTTP-Anforderung ausführt und es keine Möglichkeit gibt, zu garantieren, dass diese von demselben Thread verarbeitet wird)
    • Verarbeiten von Daten in separaten Threads, z. bei Verwendung von Java.util.stream.Stream.parallel, Java.util.concurrent.Future, Java.lang.Thread.
  • Sie müssen in der Lage sein, den Anforderungsprozessor/die Anwendung zu ändern

Einige Randnotizen:

  • Der Server verfügt über einen Thread-Pool, um die HTTP-Anforderungen zu verarbeiten. Da dies Pool ist: 

    1. ein Thread aus diesem Thread-Pool verarbeitet viele HTTP-Anforderungen, jedoch nur eine nach dem anderen (dh Sie müssen entweder Ihre Variable nach der Verwendung bereinigen oder sie für jede HTTP-Anforderung definieren. Achten Sie auf Code wie if (value!=null) { THREAD_VARIABLE.set(value);}, da Sie die wiederverwendet Wert aus der vorherigen HTTP-Anforderung, wenn value null ist: Nebeneffekte sind garantiert). 
    2. Es gibt keine Garantie dafür, dass zwei Anfragen von demselben Thread verarbeitet werden (dies kann der Fall sein, aber Sie haben keine Garantie). Wenn Sie Benutzerdaten von einer Anfrage zu einer anderen Anfrage speichern müssen, ist es besser, HttpSession.setAttribute() zu verwenden.
  • JEE @RequestScoped verwendet intern eine ThreadLocal, die Verwendung der ThreadLocal ist jedoch vielseitiger: Sie können sie in Nicht-JEE/CDI-Containern verwenden (z. B. in Multithread-JRE-Anwendungen)
0
Julien Kronegg

Versuchen Sie es mit request.setAttribute("param",value);. Es hat gut für mich funktioniert.

Bitte finden Sie dieses Codebeispiel:

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

Sie können Regular Expression für die Bereinigung verwenden. Rufen Sie vor dem Aufruf von chain.doFilter (request, response) innerhalb des Filters diesen Code auf . Hier ist Beispielcode:

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