Sửa đổi thông số yêu cầu với bộ lọc servlet


114

Ứng dụng web hiện có đang chạy trên Tomcat 4.1. Đã xảy ra sự cố XSS với một trang nhưng tôi không thể sửa đổi nguồn. Tôi đã quyết định viết một bộ lọc servlet để làm sạch tham số trước khi nó được nhìn thấy bởi trang.

Tôi muốn viết một lớp Bộ lọc như thế này:

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

Nhưng ServletRequest.setParameterkhông tồn tại.

Làm cách nào để thay đổi giá trị của tham số yêu cầu trước khi chuyển yêu cầu xuống chuỗi?


HttpServletRequestWrapper có rất nhiều API được định nghĩa, tôi đang cố gắng hiểu ý nghĩa từng API .Javadoc không giúp hiểu được các API như 'userinRole', 'getPrincipal'etx., Chính xác thì tôi có thể nhận trợ giúp ở đâu?
sskumar86

Câu trả lời:


132

Như bạn đã lưu ý HttpServletRequest, không có phương thức setParameter. Điều này là có chủ ý, vì lớp đại diện cho yêu cầu khi nó đến từ máy khách và việc sửa đổi tham số sẽ không đại diện cho điều đó.

Một giải pháp là sử dụng HttpServletRequestWrapperlớp, cho phép bạn kết hợp một yêu cầu này với một yêu cầu khác. Bạn có thể phân lớp đó và ghi đè getParameterphương thức để trả về giá trị đã khử trùng của bạn. Sau đó, bạn có thể chuyển yêu cầu được bọc đó đến chain.doFilterthay vì yêu cầu ban đầu.

Nó hơi xấu, nhưng đó là những gì API servlet nói rằng bạn nên làm. Nếu bạn cố gắng chuyển bất kỳ thứ gì khác tới doFilter, một số thùng chứa servlet sẽ phàn nàn rằng bạn đã vi phạm thông số kỹ thuật và sẽ từ chối xử lý.

Một giải pháp thanh lịch hơn là nhiều công việc hơn - sửa đổi servlet / JSP ban đầu xử lý tham số, để nó mong đợi một thuộc tính yêu cầu thay vì một tham số. Bộ lọc kiểm tra tham số, làm sạch nó và đặt thuộc tính (sử dụng request.setAttribute) với giá trị đã khử trùng. Không phân lớp, không giả mạo, nhưng yêu cầu bạn sửa đổi các phần khác trong ứng dụng của mình.


6
HttpServletRequestWrapper thật tuyệt vời; Tôi không bao giờ biết nó tồn tại. Cảm ơn!
Jeremy Stein

3
Cảm ơn vì sự thay thế cài đặt thuộc tính! Đã xem mã mẫu bằng cách sử dụng trình bao bọc yêu cầu và phản hồi trong Head First Servlets và JSP và không thể tin rằng thông số kỹ thuật lại thúc đẩy mọi người tiếp cận mọi thứ theo cách đó.
Kevin

Tôi đã đưa tay ra với các giá trị của tôi trong bộ điều khiển và tôi đã thiết tha tham số (email và pass) ... bây giờ làm thế nào tôi có thể thay thế các giá trị trong servlet của tôi <property name="username" value="somemail@gmail.com" /> //Change email on logging in <property name="password" value="*********" />//Change Password on logging in
Umashankar

73

Về bản ghi, đây là lớp tôi đã kết thúc:

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
Bạn cũng có thể cần tính đến phương thức getParameterMap. Có thể ném và ngoại lệ không được hỗ trợ để không có thành phần nào sử dụng phương thức và bỏ qua logic sanitize.
Tom

1
Tốt, Tom. Trong trường hợp cụ thể này, tôi đã kiểm tra và thấy nó không được gọi là gì, nhưng đáng lẽ tôi nên thêm điều đó cho hoàn chỉnh và vì lợi ích của người tiếp theo. Cảm ơn!
Jeremy Stein

13
Có vẻ như tôi là người tiếp theo, Jeremy. Tôi tìm thấy bài đăng này khi tìm kiếm các tùy chọn để sửa đổi dữ liệu được chuyển từ một ứng dụng bên ngoài sang một servlet của bên thứ ba. Trong trường hợp của tôi, servlet không gọi HTTPServletRequest.getParameter (), getParameterMap () hoặc thậm chí getAttribute () để lấy dữ liệu yêu cầu, do đó, qua thử nghiệm và lỗi, tôi xác định rằng servlet đang gọi HTTPServletRequest.getInputStream () và getQueryString (). Lời khuyên của tôi cho bất cứ ai cố gắng nhiệm vụ này cho servlets khép kín là để quấn mỗi accessor duy nhất trong HttpServletRequest để hiểu những gì đang thực sự xảy ra
Fred Sobotka

3
Đối với SrpingMVC, bạn sẽ cần ghi đè getParameterValues ​​() để đánh lừa Spring. RequestParamMethodArgumentResolver.resovleName () sử dụng phương thức đó, vì vậy bạn sẽ nhận được MissingServletRequestParameterException mà không cần ghi đè. Đã thử nghiệm trên Spring Boot 1.2.6 với spring-web 4.1.7.
barryku

10

Viết một lớp đơn giản phân cấp con HttpServletRequestWrapperbằng phương thức getParameter () trả về phiên bản đầu vào đã được khử trùng. Sau đó chuyển trực tiếp một thể hiện của bạn HttpServletRequestWrappertới Filter.doChain()thay vì đối tượng yêu cầu.


1

Tôi đã gặp vấn đề tương tự (thay đổi một tham số từ yêu cầu HTTP trong Bộ lọc). Tôi đã kết thúc bằng cách sử dụng a ThreadLocal<String>. Trong Filtertôi có:

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

Trong bộ xử lý yêu cầu của tôi ( HttpServlet, bộ điều khiển JSF hoặc bất kỳ bộ xử lý yêu cầu HTTP nào khác), tôi lấy lại giá trị luồng hiện tại:

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

Ưu điểm:

  • linh hoạt hơn việc truyền các tham số HTTP (bạn có thể chuyển các đối tượng POJO)
  • nhanh hơn một chút (không cần phân tích cú pháp URL để trích xuất giá trị biến)
  • thanh lịch hơn HttpServletRequestWrapper lò hơi
  • phạm vi biến rộng hơn chỉ yêu cầu HTTP (phạm vi bạn có khi thực hiện request.setAttribute(String,Object), tức là bạn có thể truy cập biến trong các bộ lọc khác.

Nhược điểm:

  • Bạn chỉ có thể sử dụng phương pháp này khi luồng xử lý bộ lọc giống với luồng xử lý yêu cầu HTTP (đây là trường hợp xảy ra trong tất cả các máy chủ dựa trên Java mà tôi biết). Do đó, điều này sẽ không hoạt động khi
    • thực hiện chuyển hướng HTTP (vì trình duyệt thực hiện một yêu cầu HTTP mới và không có cách nào để đảm bảo rằng nó sẽ được xử lý bởi cùng một chuỗi)
    • xử lý dữ liệu trong chủ đề riêng biệt , ví dụ như khi sử dụng java.util.stream.Stream.parallel, java.util.concurrent.Future, java.lang.Thread.
  • Bạn phải có thể sửa đổi trình xử lý yêu cầu / ứng dụng

Một số lưu ý bên lề:

  • Máy chủ có một nhóm Luồng để xử lý các yêu cầu HTTP. Vì đây là hồ bơi:

    1. một Luồng từ nhóm luồng này sẽ xử lý nhiều yêu cầu HTTP, nhưng chỉ một yêu cầu tại một thời điểm (vì vậy bạn cần dọn dẹp biến của mình sau khi sử dụng hoặc xác định nó cho mỗi yêu cầu HTTP = chú ý đến mã chẳng hạn như if (value!=null) { THREAD_VARIABLE.set(value);}vì bạn sẽ sử dụng lại giá trị từ yêu cầu HTTP trước đó khi valuelà null: các tác dụng phụ được đảm bảo).
    2. Không có gì đảm bảo rằng hai yêu cầu sẽ được xử lý bởi cùng một chuỗi (có thể là như vậy nhưng bạn không có gì đảm bảo). Nếu bạn cần giữ dữ liệu người dùng từ yêu cầu này đến yêu cầu khác, tốt hơn là sử dụngHttpSession.setAttribute()
  • JEE @RequestScopednội bộ sử dụng a ThreadLocal, nhưng sử dụng thì ThreadLocallinh hoạt hơn: bạn có thể sử dụng nó trong các vùng chứa không phải JEE / CDI (ví dụ: trong các ứng dụng JRE đa luồng)

Đặt một tham số trong phạm vi của luồng có thực sự là một ý tưởng hay không? Nhiều yêu cầu có bao giờ thấy cùng một chủ đề không? (Tôi giả sử không)
Zachary Craig

Đó có phải là một ý kiến ​​hay không = có (nhưng bạn cần biết mình đang làm gì, dù sao thì bên trong JEE @RequestScopedcũng làm như vậy). Nhiều yêu cầu sẽ thấy cùng một chủ đề = không (hoặc ít nhất là bạn không có gì đảm bảo). Tôi đã chỉnh sửa câu trả lời để chính xác những điểm này.
Julien Kronegg 23/03/18

1

Đây là những gì tôi đã kết thúc

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

Dựa trên tất cả các nhận xét của bạn, đây là đề xuất của tôi đã phù hợp với tôi:

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

    }

lưu ý: queryString () yêu cầu xử lý TẤT CẢ các giá trị cho mỗi KEY và đừng quên encodeUrl () khi thêm các giá trị tham số của riêng bạn, nếu cần

Có một giới hạn là nếu bạn gọi request.getParameterMap () hoặc bất kỳ phương thức nào gọi request.getReader () và bắt đầu đọc, bạn sẽ ngăn chặn bất kỳ lệnh gọi nào khác tới request.setCharacterEncoding (...)


0

Bạn có thể sử dụng Biểu thức chính quy cho Sanitization. Bên trong bộ lọc trước khi gọi phương thức chain.doFilter (yêu cầu, phản hồi) , hãy gọi mã này. Đây là Mã mẫu:

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
Bạn không sửa đổi các tham số yêu cầu ban đầu theo cách này, nhưng trên một bản sao.
Mehdi

-1

Thử request.setAttribute("param",value); . Nó làm việc tốt cho tôi.

Vui lòng tìm mẫu mã này:

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
        }
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.