Змініть параметр запиту за допомогою фільтра сервлетів


114

Існуюча веб-програма працює на 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не існує.

Як я можу змінити значення параметра запиту перед тим, як передати запит по ланцюгу?


У HttpServletRequestWrapper визначено безліч API. Я намагаюся зрозуміти кожен API досконало. Явадок не допомагає зрозуміти такі API, як "userinRole", "getPrincipal'etx". Де саме я можу отримати допомогу?
sskumar86

Відповіді:


132

Як ви зазначали HttpServletRequest, не існує методу setParameter. Це навмисно, оскільки клас представляє запит як прийшов від клієнта, і зміна параметра не представляла б цього.

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

Це трохи некрасиво, але це те, що API сервлетів говорить, що ви повинні зробити. Якщо ви спробуєте передати щось інше doFilter, деякі контейнери сервлетів поскарзяться на те, що ви порушили специфікацію, і відмовляться від цього.

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


6
HttpServletRequestWrapper чудовий; Я ніколи не знав, що воно існує. Дякую!
Джеремі Штейн

3
Дякуємо за альтернативу налаштування атрибутів! Бачив зразок коду, використовуючи обгортки запитів та відповідей у ​​Head First Servlets та JSP, і не міг повірити, що специфікація підштовхує людей йти до цього шляху.
Кевін

Я потягнувся зі своїми значеннями в контролері, і я встановив параметр tha (електронна пошта та пропуск) ... тепер, як я можу замінити значення у своєму сервлеті <property name="username" value="somemail@gmail.com" /> //Change email on logging in <property name="password" value="*********" />//Change Password on logging in
UmaShankar

73

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

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
Вам також може знадобитися облік методу getParameterMap. Можливо, викинути і непідтримуваний виняток просто так, що жоден компонент не використовує метод і пропускає санітарну логіку.
Том

1
Добрий момент, Томе. У цьому конкретному випадку я перевірив і виявив, що його не називають, але я повинен був додати це для повноти та заради наступної людини. Дякую!
Джеремі Штейн

13
Схоже, я наступний чоловік, Джеремі. Я знайшов цю публікацію, коли шукав варіанти змінити дані, що передаються із зовнішньої програми на сторонній сервлет. У моєму випадку сервлет не викликав HTTPServletRequest.getParameter (), getParameterMap () або навіть getAttribute () для отримання даних запиту, тож, через пробу та помилку, я визначив, що сервлет викликав HTTPServletRequest.getInputStream () і getQueryString (). Моя порада всім, хто намагається виконати це завдання для закритих сервлетів, - це обернути кожного окремого доступу в HTTPServletRequest, щоб зрозуміти, що відбувається насправді
Фред Соботка,

3
Для SrpingMVC вам потрібно буде замінити getParameterValues ​​(), щоб обдурити Spring. RequestParamMethodArgumentResolver.resovleName () використовує цей метод, тож ви отримаєте MissingServletRequestParameterException без перевищення. Тестовано на Spring Boot 1.2.6 з Spring-web 4.1.7.
barryku

10

Напишіть простий клас, який підраховує HttpServletRequestWrapperметодом getParameter (), який повертає санізовану версію вводу. Потім передати екземпляр вашої , HttpServletRequestWrapperщоб Filter.doChain()замість об'єкта запиту безпосередньо.


1

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

Чи дійсно гарна ідея встановити параметр в області потоку? Чи побачать кілька запитів колись одну й ту саму нитку? (Я припускаю, що ні)
Захарій Крейг

Це гарна ідея = так (але вам потрібно знати, що ви робите, все одно JEE @RequestScopedробить все те саме). Чи буде кілька запитів бачити один і той же потік = ні (або принаймні у вас немає гарантії). Я відредагував відповідь, щоб точно вказати ці моменти.
Жульєн Кронегг

1

Це те, що я закінчив робити

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

На основі всіх ваших зауважень ось моя пропозиція, яка працювала на мене:

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

    }

Примітка: queryString () вимагає обробити ВСІ значення для кожного KEY і не забудьте кодуватиUrl (), додаючи власні параметри, якщо потрібно

Як обмеження, якщо ви зателефонуєте на request.getParameterMap () або будь-який метод, який викликав би request.getReader () і розпочав читання, ви запобіжете подальшим викликам на request.setCharacterEncoding (...)


0

Ви можете використовувати регулярні вирази для дезінфекції. Внутрішній фільтр перед тим, як викликати метод 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();   
    }
}

1
Ви не змінюєте вихідні параметри запиту таким чином, але на копії.
Мехді

-1

Спробуйте 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
        }
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.