Servlet Filter - 특정 HTTP Method 요청 방지 필터 만들기

식빵·2024년 5월 27일
0

Java Lab

목록 보기
24/29

이번에 모의해킹과 관련된 테스트를 받았는데, 불필요한 HTTP Method 라는 항목에
대해서 취약점이 발견되었습니다.

그래서 HTTP Method 중에서 특정 Method 에 대해서는 사전에 차단시키는
Servlet Filter 를 만들었는데, 해당 소스를 공유합니다.



Filter 클래스 작성

참고로 Java 17, Tomcat 10 (=> jakarta 패키지) 를 기반으로 작성된 코드입니다.

package secure.filter;

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * <h2>LimitHttpMethodFilter</h2>
 * 불필요한 HTTP METHOD 요청 방지를 위한 Filter 이다.
 */
public class LimitHttpMethodFilter implements Filter {

    private static final Logger log = LoggerFactory.getLogger(LimitHttpMethodFilter.class);

    /**
     * web.xml 에서 LimitHttpMethodFilter 에 initParameter 로 limitHttpMethod 을<br>
     * 제공하지 않으면 빈 List 가 들어간다. 즉 아무런 검사를 수행하지 않게 된다.
     */
    private List<String> LIMIT_METHOD_LIST = Collections.emptyList();

    /**
     * 제한할 METHOD 목록(=limitHttpMethod)을 init parameter 에서 추출하여<br>
     * LIMIT_METHOD_LIST 에 설정한다. 설정하지 않으면 LIMIT_METHOD_LIST 는 <br>
     * 비어있는 리스트가 되며, 실질적인 검사를 수행하지 않게 된다.
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String limitHttpMethod = filterConfig.getInitParameter("limitHttpMethod");
        if (limitHttpMethod != null && !limitHttpMethod.trim().isEmpty()) {
            String[] split = limitHttpMethod.trim().split(",");
            LIMIT_METHOD_LIST = new ArrayList<>();
            for (String method : split) {
                LIMIT_METHOD_LIST.add(method.trim());
            }
        }
        log.debug("LimitHttpMethodFilter initialized with limit method list => {}", LIMIT_METHOD_LIST);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request instanceof HttpServletRequest httpRequest && response instanceof HttpServletResponse httpResponse) {
            String requestURL = httpRequest.getRequestURI();
            String method = httpRequest.getMethod();
            // 금지된 HTTP Method 리스트에 포함된 Method 요청인지 확인
            if (isLimitMethod(method)) 
            {
                log.debug("LimitHttpMethodFilter detected client's limit method request => {} {}", 
                			method, requestURL);
                configErrorResponse(httpResponse, requestURL, method);
                return;
            }
            chain.doFilter(request, response);
        }
    }

    /**
     * Client 요청의 Method 가 금지된 Method 인지 확인하는 메소드
     * @return 금지된 Method 이면 true, 아니면 false
     */
    private boolean isLimitMethod(String requestMethod) {
        for (String limitMethod : LIMIT_METHOD_LIST) {
            if (limitMethod.equalsIgnoreCase(requestMethod)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 에러 http response 을 설정하는 메소드이다.<br>
     * @param servletResponse 에러 http response 를 설정하기 위한 HttpServletResponse 인스턴스
     * @param requestURI Client 가 요청한 자원의 경로(=URI)
     * @param method Client 요청 HTTP 의 Method 명칭
     * @throws IOException HttpServletResponse 에 write 작업 시 어떠한 
     *                     이유에서든 문제가 생기면 발생하는 예외
     */
    private void configErrorResponse(HttpServletResponse servletResponse, 
    								String requestURI, 
                                    String method) throws IOException 
	{
        // 참고: https://developer.mozilla.org/ko/docs/Web/HTTP/Status/405
        servletResponse.setStatus(405); // HTTP Response Status => 405 Method Not Allowed

        // HEAD Method 는 BODY 를 제공하지 않는다. (https://http.dev/methods#head 참고)
        if (!"HEAD".equalsIgnoreCase(method)) {
            servletResponse.setCharacterEncoding("UTF-8");
            servletResponse.setHeader("Content-Type", "application/json");
            String errorResponseJson = createErrorResponseJson(requestURI);
            servletResponse.getWriter().write(errorResponseJson);
        } else {
            log.debug("Client Send HEAD METHOD Request. " + 
            		  "The http error response will not send payload");
        }
    }

    /**
     * 에러 http response 의 payload 에 사용될
     * @param currentRequestUrl client 요청 url
     * @return json 형태의 에러 메타 정보
     */
    private String createErrorResponseJson(String currentRequestUrl) {
        int ERROR_CODE = 999;
        String DEFAULT_ERROR_MSG = "지원하지 않는 HTTP METHOD 입니다.";
        return """
       {
          "code":"%s",
          "message":"%s",
          "result":"%s"
       }
       """.formatted(
            ERROR_CODE,
            DEFAULT_ERROR_MSG,
            String.format("[%s] 요청이 블록되었습니다.", currentRequestUrl)
        ).trim();
    }
}



web.xml 에 적용

  <!-- Limit Http Method Filter -->
  <filter>
    <filter-name>limitHttpMethodFilter</filter-name>
    <filter-class>secure.filter.LimitHttpMethodFilter</filter-class>
    <init-param>
      <!-- 제한하고자 하는 method 를 작성한다. 구분자는 , 이다 -->
      <param-name>limitHttpMethod</param-name>
      <param-value>HEAD,OPTIONS</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>limitHttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
  </filter-mapping>
profile
백엔드를 계속 배우고 있는 개발자입니다 😊

0개의 댓글