보안정보
전문화된 보안 관련 자료, 보안 트렌드를 엿볼 수 있는
차세대 통합보안관리 기업 이글루코퍼레이션 보안정보입니다.
Thymeleaf 환경의 SSTI 공격사례 및 대응 방안: Template fragment와 CVE-2023-38286기반의 SSTI
2024.07.29
445
01. SSTI와 Thymeleaf 개요
1) SSTI 취약점과 Thymeleaf 개요
타깃 시스템의 애플리케이션에 악성 템플릿 코드를 주입하여 서버에서 템플릿 엔진이 실행되면서 공격자가 삽입한 임의의 공격 코드가 실행되는 공격을 SSTI(Server Side Template Injection)라 한다. SSTI는 다른 SQL Injection, Command Injection, LDAP Injection 등과 같이 OWASP(Open Web Application Security Project) Top 10 2021의 ‘A03 : Injection’에 포함되는 Injection 공격의 일종이다.
SSTI가 악용하는 서버 사이드 템플릿 엔진은 웹 개발에서 UI 렌더링을 위해 반드시 필요한 기술로 서버에서 동적 콘텐츠를 생성하기 위해 모델 데이터를 템플릿과 결합하여 HTML 등의 결과물을 출력하는 역할을 한다. 이글루코퍼레이션 2022년 9월 호에서 『웹 템플릿 엔진 기반의 SSTI 취약점 분석』을 통해 SSTI 취약점을 분석한 바 있어서 이번 문서에서는 서버 사이드 템플릿 엔진 중 스프링 부트(Spring Boot)를 비롯한 프레임워크에서 JAVA 기반의 뷰 템플릿 엔진(View Template Engine)인 타임리프(Thymeleaf) 환경에서 발생하는 SSTI에 대해서 보다 자세히 살펴보고자 한다.
Thymeleaf는 Spring 공식 문서에서도 주요 템플릿 엔진으로 권장될 정도로 JAVA 기반 웹 애플리케이션 개발에 널리 사용되는 서버 사이드 템플릿 엔진이다. Spring MVC와 자연스럽게 통합되어 컨트롤러에서 전달된 모델 데이터를 템플릿에서 사용하기 쉽다는 장점뿐만 아니라, Spring Boot에서 별도의 설정 없이 바로 사용할 수 있는 기본 템플릿 엔진이기 때문에 Spring Boot에서 많이 사용되고 있다.
Thymeleaf의 가장 큰 특징은 일반 HTML 안에 동적 데이터를 삽입할 수 있는 태그와 속성을 제공한다는 점이다. 이를 통해 개발자는 서버 사이드에서 생성된 데이터를 HTML 문서에 효과적으로 통합할 수 있으며, 이 과정에서 SpringEL과 같은 표현 언어를 사용하여 데이터를 동적으로 처리할 수 있다. 웹 페이지 개발 시에 웹 페이지 상단이나 하단, 카테고리 영역 등 중복되는 영역을 반복해서 사용하는 것은 유지보수나 가독성 측면에서 비효율적이지만, Thymeleaf에서는 템플릿 프래그먼트(fragment)를 제공하기 때문에 코드 중복을 최소화 할 수 있다.
따라서 본 문서에서는 Thymeleaf의 특징인 코드 재사용성과 동적 콘텐츠 생성의 편의성을 악용하여 SSTI를 발현시킬 수 있는 방법에 대해 살펴보고 이에 따른 대응 방안을 모색해 보고자 한다.
2) Thymeleaf 특성 및 기본 문법
Spring 환경에서의 Thymeleaf 템플릿 엔진의 특성을 요약하자면 아래와 같다.
- ① Thymeleaf의 템플릿 fragment 기능을 활용한 코드 재사용성
- ② Thymeleaf의 템플릿 동적 콘텐츠 생성의 용이성
Thymeleaf의 특성은 웹 어플리케이션 개발에 있어서는 유용할 수 있으나 반대로 SSTI 공격에 악용할 수 있는 영역을 제공하게 된다. 본격적으로 Thymeleaf 템플릿 엔진을 이용한 SSTI 공격을 살펴보기에 앞서 SSTI에서 악용하는 Thymeleaf의 기본 표현방식과 태그 속성에 대해서 [표 1]을 통해 간략하게 알아보고자 한다.
02. Thymeleaf 환경의 SSTI 취약점 분석
2. 1 Template fragment을 이용한 SSTI 분석
Thymeleaf에서 템플릿 내에 코드 중복성을 최소화하기 위한 방안인 fragment을 악용하면 SSTI 공격을 할 수 있다. 예제를 살펴보면서 Thymeleaf의 fragment을 이용해 SSTI를 유발하는 방법에 대해서 알아보고자 한다.
1) Template fragment를 활용한 SSTI 공격 개요
[그림 1]은 Spring 환경에서의 메인 컨트롤러 구조로 컨트롤러에서 모델에 데이터를 담아 뷰로 전달한다.
fragment 메소드는 "/fragment" 경로에 대한 GET 요청을 처리하며, RequestParam인 section값을 받아 Line10과 같이 "welcome::"을 통해 [그림 2]에 정의한 welcome.html 파일이 호출되면서 section 값을 반환하게 된다. 여기서 사용자가 입력한 section 값을 받아 처리하기 때문에 section 값에 따라 동적으로 뷰의 이름과 fragment가 결정된다.
[그림 2]는 Thymeleaf 뷰 템플릿으로 전달받은 데이터를 활용해 동적 데이터를 화면에 렌더링한다. Line 2에서 Thymeleaf 네임스페이스를 선언하면 th:로 시작하는 속성들을 Thymeleaf 템플릿 지시어로 인식하고 처리할 수 있게 된다.
Line 3에서 th:fragment="header" 속성은 div 태그가 "header"라는 이름의 템플릿 fragment를 정의하고, Line 6은 또 다른 div 태그로 "main"이라는 이름의 또 다른 템플릿 fragment를 정의한다. 이렇게 정의된 "header"와 "main"이라는 템플릿 fragment를 다른 템플릿에서 호출하면 [그림 2]의 welcome.html에서 정의한 템플릿을 다른 템플릿에서 fragment를 참조하여 재사용할 수 있다.
[그림 3]과 같이 section의 파라미터 값을 header로 호출하면 컨트롤러에서 반환되는 문자열은 "welcome::header"로 [그림 1]에서 welcome.html을 호출되면서 welcome.html의 Line 4인 "Spring Web Thymeleaf Example – SSTI"문자열이 출력된다. 이는 뷰를 통해 welcome 템플릿 내 "header" fragment를 렌더링하여 브라우저에 출력하는 것을 의미한다.
2) Template fragment를 이용한 공격 시연
[그림 5]와 같이 디버깅을 통해 section 파라미터 값으로 SpringEL 공격 구문이 삽입되어 계산기가 실행되는 것을 확인할 수 있다.
2. 2 CVE-2023-38286을 이용한 SSTI 분석
Thymeleaf은 코드 재사용성과 동적 콘텐츠 생성의 편의성을 제공하기 때문에 이메일이나 보고서와 같이 공통된 포맷을 제공해야 하는 업무에 효율적으로 사용이 가능하다. TemplateEngine 클래스의 process 메소드가 동적 콘텐츠를 처리하는 영역으로 템플릿을 렌더링해 최종 결과물을 만드는 핵심적인 역할을 수행한다.
우리가 살펴볼 두 번째 공격 방식이 바로 이러한 동적 콘텐츠 생성의 핵심인 TemplateEngine 클래스를 악용한 SSTI 공격이다. 앞서 살펴본 Template fragment과 달리 이번 공격은 CVE-2023-38286 취약점을 통해서 설명해 보고자 한다.
1) CVE-2023-38286 취약점 개요
CVE-2023-38286은 CVSS 3.1기준 7.5 Score로 Spring Boot Admin의 템플릿 엔진 Thymeleaf에서 SSTI를 이용한 RCE(Remote Code Execution)공격이 가능한 고위험 취약점이다. Spring Boot Admin이 제공하는 기능 중 MailNotifier(메일 알림 기능)가 활성화되었을 때, 기존 템플릿(status-changed.html) 대신 악성 템플릿(poc3.html)이 동적으로 실행하면서 공격이 발생한다. TemplateEngine 클래스의 process 메소드가 SpringEL로 작성된 악성 템플릿을 렌더링하여 Thymeleaf의 샌드박스를 탈출하는 과정에서 Spring Boot Admin 서버 측에서 RCE가 발현된다.
CVE-2023-38286 취약점이 발현되기 위해서는 아래의 전제 조건이 필요하다.
- ① Spring Boot Admin 웹 애플리케이션에 HTML 파일 업로드가 가능해야 한다.
- ② Spring Boot Admin의 /actuator/env 엔드포인트에 POST 요청으로 환경변수 설정이 가능해야 한다. 이 엔드포인트는 애플리케이션의 Environment를 조작하는 데 사용된다. 이를 위해서, POST 요청을 명시적으로 허용해 주어야 하는데 해당 설정은 "management.endpoint.env.post.enabled=true"이다.
- ③ Spring Boot Admin <= 3.1.0 RELEASE, Tymeleaf <= 3.1.1 RELEASE에서 취약점이 발현된다.CVE-2023-38286 취약점은 [표 4-2]의 환경에서 PoC를 진행하였다.
CVE-2023-38286 취약점은 [표 2]의 환경에서 PoC를 진행하였다.
2) CVE-2023-38286 공격 시나리오
CVE-2023-38286을 이용해 공격하기 위해서는 [그림 7]과 같이 Spring Boot Admin 웹 애플리케이션에 파일 업로드가 가능한 상태에서 Thymeleaf로 작성된 악성 템플릿(poc3.html)이 이미 존재한다는 전제로 공격이 진행된다. 따라서 악성 템플릿 업로드가 불가능 환경에서는 공격 시나리오가 정상적으로 수행되지 않는다.
- ① Spring Boot Admin의 MailNotifier(메일 알림 기능)을 활성화시킨다.
- ② Spring Boot Admin의 메일 템플릿을 악성 템플릿(poc3.html)으로 변경한다.
- ③ Spring Boot Admin에 애플리케이션 등록을 요청하여, 새로운 인스턴스를 생성한다.
- ④ 새로운 인스턴스가 생성됨과 동시에, 악성 템플릿(poc3.html)이 동적 실행되어 Thymeleaf 샌드박스 탈출 및 Spring Boot Admin 서버에 RCE를 발현시킨다.
먼저 [그림 8]과 같이 Thymeleaf로 작성된 악성 템플릿(poc3.html)의 코드를 분석해 보고자 한다.
[그림 8]은 Thymeleaf 샌드박스를 탈출하는 과정에서 RCE 공격을 유발하는 공격 코드로 org.springframework.util 패키지를 활용한다. org.springframework.util 패키지에는 SpringEL 표현식을 실행시킬 수 있는 클래스가 포함되는데, 해당 패키지의 하위 클래스인 ReflectionUtils는 동적 메서드 실행이나 필드 접근 등의 기능을 제공한다.
[그림 8]의 Line 9 getRuntimeMethod 변수는 ReflectionUtils와 ClassUtils 클래스를 활용해 java.lang.Runtime의 getRuntime 메소드를 가져온다. getRuntime은 메소드를 동적으로 찾고 호출할 수 있게 해주는 메소드이다.
Line 16에서 선언된 exeMethod 변수는 ReflectionUtils와 ClassUtils 클래스를 활용하여 java.lang.Runtime의 exec 메소드를 가져온다. exec 메소드는 시스템 명령어를 실행할 수 있기 때문에 외부에서 안전하지 않은 입력값을 삽입하게 되면 공격에 악용될 소지가 있다. Line 19의 param2에서는 앞서 선언한 변수 runtimeObj와 exeMethod를 실행한다. runtimeObj는 실행할 대상 객체로 "calc"는 exeMethod에 전달되어 최종적으로 Line 21에서 동적으로 실행된 param2에 의해 계산기가 실행되게 된다.
3) CVE-2023-38286 시연 및 분석
Spring Boot Admin을 통해 Thymeleaf로 작성된 악성 템플릿(poc3.html)을 실행시켜 공격이 실행되는 과정을 따라가보도록 하자. [그림 7] CVE-2023-38286 공격 시나리오의 "① MailNotifier 기능 활성화"에 따라 "spring.boot.admin.notify.mail.enabled"의 값을 "true"로 변경하는 요청을 "/actuator/env"로 보내 메일 알림 기능(MailNotifier)을 활성화한다.
이후 "② 메일 템플릿 변경"에 따라 "spring.boot.admin.notify.mail.template"의 값을 악성 템플릿(poc3.html)이 업로드 된 경로("http://SpringBootAdmin주소(공격대상)/poc3.html")로 변경하여 메일 템플릿을 악성 템플릿(poc3.html)으로 변경 요청한다.
[그림 10]의 POST 요청으로 인한 MailNotifier 클래스의 내부 동작 과정은 [그림 11]과 같다.
[그림 11]의 Line 104를 보면 사용자 요청에 대한 별도의 검증 로직이 존재하지 않고, 기존의 메일 알림 템플릿(status-changed.html)이 악성 템플릿(poc3.html)으로 변경되는 것을 확인할 수 있다.
변경된 템플릿 적용을 위해 "/actuator/refresh" 엔드포인트에 빈 값을 전송하여, MailNotifier의 template 변수가 악성 템플릿("http://SpringBootAdmin주소(공격대상)/poc3.html")으로 적용되도록 새로고침 요청 패킷을 보낸다.
"③ 애플리케이션 등록 요청" 단계에서는 악성 템플릿(poc3.html)의 Thymeleaf 코드를 동적으로 실행시키기 위해 메일 알림 인스턴스 생성 요청을 보낸다. 새로운 메일 알림이 생성되는 즉시 Thymeleaf 템플릿(poc3.html) 코드가 실행되어 공격 시나리오의 "④ Thymeleaf 악성 템플릿 동적 실행으로 RCE 발현"된다.
"③ 애플리케이션 등록 요청"에서 "④ Thymeleaf 악성 템플릿 동적 실행으로 RCE 발현"까지 과정을 소스코드를 통해 상세하게 분석해보고자 한다. 메일 알림이 생성됨과 동시에 Spring Boot Admin에서 SSTI 공격 과정을 디버깅한 내용으로 [그림 13]의 POST 요청으로 인한 Spring Boot Admin의 MailNotifier 클래스 동작 과정은 [그림 14]과 같다.
Line 67에서 getBody 메소드의 매개변수(ctx)로 생성된 메일 알림 인스턴스를 받아오면, 디버깅 창에서 process 메소드로 전달하는 매개변수 this.template이 poc3.html 파일임을 확인할 수 있다.
[그림 15]는 [그림 14] Line 68의 process 메소드로 동일한 클래스 내 매개변수가 3개인 process 메소드를 호출한다.
[그림 15]의 Line 363의 process 메소드 내부로 매개변수 templateSpec의 값으로 악성 템플릿이 삽입된다. templateSpec는 템플릿의 명세를 담고 있는 객체로 템플릿 이름, 선택자, 모드, 해석 속성 등을 포함한다. Line 390 tmeplateManger.parseAndProcess 메소드를 통해 동적 페이지 뷰가 생성되면서 RCE가 발현되기 때문에 마찬가지로 소스코드를 통해 상세한 과정을 살펴보고자 한다.
tmeplateManger.parseAndProcess 메소드 내부이다. 해당 메소드는 템플릿 처리 사이클 전체(해석, 파싱, 처리)를 수행한다. Line 220을 보면, templateSpec으로 악성 템플릿의 내용을 template 변수에 할당한다. Line 228부터 캐시된 템플릿 데이터를 기반으로 엔진 Context를 준비하고 Context에 따라 핸들러 체인이 생성되면 템플릿 엔진은 핸들러 체인을 따라 템플릿을 처리한다. 마지막으로 Line 231에서 process 메소드에서 악성 템플릿 코드가 동적으로 실행되어 [그림 13]처럼 Windows 계산기가 실행된다.
PoC 분석을 통해 SSTI 공격 원인은 공격자가 업로드한 악성 템플릿이 정상 템플릿으로 지정되고 이를 동적으로 실행하기 때문임을 알 수 있다. 그렇다면 MailNotifier 클래스가 [그림 11]에서와 같이 어떻게 공격자의 악성 템플릿 경로("http://SpringBootAdmin주소(공격대상)/poc3.html")를 참조할 수 있는지 알아보고자 한다.
[그림 18]은 Spring Boot Admin에서 MailNotifier의 TemplateEngine Resolver를 생성하는 코드이다. 템플릿 엔진 리졸버는 Spring MVC 애플리케이션에서 Thymeleaf 템플릿을 찾고 처리하는 역할을 한다.
Line 182에서 객체로 생성된 SpringResourceTemplateResolver는 setApplicationContext로 리소스를 로드할 때 별도의 검증 과정 없이 사용자가 입력한 템플릿 이름 그대로를 사용한다. 이를 악용하면 템플릿 이름으로 "poc3.html"를 전달하여 공격자가 악의적인 템플릿을 주입할 수 있게 된다. 그 결과, TemplateResolver는 악성 템플릿(poc3.html)을 찾아 로드하게 된다.
[그림 19]는 [그림 18]의 Line 183 setApplicationContext 메소드 내부로 applicationContext는 애플리케이션 전역의 클래스 패스나 파일 시스템과 같은 리소스 위치에서 템플릿을 로드하기 때문에 공격자가 애플리케이션에 업로드한 경로("http://SpringBootAdmin주소(공격대상)/poc3.html")의 파일도 참조할 수 있다.
[그림 14]와 같이 [그림 20]의 getSubject 메소드도 악성 템플릿(poc3.html)을 렌더링 할 수 있다.
[그림 18]에서 TemplateResolver가 정상 템플릿으로 주입한 "poc3.html"을 process 메소드가 템플릿 처리를 위해 SpringEL을 동적으로 실행하면서 RCE가 발현된다.
03.대응 방안
지금까지 Thymeleaf환경에서 SSTI을 수행하기 위한 방안인 △ Template fragment 악용과 △ Spring Framework의 Thymeleaf에서 CVE-2023-38286을 이용한 SSTI 공격방식에 대해서 살펴보았다. 분석해본 2개의 공격 방식 모두 원격코드 실행이 가능한 고위험 공격 방식이기 때문에 대응 방안이 중요하다. 이를 위해 △ Thymeleaf 및 Spring 등 최신 버전 업데이트, △ 소스코드 안정성 검사, △ Thymeleaf 템플릿 엔진의 리졸버 권한 최소화의 대응 방안을 제시하고자 한다.
1) Thymeleaf 및 Spring 등 최신 버전 업데이트
Thymeleaf 템플릿 엔진과 Thymeleaf를 내장하는 Spring 등의 소프트웨어를 최신 버전을 사용하는 것이 중요하다. CVE-2023-38286 취약점은 Spring Boot Admin는 3.1.1 이상이거나 Thymeleaf 3.1.2 이상인 경우 취약점을 완화하였기 때문에 SSTI를 막을 수 있다.
Thymeleaf 3.1.2 부터는 [그림 21]과 같이 org.springframework.util 패키지를 어떠한 경우에도 사용하지 못하도록 "BLOCKED_TYPE_REFERENCE_PACKAGE_NAME_PREFIXES"차단 목록에 추가하였다. org.springframework.util은 Thymeleaf 템플릿에 포함된 SpringEL 표현식에서 정적으로 액세스되는 클래스가 포함되어 있기 때문에 해당 패키지를 사용하지 않도록 차단하는 것을 권고한다.
2) 소스코드 안정성 검사
Thymeleaf 템플릿 엔진을 사용할 때, 소스코드 단에서 SSTI에 대응 가능한 가장 안전한 코드는 사용자 입력으로 템플릿을 생성하지 않는 것이다. 즉, 컨트롤러에서 반환하는 return 값에 사용자 입력값을 포함시키지 않는 것이다. 사용자 입력값을 포함하여 반환해야 한다면 @ResponseBody 어노테이션을 사용하여 Spring 프레임워크가 사용자 입력값을 뷰 이름으로 해석하지 않고 단순 문자열로 해석하는 방법이 있다. 또한 뷰 이름 반환 시, 뷰 이름 앞에 "redirect:"를 추가하여 [그림 6]의 ThymeleafView 클래스 대신 RedirectView 클래스를 이용하여 리다이렉트를 처리할 수 있다.
3) Thymeleaf 템플릿 엔진의 리졸버 권한 최소화
Spring Boot Admin인 경우에는 Thymeleaf 템플릿 엔진 리졸버를 더 낮은 권한의 리졸버로 변경해야 한다. [그림 23]에서 설명하고 있는 내용은 CVE-2023-38286 취약점의 보안 패치 전후를 비교한 내용으로 기존 SpringResourceTemplateResolver([그림 23]의 좌측 Line 51, Line 182)에서 ClassLoaderTemplateResolver([그림 23]의 우측 Line 52, Line 182)로 대체하였다. setApplicationContext 메소드([그림 23]의 좌측 Line 183)는 애플리케이션 패스 전역에서 로드할 템플릿을 찾지만, 변경된 ClassLoaderTemplateResolver는 지정된 클래스 패스를 기반으로 템플릿을 로딩하므로 외부 입력 템플릿에 의해 영향받을 가능성이 작다.
[그림 24]의 좌측 Line 188의 addTemplateResolver 메소드의 경우 기존에 등록된 템플릿 리졸버에 새로운 템플릿 리졸버를 추가하는 방식이다. [그림 24]의 우측 Line 187의 setTemplateResolver는 보안 패치로 변경된 리졸버이다. setTemplateResolver는 하나의 템플릿 리졸버만 지정하여 사용하므로 지정된 템플릿 리졸버의 보안 설정에만 집중할 수 있어 보안상으로 안전하고 관리가 용이하다.
04. 결론
지금까지 Thymeleaf의 코드 재사용성 및 동적 콘텐츠 생성의 편의성이라는 특성을 악용한 SSTI 공격방식과 대응 방안에 대해 살펴보았다. 본 문서에서는 Spring 프레임워크의 Thymeleaf 템플릿 엔진의 SSTI 공격에 대해 다뤘지만, 이외에도 Freemarker와 Velocity 등 다양한 템플릿 엔진을 통해서 SSTI가 발생할 수 있다. PowerSwigger의 James Kettle이 발표한 ‘Server-Side Template Injection’에서도 이와 같이 다양한 템플릿 엔진에서 발생하는 취약점과 대응 방안의 필요성에 대해서 언급하기도 했다.
SSTI는 템플릿 엔진에서 안전하지 않은 템플릿을 해석하면서 발생하는 취약점으로 사용자 입력값에 적절하지 않은 템플릿이 삽입되면 공격에 악용될 수 있게 된다. 따라서 SSTI를 대응하는 가장 간단한 방법은 Mustache와 같은 ‘logic-less’ 템플릿 엔진을 사용하여 단순하고 안전하게 템플릿을 설계하는 것이다. 하지만 ‘logic-less’라는 특성으로 인해 템플릿에 대한 자유도가 다소 낮다는 문제가 있기 때문에 다른 Injection 공격 유형의 대응 방안과 유사하게 Sanitization, Sandboxing, Input Validation을 다중 적용하는 방법이 있다.
이처럼 각 템플릿 엔진마다 고유한 특성과 위험 요소가 존재하므로 템플릿 엔진 선택 시에는 보안을 고려하여 템플릿 엔진으로 인한 취약점의 이해와 관리 방안이 필요하다. 웹 애플리케이션의 복잡성이 증가하는 한편 공격 기법 역시 고도화됨에 따라 새로운 템플릿 엔진이나 관리되지 않은 기존의 취약점들은 공격벡터로 작용할 수 있다. 따라서 향후 다양한 템플릿 엔진의 SSTI 취약점 연구를 통해서 안전한 소프트웨어 환경에 기여할 수 있기를 기대해 본다.
05. 참고 자료
1.A03:2021 – Injection, OWASP : https://owasp.org/Top10/A03_2021-Injection
2.Exploiting SSTI in Thymeleaf, Acunetix : https://www.acunetix.com/blog/web-security-zone/exploiting-ssti-in-thymeleaf
3.Server-Side Template Injection, James Kettle : https://portswigger.net/research/server-side-template-injection
4.How to prevent Server-Side Template Injection, Portswigger : https://portswigger.net/web-security/server-side-template-injection#how-to-prevent-server-side-template-injection-vulnerabilities
5.Spring View Manipulation Vulnerability, veracode-research : https://github.com/veracode-research/spring-view-manipulation
6.CVE-2023-38286 : https://github.com/p1n93r/SpringBootAdmin-thymeleaf-SSTI
7.CVE-2023-38286 Details : https://github.com/advisories/GHSA-7gj7-224w-vpr3
8.Spring Boot Admin : https://github.com/codecentric/spring-boot-admin
9.Thymeleaf : https://github.com/thymeleaf/thymeleaf