1. 개요
2017년 10월에 공개된 CVE-2017-10271은 Oracle WebLogic Server에 영향을 미치는 심각한 원격 코드 실행(Remote Code Execution, RCE) 취약점이다. 해당 취약점은 인증되지 않은 원격 공격자가 특수하게 조작된 XML 데이터를 전송하여 서버를 완전히 장악할 수 있게 한다. CVSS 3.0 점수가 7.5(HIGH)에 달할 정도로 심각도가 매우 높으며, 공격에 특별한 권한이 필요 없어 공격의 장벽이 매우 낮다.
이 취약점은 공개된 직후부터 전 세계적으로 암호화폐 채굴 악성코드 배포, 봇넷 확장, 랜섬웨어 공격 등 다양한 사이버 범죄에 광범위하게 악용되었으며, 본 보고서는 CVE-2017-10271의 기술적 근본 원인을 심층적으로 분석하고, 특히 Java의 XMLDecoder 클래스가 어떻게 공격의 핵심 도구로 사용되었는지 상세히 설명하며, 이에 대한 방어 및 완화 전략을 제시한다.
2. 취약점 사전 지식
2.1. Java 언어 특성으로 인해 발생하는 취약점
먼저, 공격자가 만든 악성 XML 파일과 XML 파일을 읽는 간단한 Java 애플리케이션을 준비한다.
<?xml version="1.0" encoding="UTF-8" ?>
<java>
<!-- 1. ProcessBuilder 객체를 생성하라고 지시 -->
<object class="java.lang.ProcessBuilder">
<!-- 2. 실행할 명령어로 "calc" (계산기)를 전달 -->
<array class="java.lang.String" length="1">
<void index="0"><string>calc</string></void>
</array>
<!-- 3. 위에서 만든 프로세스를 시작(start)하라고 지시 -->
<void method="start"></void>
</object>
</java>
Java
복사
[코드 1] poc.xml (악성 XML 파일)
poc.xml은 단순한 데이터를 담고 있는 것처럼 보이지만 Java 프로그램에 특정 동작을 지시하는 명령어이다.
package org.example;
import java.beans.XMLDecoder;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class Main {
public static void main(String[] args) {
// "poc.xml" 파일 경로를 지정
File file = new File("C:\\path\\to\\your\\poc.xml");
XMLDecoder xd = null;
try {
// XMLDecoder로 파일을 읽을 준비
xd = new XMLDecoder(new BufferedInputStream(new FileInputStream(file)));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 이 한 줄에서 모든 일이 일어납니다!
Object o = xd.readObject();
xd.close();
}
}
Java
복사
[코드 2] Main.java (poc.xml을 읽는 평범한 Java 코드)
Main.java은 특별할 것 없이 주어진 경로의 XML 파일을 XMLDecoder를 이용해 읽어 들이는 역할을 한다.
2.1.1. 실행결과
[그림 1] 윈도우 계산기 실행
Main.java 파일을 실행하면 윈도우 계산기가 실행된다.
poc.xml 파일을 읽기만 했는데 xml 내부에 숨겨진 “[코드 1]계산기를 실행하라(calc)” 명령이 그대로 실행된 것을 볼 수 있다.
2.2. XMLDecod 심층 분석
CVE-2017-10271 취약점을 이해하기 위해서는 XMLDecoder가 단순한 데이터 파서가 아니라, 사실상 XML 형식의 스크립트를 실행하는 인터프리터처럼 동작한다는 사실을 인지하는 것이 가장 중요하다.
[그림 2] OpenJDK8 XMLDecoder 내부 파싱 로직 도식화
XMLDecoder 내부를 파싱 부분을 단계 별로 살펴보면 아래와 같다.
•
1단계 - 초기화 및 파싱 시작
◦
악성 XML 데이터 스트림을 인자로 XMLDecoder 객체를 생성한다.
◦
readObject 메소드를 호출하여 파싱 작업을 수행한다.
•
2단계 - 디스패칭 및 태그 처리
◦
DocumentHandler 클래스의 startElement 메서드는 XML 시작 태그를 만날 때마다 호출되며, 중앙 디스패처(dispatcher) 역할을 수행한다.
•
3단계 - 객체/인자 생성
◦
startElement 메소드를 통해 태그(object, array, void)를 확인하고 각각의 핸들러 ObjectElementHandler, ArrayElementHandler, VoidElementHandler가 태그별로 인스턴스 생성한다.
•
4단계 - 최종 실행
//...
} else {
name = (this.method!= null) && (0 < this.method.length())
? this.method
: "new"; // NON-NLS: the constructor marker
}
Expression expression = new Expression(bean, name, args);
return ValueObjectImpl.create(expression.getValue());
}
//...
Java
복사
[코드 3] ObjectElementHandler.java 코드 일부
◦
name = this.method: addAttribute 메서드에서 <void method="start"/>의 method 속성값("start")을 읽어와 this.method 변수에 저장해고, 이 값이 name 변수에 할당된다.
◦
Expression expression = new Expression(bean, name, args);
▪
bean: 타겟 객체인 ProcessBuilder 인스턴스
▪
name: 실행할 메서드 이름인 "start"
▪
args: 메서드에 전달할 인자 배열
//...
public Object getValue() throws Exception {
if (value == unbound) {
setValue(invoke());
}
return value;
}
//...
Java
복사
[코드 5] Expression.java 코드 일부
◦
expression.getValue(): 바로 이 메서드 호출이 실제 리플렉션을 트리거한다.
//...
Object invoke() throws Exception {
AccessControlContext acc = this.acc;
if ((acc == null) && (System.getSecurityManager() != null)) {
throw new SecurityException("AccessControlContext is not set");
}
try {
return AccessController.doPrivileged(
new PrivilegedExceptionAction<Object>() {
public Object run() throws Exception {
return invokeInternal();
}
},
acc
);
}
catch (PrivilegedActionException exception) {
throw exception.getException();
}
}
private Object invokeInternal() throws Exception {
//...
if (m != null) {
try {
if (m instanceof Method) {
return MethodUtil.invoke((Method)m, target, arguments);
//...
Java
복사
[코드 6] Statement.java 코드 일부
◦
Statement 클래스의 invokeInternal 메서드까지 추적하면 MethodUtil.invoke((Method)m, target, arguments) 가 호출하여 전달된 메서드(ProcessBuilder 객체,start 메서드,args 인자)를 실행한다.
각 XML 태그는 Java의 특정 연산에 직접 매핑되며, 이 점이 바로 XMLDecoder를 매우 강력하면서도 위험하게 만드는 핵심 요소이다. 공격에 주로 사용되는 핵심 태그와 그 기능은 다음과 같다.
XML 태그 | 속성 | Java 상응 연산 | 익스플로잇에서의 역할 |
<object> | class="java.lang.Runtime” | new java.lang.Runtime() | 특정 클래스의 인스턴스를 생성한다. 공격에서는 ProcessBuilder나 Runtime과 같은 "가젯(gadget)" 클래스를 인스턴스화하는 데 사용된다. |
<void> | method="exec” | object.exec(...) | 현재 객체의 메서드를 호출한다. 반환 값은 무시됩니다. 객체 생성 후 특정 동작(예: 명령어 실행)을 트리거하는 데 사용된다. |
<string> | - | "calc.exe" | 문자열 리터럴 객체를 생성한다. 메서드 호출 시 인자로 전달될 값을 정의하는 데 사용된다. |
<array> | class="java.lang.String" length="1" | new String[1] | 지정된 타입과 길이의 배열을 생성한다. 명령어와 그 인자들을 배열 형태로 구성하여 메서드에 전달하는 데 사용된다. |
<int> | - | 123 | 정수 리터럴 객체를 생성한다. 메서드 인자나 배열 인덱스 등으로 사용된다. |
이 태그들을 조합하면 정교한 객체 그래프를 생성하고 메서드 호출 체인, 즉 "가젯 체인(gadget chain)"을 구성할 수 있다. 예를 들어, <object>로 ProcessBuilder를 생성하고, <array>와 <string>으로 실행할 명령어를 정의한 뒤, <void method="start">로 프로세스를 시작하는 일련의 과정을 XML만으로 완벽하게 기술할 수 있다.
<?xml version="1.0" encoding="UTF-8" ?>
<java>
<!-- 1. ProcessBuilder 객체를 생성하라고 지시 -->
<object class="java.lang.ProcessBuilder">
<!-- 2. 실행할 명령어로 "calc" (계산기)를 전달 -->
<array class="java.lang.String" length="1">
<void index="0"><string>calc</string></void>
</array>
<!-- 3. 위에서 만든 프로세스를 시작(start)하라고 지시 -->
<void method="start"></void>
</object>
</java>
Java
복사
[코드 7] poc.xml (악성 XML 파일)
이 페이로드를 XMLDecoder.readObject() 메서드로 처리하면 다음과 같은 과정이 순차적으로 서버 내부에서 발생한다.
1.
<object class="java.lang.ProcessBuilder">: XMLDecoder는 java.lang.ProcessBuilder 클래스의 기본 생성자를 호출하여 새 인스턴스를 생성한다.
2.
<array class="java.lang.String" length="1">: ProcessBuilder 생성자의 인자로 전달될 String 타입의 배열을 길이 1로 생성한다.
3.
<void index="0"><string>calc</string></void>: 배열의 0번 인덱스에 calc 문자열을 할당한다.
4.
<void method="start"/>: 생성된 ProcessBuilder 인스턴스의 start() 메서드를 호출한다. 이 시점에서 운영체제는 calc명령을 실행하게 된다.
결론적으로, XMLDecoder에 신뢰할 수 없는 XML 입력을 제공하는 행위는 해커에게 서버에서 임의의 Java 코드를 실행할 권한을 부여하는 것과 동일하다.
이러한 공격의 성공 여부는 결정론적이며 환경에 의존적이다. 메모리 손상 버그와 달리, 페이로드가 문법적으로 정확하고 필요한 가젯 클래스(예: ProcessBuilder)가 애플리케이션의 클래스패스(classpath)에 존재하기만 하면 공격은 100% 성공한다. 이는 공격자에게 매우 신뢰성 높은 공격 벡터를 제공한다. 더 나아가, 이는 애플리케이션의 의존성(dependency)이 곧 잠재적인 공격 표면이 됨을 의미한다. 최소한의 라이브러리를 가진 애플리케이션은 공격에 사용할 가젯이 부족할 수 있지만, 수천 개의 라이브러리를 포함하는 Oracle WebLogic과 같은 대규모 엔터프라이즈 서버는 공격자에게 풍부한 가젯 체인 구성 도구를 제공하는 셈이다. 따라서 의존성 관리와 취약점의 심각도 사이에는 직접적인 인과 관계가 성립한다.
3. 취약점 원인 분석
CVE-2017-10271의 근본 원인은 두 가지 핵심 요소의 위험한 조합에 있다.
1.
신뢰할 수 없는 데이터의 부적절한 처리: Oracle WebLogic Server의 WLS Security 컴포넌트에 포함된 WorkContextXmlInputAdapter 클래스가 외부로부터 수신한 XML 데이터를 아무런 검증 없이 신뢰할 수 있는 것으로 간주하고 처리
2.
위험한 역직렬화 함수의 사용: WorkContextXmlInputAdapter 클래스는 검증되지 않은 이 XML 데이터를 Java의 java.beans.XMLDecoder 클래스로 직접 전달
[그림 3] Oracle WebLogic 취약한 코드
공격자는 WebLogic 서버의 웹 서비스 엔드포인트(예: /wls-wsat/CoordinatorPortType)로 악의적인 명령이 포함된 XML 페이로드를 전송할 수 있다. 서버는 이 요청을 받아 WorkContextXmlInputAdapter를 통해 처리하게 되고, WorkContextServerTube 클래스의 processRequest 메서드에서 전송한 POST 패킷의 SOAP(XML) 데이터를 처리한다.
이 클래스는 수신된 악성 XML을 아무런 검증 없이XMLDecoder에 넘겨진다. XMLDecoder는 이 XML을 단순 데이터가 아닌 실행 가능한 명령어로 해석하여 서버 내에서 임의의 Java 객체를 생성하고 메서드를 호출하게 된다. 이 과정이 최종적으로 원격 코드 실행으로 이어진다.
결론적으로, 근본 원인은 신뢰 경계를 넘어온 외부 데이터를 검증 없이, 설계 자체가 임의 코드 실행 능력을 가진 위험한 XMLDecoder 역직렬화 함수로 처리한 설계상의 결함이다.
4. 검증 결과 및 시연
[동영상 1] CVE-2017-10271 임의 코드 실행 시연 영상
5. 영향 및 패치
5.1. 공격 영향
이 취약점을 성공적으로 악용하면 공격자는 WebLogic 서버 프로세스의 권한으로 원격에서 임의의 코드를 실행할 수 있다. 이는 다음과 같은 심각한 결과로 이어질 수 있다.
•
서버 및 내부 시스템에 대한 완전한 제어권 탈취
•
데이터베이스, 설정 파일, 자격 증명 등 민감 정보 유출
•
암호화폐 채굴 악성코드, 랜섬웨어, 백도어 등 추가 악성코드 설치
•
내부 네트워크의 다른 시스템을 공격하기 위한 거점으로 활용
6. 완화 및 권고 사항
1.
즉시 패치 적용: Oracle이 제공하는 최신 보안 패치를 즉시 적용하는 것이 가장 중요하고 효과적인 조치입니다. Oracle은 패치를 적용하지 않은 고객이 주된 공격 대상임을 지속적으로 강조하고 있다.
2.
네트워크 접근 제어: WebLogic 서버의 관리 포트(기본 7001)는 신뢰할 수 있는 내부 네트워크에서만 접근 가능하도록 방화벽 정책을 강화해야 한다. 인터넷에 직접 노출하는 것은 매우 위험하다.
3.
위험한 API 사용 금지: 개발 관점에서, 신뢰할 수 없는 외부 입력을 처리할 때 java.beans.XMLDecoder와 같이 설계 자체가 위험한 API의 사용을 전면 금지해야 한다.
4.
안전한 대안 사용: XML 데이터를 처리해야 할 경우, JAXB와 같이 엄격한 스키마 검증을 지원하는 라이브러리나, Jackson, Gson과 같은 안전한 데이터 바인딩 라이브러리를 사용해야 한다.
5.
지속적인 모니터링: WebLogic 프로세스에 의해 비정상적인 자식 프로세스(예: cmd.exe, powershell.exe, /bin/sh)가 실행되는지, 비정상적인 CPU 사용률 급증이 있는지 지속적으로 모니터링해야 한다.
7. 결론
CVE-2017-10271은 신뢰할 수 없는 데이터를 검증 없이, 설계상 위험한 기능에 전달했을 때 발생하는 전형적인 보안 이슈이다. WebLogic의 WorkContextXmlInputAdapter가 외부 XML 입력을 XMLDecoder로 직접 처리한 것이 이 취약점의 명백한 근본 원인이다. 이 사건은 단순히 특정 제품의 버그를 넘어, 안전한 소프트웨어 개발의 핵심 원칙, 즉 "모든 외부 입력은 잠재적으로 악의적이다"라는 가정을 잊었을 때 어떤 결과가 초래되는지를 보여주는 중요한 사례 연구이다. 따라서 방어자는 단순히 패치를 적용하는 것을 넘어, 아키텍처 수준에서 안전한 데이터 처리 방식을 채택하고 지속적인 모니터링 체계를 구축하는 심층 방어 전략을 수립해야 한다.
당사의 솔루션인 PurpleHound는 실전 기반 시나리오를 재구성하여 기업이 보유한 보안 장비와 시스템이 실제 공격에 얼마나 효과적으로 대응할 수 있는지를 검증할 수 있도록 지원합니다.
Oracle WebLogic CVE-2017-10271 취약점은 PurpleHound에 이미 액션으로 개발 완료되어 탑재된 취약점으로 이 뿐만 아니라 Oracle WebLogic에서 발생한 또 다른 취약점인 (CVE-2020-14882, CVE-2020-14883, CVE-2023-21839, CVE-2023-21931 등) 탑재되어 있으며 고객사가 신속하고 정확하게 위협을 검증할 수 있도록 전문적인 지원을 제공하고 있습니다.