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


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를 의미있게 이해하려고합니다 .Javadoc은 'userinRole', 'getPrincipal'etx와 같은 API를 이해하는 데 도움이되지 않습니다. 정확히 어디에서 도움을받을 수 있습니까?
sskumar86

답변:


132

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

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

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

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


6
HttpServletRequestWrapper는 훌륭합니다. 나는 그것이 존재한다는 것을 결코 몰랐습니다. 감사!
Jeremy Stein

3
속성 설정 대안에 감사드립니다! Head First Servlet 및 JSP에서 요청 및 응답 래퍼를 사용하는 샘플 코드를 보았지만 사양이 사람들을 그런 식으로 진행하도록하는 것을 믿을 수 없었습니다.
Kevin

지금 난 내 서블릿의 값을 대체 할 수있는 방법을 ... 나는 컨트롤러 내 값을 밖으로 도달했고 나는 그쪽으로 매개 변수 (이메일 및 패스)를 설정 한 <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 메소드를 고려해야 할 수도 있습니다. 어떤 구성 요소도 메서드를 사용하지 않고 삭제 논리를 건너 뛰도록 예외를 throw하고 지원하지 않을 수 있습니다.

1
좋은 지적이야, 톰. 이 특별한 경우에, 나는 그것이 호출되지 않는 것을 확인하고 발견했지만 완전성과 다음 사람을 위해 그것을 추가해야했습니다. 감사!
Jeremy Stein

13
제가 다음 사람인 것 같아요, 제레미. 외부 애플리케이션에서 타사 서블릿으로 전달되는 데이터를 수정하는 옵션을 찾을 때이 게시물을 찾았습니다. 제 경우에는 서블릿이 요청 데이터를 얻기 위해 HTTPServletRequest.getParameter (), getParameterMap () 또는 getAttribute ()를 호출하지 않았으므로 시행 착오를 통해 서블릿이 HTTPServletRequest.getInputStream ()을 호출하고 있음을 확인했습니다. 및 getQueryString (). 닫힌 서블릿에 대해이 작업을 시도하는 모든 사람에게 내 조언은 실제로 무슨 일이 일어나고 있는지 이해하기 위해 HTTPServletRequest에서 모든 접근자를 래핑하는 것입니다.
Fred Sobotka

3
SrpingMVC의 경우 Spring을 속이기 위해 getParameterValues ​​()를 재정의해야합니다. RequestParamMethodArgumentResolver.resovleName ()은 해당 메서드를 사용하므로 재정의하지 않고 MissingServletRequestParameterException이 발생합니다. spring-web 4.1.7을 사용하여 Spring Boot 1.2.6에서 테스트되었습니다.
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);}값을 재사용 할 것이므로 코드에주의를 기울여야 합니다. valuenull 일 때 이전 HTTP 요청에서 : 부작용이 보장됨).
    2. 두 개의 요청이 동일한 스레드에서 처리된다는 보장은 없습니다 (그럴 수 있지만 보장 할 수 없음). 한 요청에서 다른 요청으로 사용자 데이터를 유지해야하는 경우 사용하는 것이 좋습니다.HttpSession.setAttribute()
  • JEE는 @RequestScoped내부적으로를 사용 하지만를 ThreadLocal사용하는 ThreadLocal것이 더 다양합니다. 비 JEE / CDI 컨테이너 (예 : 다중 스레드 JRE 응용 프로그램)에서 사용할 수 있습니다.

스레드의 범위에 매개 변수를 설정하는 것이 정말 좋은 생각입니까? 여러 요청에 동일한 스레드가 표시됩니까? (난 안 가정)
재커리 크레이그

좋은 생각입니까 = 예 (하지만 JEE @RequestScoped는 내부적으로 동일하게 수행하는 작업을 알아야 합니다). 여러 요청에 동일한 스레드가 표시됩니다 = 아니요 (또는 최소한 보장 할 수 없음). 이 점에 대한 답을 수정했습니다.
Julien Kronegg

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의 모든 값을 처리해야하며 필요한 경우 고유 한 매개 변수 값을 추가 할 때 encodeUrl ()을 잊지 마십시오.

제한적으로 request.getParameterMap () 또는 request.getReader ()를 호출하는 메서드를 호출하고 읽기를 시작하면 request.setCharacterEncoding (...)에 대한 추가 호출을 방지 할 수 있습니다.


0

Sanitization에 정규식 을 사용할 수 있습니다 . chain.doFilter (request, response) 메소드를 호출 하기 전에 필터 내 에서이 코드를 호출하십시오. 다음은 샘플 코드입니다.

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
이런 방식으로 원래 요청 매개 변수를 수정하지 않고 사본에서 수정합니다.
Mehdi

-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
        }
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.