Изменить параметр запроса с помощью фильтра сервлета

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

Я хотел бы написать класс фильтра следующим образом:

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

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

Ответ 1

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

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

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

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

Попробуйте 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
        }
    }

Ответ 5

Вы можете использовать Регулярное выражение для санитарии. Внутри фильтра перед вызовом метода 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();   
    }
}

Ответ 6

У меня была такая же проблема (изменение параметра из 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)

Ответ 7

Это то, что я в итоге делал

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