Изменить параметр запроса с помощью фильтра сервлетов - PullRequest
105 голосов
/ 12 сентября 2009

Существующее веб-приложение работает на Tomcat 4.1.Существует проблема XSS со страницей, но я не могу изменить источник.Я решил написать фильтр сервлетов, чтобы очистить параметр, прежде чем его увидит страница.

Я хотел бы написать класс Filter, подобный этому:

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 не существует.

Как изменить значение параметра запроса перед передачей запроса по цепочке?

Ответы [ 7 ]

120 голосов
/ 12 сентября 2009

Как вы заметили, HttpServletRequest не имеет метода setParameter. Это является преднамеренным, поскольку класс представляет запрос в том виде, как он поступил от клиента, и изменение параметра не будет отражать это.

Одним из решений является использование класса HttpServletRequestWrapper, который позволяет обернуть один запрос другим. Вы можете создать подкласс этого класса и переопределить метод getParameter, чтобы вернуть очищенное значение. Затем вы можете передать этот завернутый запрос на chain.doFilter вместо исходного запроса.

Это немного уродливо, но это то, что API сервлетов говорит, что вы должны делать. Если вы попытаетесь передать что-либо еще doFilter, некоторые контейнеры сервлетов будут жаловаться, что вы нарушили спецификацию, и откажутся обрабатывать это.

Более элегантное решение - больше работы - измените исходный сервлет / JSP, который обрабатывает параметр, так, чтобы он ожидал запрос атрибут вместо параметра. Фильтр проверяет параметр, очищает его и устанавливает атрибут (используя request.setAttribute) с очищенным значением. Нет подклассов, нет спуфинга, но требуется изменить другие части вашего приложения.

68 голосов
/ 14 сентября 2009

Для справки, вот класс, который я закончил писать:

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) {
    }
}
10 голосов
/ 12 сентября 2009

Напишите простой класс, который вычисляет HttpServletRequestWrapper с помощью метода getParameter (), который возвращает очищенную версию ввода. Затем передайте экземпляр вашего HttpServletRequestWrapper в Filter.doChain() вместо объекта запроса напрямую.

1 голос
/ 23 января 2019

Это то, что я закончил

//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 голосов
/ 21 марта 2018

У меня была такая же проблема (изменение параметра из HTTP-запроса в фильтре). В итоге я использовал ThreadLocal<String>. В Filter у меня есть:

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

В моем обработчике запросов (HttpServlet, JSF-контроллере или любом другом обработчике HTTP-запросов) я получаю текущее значение потока:

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

Преимущества:

  • более универсален, чем передача параметров HTTP (вы можете передавать объекты POJO)
  • немного быстрее (не нужно разбирать URL для извлечения значения переменной)
  • более элегантный, чем HttpServletRequestWrapper шаблон
  • область действия переменной шире, чем просто HTTP-запрос (область, которую вы имеете при выполнении request.setAttribute(String,Object), т.е. вы можете получить доступ к переменной в других фильтрах.

Недостатки:

  • Этот метод можно использовать только в том случае, если поток, обрабатывающий фильтр, совпадает с потоком, обрабатывающим HTTP-запрос (это относится ко всем известным мне Java-серверам). Следовательно, не будет работать, если
    • делает перенаправление HTTP (потому что браузер делает новый HTTP-запрос и нет способа гарантировать, что он будет обработан тем же потоком)
    • обработка данных в отдельных потоках , например при использовании java.util.stream.Stream.parallel, java.util.concurrent.Future, java.lang.Thread.
  • Вы должны иметь возможность изменять обработчик запросов / приложение

Некоторые примечания:

  • На сервере имеется пул потоков для обработки HTTP-запросов. Так как это бассейн:

    1. Поток из этого пула потоков будет обрабатывать множество HTTP-запросов, но только по одному за раз (поэтому вам нужно либо очистить переменную после использования, либо определить ее для каждого HTTP-запроса = обратите внимание на код, такой как if (value!=null) { THREAD_VARIABLE.set(value);} потому что вы будете использовать значение из предыдущего HTTP-запроса, когда value равно нулю: побочные эффекты гарантированы).
    2. Нет никакой гарантии, что два запроса будут обработаны одним потоком (это может быть так, но у вас нет гарантии). Если вам нужно сохранить пользовательские данные от одного запроса к другому, лучше использовать HttpSession.setAttribute()
  • JEE @RequestScoped внутренне использует ThreadLocal, но использование ThreadLocal более универсально: вы можете использовать его в контейнерах не JEE / CDI (например, в многопоточных приложениях JRE)
0 голосов
/ 13 февраля 2016

Вы можете использовать Регулярное выражение для санации. Внутри фильтра перед вызовом chain.doFilter (запрос, ответ) , вызовите этот код. Вот пример кода:

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 голосов
/ 12 февраля 2016

Попробуйте request.setAttribute("param",value);. У меня все работало нормально.

Пожалуйста, найдите этот пример кода:

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
        }
    }
...