Programming

서블릿 필터로 요청 매개 변수 수정

procodes 2020. 8. 9. 10:33
반응형

서블릿 필터로 요청 매개 변수 수정


기존 웹 애플리케이션이 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존재하지 않습니다.

요청을 체인으로 전달하기 전에 요청 매개 변수의 값을 어떻게 변경할 수 있습니까?


앞서 언급했듯이 HttpServletRequestsetParameter 메서드가 없습니다. 이는 클래스가 클라이언트에서 온 요청을 나타내며 매개 변수를 수정해도이를 나타내지 않기 때문에 의도적입니다.

한 가지 해결책은 HttpServletRequestWrapper하나의 요청을 다른 요청으로 래핑 할 수 있는 클래스 를 사용하는 것입니다. 이를 하위 클래스로 분류하고 getParameter메서드를 재정 의하여 삭제 된 값을 반환 할 수 있습니다. 그런 다음 chain.doFilter원래 요청 대신 래핑 된 요청을에 전달할 수 있습니다 .

약간 못 생겼지 만 서블릿 API가해야한다고 말하는 것입니다. 다른 것을에 전달하려고하면 doFilter일부 서블릿 컨테이너가 사양을 위반했다고 불평하고 처리를 거부합니다.

더 우아한 솔루션은 더 많은 작업 입니다. 매개 변수 대신 요청 속성을 예상하도록 매개 변수를 처리하는 원래 서블릿 / JSP를 수정하십시오 . 필터는 매개 변수를 검사하고이를 request.setAttribute삭제하고 삭제 된 값으로 속성 (사용 )을 설정합니다 . 서브 클래 싱이나 스푸핑은 없지만 애플리케이션의 다른 부분을 수정해야합니다.


기록을 위해 여기에 내가 쓴 수업이 있습니다.

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

HttpServletRequestWrapper입력의 삭제 된 버전을 반환하는 getParameter () 메서드로 하위 계산하는 간단한 클래스를 작성합니다 . 그런 다음의 인스턴스를 전달 HttpServletRequestWrapper하는 Filter.doChain()대신 직접 요청 개체의.


동일한 문제가 발생했습니다 (필터의 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);}값을 재사용 할 것이기 때문에 코드에주의를 기울여야 합니다. valuenull 일 때 이전 HTTP 요청에서 : 부작용이 보장됨).
    2. There is no guarantee that two requests will be processed by the same thread (it may be the case but you have no guarantee). If you need to keep user data from one request to another request, it would be better to use HttpSession.setAttribute()
  • The JEE @RequestScoped internally uses a ThreadLocal, but using the ThreadLocal is more versatile: you can use it in non JEE/CDI containers (e.g. in multithreaded JRE applications)

This is what i ended up doing

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

Try request.setAttribute("param",value);. It worked fine for me.

Please find this code sample:

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

You can use Regular Expression for Sanitization. Inside filter before calling chain.doFilter(request, response) method, call this code. Here is Sample Code:

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

참고URL : https://stackoverflow.com/questions/1413129/modify-request-parameter-with-servlet-filter

반응형