이번에 모의해킹과 관련된 테스트를 받았는데, 불필요한 HTTP Method
라는 항목에
대해서 취약점이 발견되었습니다.
그래서 HTTP Method 중에서 특정 Method 에 대해서는 사전에 차단시키는
Servlet 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();
}
}
<!-- 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>