티스토리 뷰

어느 날 고객사에서 Apache Commons Fileupload 이슈가 인입되었다

apache commons file upload library 1.5 버전 미만에서 RequestParts 개수 제한을 걸어두지 않아 Dos 공격이 가능하다

 

Apache Tomcat: Important: Apache Tomcat denial of service (CVE-2023-24998)

Rapid7's VulnDB is curated repository of vetted computer software exploits and exploitable vulnerabilities.

www.rapid7.com

 

이후로 팀원분이 내부 동작을 파악하고 간단하게 리뷰를 해주셨는데 들을 때는 그렇구나 하고

수동적으로 들어서 그런지 남는 게 없는 것 같아 주말에 내부 동작을 한번 파보고자 프로젝트를 새로 하나 팠다

jdk17, springboot 3.0.5를 사용했고 UI는 https://www.bezkoder.com/thymeleaf-file-upload/를 참고했다

 

GitHub - Ryu-JeongMoon/spring-practice

Contribute to Ryu-JeongMoon/spring-practice development by creating an account on GitHub.

github.com

 

시작부터 문제가 있었는데 새로 판 프로젝트에 이번 이슈의 원흉인 CommonsMultipartResolver가 없었다

원인은 2021년에 스프링 메인 컨트리뷰터가 내린 설계 결정이었다

관리도 안 되고 javax.servlet -> jakarta.servlet 네임스페이스도 바꾸지 않는 file upload를 지원하지 않겠다는 것이다

 

Drop outdated Servlet-based integrations: Commons FileUpload, FreeMarker JSP support, Tiles · Issue #27423 · spring-projects/s

Several integration options in our web support date back to the 2005 era, not having seen maintenance in recent years and apparently not getting an upgrade for Jakarta Servlet (in the jakarta.servl...

github.com

 

여기까지만 알고 있었어도 사실 간단한 문제다

특별한 이유가 없다면 제거하기로 결정한 CommonsMultipartResolver가 다시 지원될 일은 거의 없을 것이다

넓게 본다면 Spring6, Springboot3에서 없어진 클래스 가지고 고민할 필요가 없다

그럼 CommonsMultipartResolver 대신 무엇을 써야 할까?

컨테이너 종속적인 StandardServletMultipartResolver를 쓰란다, 스프링에서 지원하는 다른 구현체가 없기도 하다

 

기존에 CommonsMultipartResolver는 사용자에게 선택권을 준 것이라 볼 수 있다

어떤 Servlet Container를 사용하던 apache.commons.fileupload에 종속된 코드를 작성할 수 있다

즉 해당 라이브러리만 이용하면 tomcat, jetty, undertow 등 Servlet Container를 맘대로 갈아낄 수 있다는 것이다

대안으로 나온 StandardServletMultipartResolver는 Servlet Container에게 맡겨버린다

어떤 라이브러리를 쓰던지 Servlet Container의 기본 구현에 맡기면 된다

일반적으로 라이브러리 바꿀 일은 많아도 Servlet Container 바꿀 일은 극히 드물지 않은가?

게다가 Servlet Container을 바꾸면 파일 업로드가 문제가 아니라 세부적인 설정을 다 바꿔줘야 하는 게 더 심각하다

Tomcat, Undertow까지만 테스트해봤는데 당연하게도 별 이상이 없었다

 

한 가지 재밌는 점은 Servlet Container로 Tomcat을 쓸 때

StandardServletMultipartResolver의 내부를 따라가면 apache commons 라이브러리를 사용한다

Facade 패턴을 사용해 apache.commons 라이브러리를 적당히 감싼 형태로 톰캣이 제공한다

 

결론으로

간단하게는 Springboot 2.7.8 이상 혹은 3.0.3 이상 사용하고

아무런 설정도 하지 않으면 MultipartAutoConfiguration에 의해 StandardServletMultipartResolver 자동 설정된다

부트 버전을 쉽게 바꿀 수 없다면 차선책으로 embedded-container 버전을 바꿔 올려준다

embedded-container을 사용하지 않고 버전 업까지 자유롭지 않다면 매우 귀찮아진다

CommonsMultipartResolver에 추가된 기능에 확장 포인트가 없어서 직접 커스텀으로 만들어야 한다

이 방법은 당장에 문제를 해결할 순 있어도 작성하는 순간 레거시가 돼버리는 코드라 추천하지 않는다

프레임워크, 컨테이너 버전 업하는 순간 바로 삭제되어야 하는 코드인데 누가 한 명 총대 메고 나서지 않는다면

구전으로만 내려오는 전설의 코드가 되어버릴 수 있다

 

이제 문제의 원인을 찾기 위해 내부를 살펴보자

사용자가 MultipartResolver 타입으로 빈을 만들어두지 않았다면 부트가 알아서 만들어준다

multipartConfigElement 빈은 Container가 Multipart를 다룰 때 사용하므로 주의해서 설정하자

 

spring.servlet.multipart prefix로 다음 속성들을 제어할 수 있다

MultipartProperties

 

MultipartResolver 인터페이스의 핵심 메서드다

HTTP request를 파일과 인자로 쪼개고 MultipartHttpServletRequest 객체로 반환해 준다

MultipartResolver#resolveMultipart

 

우리가 봐야 할 StandardServletMultipartResolver의 구현은 다음과 같다

로직이 있어야 할 자리 같은데 StandardMultipartHttpServletRequest의 생성자를 호출하고 있다

StandardMultipartHttpServletRequest의 클래스 상단 자바독을 읽어보면 그 이유를 알 수 있다

프레임워크 자체에는 커스텀 로직이 없고 감싸기 위한 Adapter로 제공하기 때문이란다

StandardServletMultipartResolver#resolveMultipart

 

내부를 따라가 보면 lazyParsing 설정이 되어있지 않으면 파싱한다, lazyParsing은 default false다

StandardMultipartHttpServletRequest

 

HttpServletRequest에서 파싱하여 Part 컬렉션을 가져오는 부분이며 tomcat을 사용 중이라

org.apache.catalina.connector.Request의 getParts() 메서드가 호출된다

StandardMultipartHttpServletRequest#parseRequest

 

private 메서드 parseParts()를 호출해 내부적으로 처리한다

Request#getParts

 

2714 라인에서 getWrapper().getMultipartConfigElemet() 가 앞서 말한 MultipartAutoConfiguration에서

MultipartProperties로 주의해서 설정해야 한다는 부분을 가져와 쓰는 곳이다

2731 라인에서는 tomcat이 기본적으로 설정하는 maxParameterCount를 가져오는 부분이다

Request#parseParts-1

 

같은 메서드 내의 2780 라인부터 CVE-2023-24998 문제점에서 발생했던 문제를 해결하는 부분이다

별다른 설정이 없다면 maxParameterCount는 tomcat의 기본 제약 사항을 따라 10,000이다

특이한 점은 파일 개수를 별도로 설정할 수 없고 파일 + 인자의 개수를 합쳐 설정해야 한다는 것이다

Request#parseParts-2

 

여기까지 tomcat 종속적인 구현으로 이루어진 StandardServletMultipartResolver를 살펴보았다

spring-web의 다양한 컴포넌트들의 존재만 어렴풋이 알고 있다가

최근 들어 이슈 처리나 호기심으로 세세한 부분까지 들어가 보는데 이것도 꽤 재밌는 것 같다

아무래도 좁은 부분으로 들어가는 것이다 보니 전체적인 스프링 설계 철학을 깨우치는 데는 오래 걸리겠지만

확장 포인트를 어떻게 만들어야 라이브러리를 공개해도 사용자의 불편함이 없을지 고민해 볼 수 있는 좋은 기회를 얻었다

댓글
링크
글 보관함
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Total
Today
Yesterday