Anforderungsparameter mit Servlet-Filter ändern


114

Eine vorhandene Webanwendung wird unter Tomcat 4.1 ausgeführt. Es gibt ein XSS-Problem mit einer Seite, aber ich kann die Quelle nicht ändern. Ich habe beschlossen, einen Servlet-Filter zu schreiben, um den Parameter zu bereinigen, bevor er auf der Seite angezeigt wird.

Ich möchte eine Filterklasse wie folgt 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) {
  }
}

Existiert ServletRequest.setParameteraber nicht.

Wie kann ich den Wert des Anforderungsparameters ändern, bevor ich die Anforderung an die Kette weitergebe?


In HttpServletRequestWrapper sind viele APIs definiert. Ich versuche, jede einzelne API sinnvoll zu verstehen. Javadoc hilft nicht, APIs wie 'userinRole', 'getPrincipal'etx. Zu verstehen. Wo genau kann ich Hilfe bekommen?
sskumar86

Antworten:


132

Wie Sie bereits bemerkt haben, HttpServletRequestgibt es keine setParameter-Methode. Dies ist absichtlich, da die Klasse die Anforderung darstellt, wie sie vom Client stammt, und das Ändern des Parameters dies nicht darstellen würde.

Eine Lösung besteht darin, die HttpServletRequestWrapperKlasse zu verwenden, mit der Sie eine Anforderung mit einer anderen umschließen können. Sie können dies in Unterklassen unterteilen und die getParameterMethode überschreiben , um Ihren bereinigten Wert zurückzugeben. Sie können diese umschlossene Anforderung dann an chain.doFilteranstelle der ursprünglichen Anforderung übergeben.

Es ist ein bisschen hässlich, aber genau das sagt die Servlet-API, dass Sie tun sollten. Wenn Sie versuchen, etwas anderes weiterzugeben doFilter, beschweren sich einige Servlet-Container, dass Sie gegen die Spezifikation verstoßen haben, und lehnen es ab, damit umzugehen.

Eine elegantere Lösung ist mehr Arbeit - das Original - Servlet / JSP ändern, die die Parameter verarbeitet, so dass er eine Anforderung erwartet Attribut anstelle einem Parameter. Der Filter untersucht den Parameter, bereinigt ihn und setzt das Attribut (using request.setAttribute) mit dem bereinigten Wert. Keine Unterklassen, kein Spoofing, aber Sie müssen andere Teile Ihrer Anwendung ändern.


6
HttpServletRequestWrapper ist wunderbar; Ich wusste nie, dass es existiert. Vielen Dank!
Jeremy Stein

3
Vielen Dank für die Alternative zur Attributeinstellung! Ich habe Beispielcode mit Anforderungs- und Antwort-Wrappern in Head First Servlets und JSPs gesehen und konnte nicht glauben, dass die Spezifikation die Leute dazu drängt, die Dinge auf diese Weise zu erledigen.
Kevin

Ich hatte meine Werte im Controller erreicht und den Parameter (E-Mail und Pass) festgelegt. Wie kann ich nun die Werte in meinem Servlet ersetzen <property name="username" value="somemail@gmail.com" /> //Change email on logging in <property name="password" value="*********" />//Change Password on logging in
?

73

Für die Aufzeichnung ist hier 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) {
    }
}

5
Möglicherweise müssen Sie auch die Methode getParameterMap berücksichtigen. Möglicherweise wird eine Ausnahme ausgelöst und nicht unterstützt, nur damit keine Komponenten die Methode verwenden und die Bereinigungslogik überspringen.
Tom

1
Guter Punkt, Tom. In diesem speziellen Fall habe ich überprüft, ob es nicht aufgerufen wurde, aber ich hätte es der Vollständigkeit halber und der nächsten Person zuliebe hinzufügen sollen. Vielen Dank!
Jeremy Stein

13
Es sieht so aus, als wäre ich die nächste Person, Jeremy. Ich habe diesen Beitrag gefunden, als ich nach Optionen zum Ändern von Daten gesucht habe, die von einer externen Anwendung an ein Servlet eines Drittanbieters übergeben werden. In meinem Fall hat das Servlet nicht HTTPServletRequest.getParameter (), getParameterMap () oder sogar getAttribute () aufgerufen, um die Anforderungsdaten abzurufen. Daher habe ich durch Ausprobieren festgestellt, dass das Servlet HTTPServletRequest.getInputStream () aufruft. und getQueryString (). Mein Rat an alle, die diese Aufgabe für geschlossene Servlets versuchen, ist, jeden einzelnen Accessor in HTTPServletRequest zu verpacken, um zu verstehen, was wirklich los ist
Fred Sobotka

3
Für SrpingMVC müssen Sie getParameterValues ​​() überschreiben, um Spring zu täuschen. RequestParamMethodArgumentResolver.resovleName () verwendet diese Methode, sodass Sie MissingServletRequestParameterException erhalten, ohne sie zu überschreiben. Getestet auf Spring Boot 1.2.6 mit Spring-Web 4.1.7.
Barryku

10

Schreiben Sie eine einfache Klasse, die HttpServletRequestWrappermit einer getParameter () -Methode subkalssiert, die die bereinigte Version der Eingabe zurückgibt. Übergeben Sie dann eine Instanz von HttpServletRequestWrapperan Filter.doChain()anstelle des Anforderungsobjekts direkt.


1

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

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 (keine Notwendigkeit, die URL zu analysieren, um den Variablenwert zu extrahieren)
  • eleganter als die HttpServletRequestWrapperBoilerplate
  • Der Variablenbereich ist breiter als nur die HTTP-Anforderung (der Bereich, den Sie dabei haben request.setAttribute(String,Object), dh Sie können in anderen Filtern auf die Variable 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 dies nicht, wenn
    • Ausführen einer HTTP-Umleitung (da der Browser eine neue HTTP-Anforderung ausführt und nicht garantiert werden kann, dass sie von demselben Thread verarbeitet wird)
    • Verarbeiten von Daten in separaten Threads , beispielsweise bei der Verwendung von java.util.stream.Stream.parallel, java.util.concurrent.Future, java.lang.Thread.
  • Sie müssen in der Lage sein, den Anforderungsprozessor / die Anforderungsanwendung zu ändern

Einige Randnotizen:

  • Der Server verfügt über einen Thread-Pool zum Verarbeiten der HTTP-Anforderungen. Da dies Pool ist:

    1. Ein Thread aus diesem Thread-Pool verarbeitet viele HTTP-Anforderungen, jedoch jeweils nur eine (Sie müssen also entweder Ihre Variable nach der Verwendung bereinigen oder sie für jede HTTP-Anforderung definieren = achten Sie auf Code, z. B. if (value!=null) { THREAD_VARIABLE.set(value);}weil Sie den Wert wiederverwenden von der vorherigen HTTP-Anfrage, wenn valuenull ist: Nebenwirkungen 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, diese zu verwendenHttpSession.setAttribute()
  • Das JEE verwendet @RequestScopedintern a ThreadLocal, aber die Verwendung von ThreadLocalist vielseitiger: Sie können es in Nicht-JEE / CDI-Containern verwenden (z. B. in Multithread-JRE-Anwendungen).

Ist es wirklich eine gute Idee, einen Parameter im Bereich des Threads festzulegen? Werden mehrere Anfragen jemals denselben Thread sehen? (Ich nehme nicht an)
Zachary Craig

Ist es eine gute Idee = ja (aber Sie müssen wissen, was Sie tun, trotzdem macht der JEE @RequestScopedintern dasselbe). Werden mehrere Anfragen den gleichen Thread sehen = nein (oder zumindest haben Sie keine Garantie). Ich habe die Antwort bearbeitet, um diese Punkte zu präzisieren.
Julien Kronegg

1

Das habe ich letztendlich 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();
        }
    }
}

1

Basierend auf all Ihren Bemerkungen hier ist mein Vorschlag, der für mich funktioniert hat:

 private final class CustomHttpServletRequest extends HttpServletRequestWrapper {

    private final Map<String, String[]> queryParameterMap;
    private final Charset requestEncoding;

    public CustomHttpServletRequest(HttpServletRequest request) {
        super(request);
        queryParameterMap = getCommonQueryParamFromLegacy(request.getParameterMap());

        String encoding = request.getCharacterEncoding();
        requestEncoding = (encoding != null ? Charset.forName(encoding) : StandardCharsets.UTF_8);
    }

    private final Map<String, String[]> getCommonQueryParamFromLegacy(Map<String, String[]> paramMap) {
        Objects.requireNonNull(paramMap);

        Map<String, String[]> commonQueryParamMap = new LinkedHashMap<>(paramMap);

        commonQueryParamMap.put(CommonQueryParams.PATIENT_ID, new String[] { paramMap.get(LEGACY_PARAM_PATIENT_ID)[0] });
        commonQueryParamMap.put(CommonQueryParams.PATIENT_BIRTHDATE, new String[] { paramMap.get(LEGACY_PARAM_PATIENT_BIRTHDATE)[0] });
        commonQueryParamMap.put(CommonQueryParams.KEYWORDS, new String[] { paramMap.get(LEGACY_PARAM_STUDYTYPE)[0] });

        String lowerDateTime = null;
        String upperDateTime = null;

        try {
            String studyDateTime = new SimpleDateFormat("yyyy-MM-dd").format(new SimpleDateFormat("dd-MM-yyyy").parse(paramMap.get(LEGACY_PARAM_STUDY_DATE_TIME)[0]));

            lowerDateTime = studyDateTime + "T23:59:59";
            upperDateTime = studyDateTime + "T00:00:00";

        } catch (ParseException e) {
            LOGGER.error("Can't parse StudyDate from query parameters : {}", e.getLocalizedMessage());
        }

        commonQueryParamMap.put(CommonQueryParams.LOWER_DATETIME, new String[] { lowerDateTime });
        commonQueryParamMap.put(CommonQueryParams.UPPER_DATETIME, new String[] { upperDateTime });

        legacyQueryParams.forEach(commonQueryParamMap::remove);
        return Collections.unmodifiableMap(commonQueryParamMap);

    }

    @Override
    public String getParameter(String name) {
        String[] params = queryParameterMap.get(name);
        return params != null ? params[0] : null;
    }

    @Override
    public String[] getParameterValues(String name) {
        return queryParameterMap.get(name);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
            return queryParameterMap; // unmodifiable to uphold the interface contract.
        }

        @Override
        public Enumeration<String> getParameterNames() {
            return Collections.enumeration(queryParameterMap.keySet());
        }

        @Override
        public String getQueryString() {
            // @see : https://stackoverflow.com/a/35831692/9869013
            // return queryParameterMap.entrySet().stream().flatMap(entry -> Stream.of(entry.getValue()).map(value -> entry.getKey() + "=" + value)).collect(Collectors.joining("&")); // without encoding !!
            return queryParameterMap.entrySet().stream().flatMap(entry -> encodeMultiParameter(entry.getKey(), entry.getValue(), requestEncoding)).collect(Collectors.joining("&"));
        }

        private Stream<String> encodeMultiParameter(String key, String[] values, Charset encoding) {
            return Stream.of(values).map(value -> encodeSingleParameter(key, value, encoding));
        }

        private String encodeSingleParameter(String key, String value, Charset encoding) {
            return urlEncode(key, encoding) + "=" + urlEncode(value, encoding);
        }

        private String urlEncode(String value, Charset encoding) {
            try {
                return URLEncoder.encode(value, encoding.name());
            } catch (UnsupportedEncodingException e) {
                throw new IllegalArgumentException("Cannot url encode " + value, e);
            }
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            throw new UnsupportedOperationException("getInputStream() is not implemented in this " + CustomHttpServletRequest.class.getSimpleName() + " wrapper");
        }

    }

Hinweis: Bei queryString () müssen ALLE Werte für jeden KEY verarbeitet werden. Vergessen Sie nicht, encodeUrl () zu verwenden, wenn Sie bei Bedarf eigene Parameterwerte hinzufügen

Wenn Sie request.getParameterMap () oder eine Methode aufrufen, die request.getReader () aufruft und mit dem Lesen beginnt, verhindern Sie als Einschränkung weitere Aufrufe von request.setCharacterEncoding (...).


0

Sie können den regulären Ausdruck für die Desinfektion verwenden. Rufen Sie diesen Code im Filter auf, bevor Sie die Methode chain.doFilter (Anfrage, Antwort) aufrufen. 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();   
    }
}

1
Sie ändern die ursprünglichen Anforderungsparameter nicht auf diese Weise, sondern auf einer Kopie.
Mehdi

-1

Versuchen Sie es 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
        }
    }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.