본문 바로가기
시큐어코딩/JAVA

[시큐어코딩]Java에서 XML 외부 개체(XXE) 취약점 안전하게 막는 방법

by ICT리더 리치 2025. 4. 16.

XML 파싱이 보안 취약점이라고요? 작은 설정 하나로 외부 시스템 파일이 노출될 수 있습니다. Java에서도 XXE 취약점은 절대 예외가 아닙니다.

안녕하세요, 시큐어코딩을 기반으로 Java 및 Spring Boot 보안에 다들 관심이 많으실 껄로 아는데요. 오늘은 많은 분들이 의외로 놓치고 있는 보안 이슈, XML 외부 개체(XXE: XML External Entity) 취약점에 대해 살펴보겠습니다. 특히 Java 11 이상의 환경에서는 다양한 XML 파서(DOM, SAX, JAXB 등)가 존재하고, 개발자 설정 실수 하나가 민감한 내부 파일을 외부로 노출시킬 수 있습니다.

이 포스팅에서는 XXE가 무엇이고, 어떤 방식으로 공격이 발생하는지, 그리고 Java 환경에서 안전하게 막는 방법까지 실무 중심으로 정리해드립니다.

1. XML 외부 개체(XXE)란 무엇인가?

XML 외부 개체(XML External Entity, XXE) 취약점은 XML 파서가 외부 엔티티를 허용할 때 발생하는 보안 문제입니다. 공격자는 XML 요청 내에 외부 시스템 파일을 참조하는 DTD(문서 유형 정의)를 삽입하여, 서버의 파일 시스템 정보, 네트워크 자원, 환경 변수 등을 노출시킬 수 있습니다.

이 취약점은 주로 아래와 같은 형태로 입력값에 포함됩니다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ELEMENT foo ANY >
  <!ENTITY xxe SYSTEM "file:///etc/passwd" >
]>
<foo>&xxe;</foo>

강의실에서 시큐어코딩을 설명하는 20대 한국인 자바 개발 강사
강의실에서 시큐어코딩을 설명하는 자바 개발 강사

2. 취약한 XML 파싱 코드 예시 (Java 11 기준)

다음은 Java 11에서 DocumentBuilderFactory를 사용해 XML을 파싱하는 전형적인 코드입니다. 외부 개체를 비활성화하지 않으면, 해당 코드에서 XXE 공격이 가능해집니다.


import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import java.io.StringReader;

String xml = "<!DOCTYPE foo [<!ENTITY xxe SYSTEM 'file:///etc/passwd'>]><foo>&xxe;</foo>";
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Document doc = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));

⚠️ 외부 엔티티 처리 여부를 명시하지 않으면, 기본값에 따라 취약한 동작을 유발할 수 있습니다. 특히 테스트 환경에서는 무의식적으로 보안이 해제된 설정이 포함되기 쉽습니다.

3. 공격 시나리오와 정보 유출 흐름

XXE 취약점이 존재하는 경우, 공격자는 XML 요청 안에 외부 엔티티를 정의하고 이를 서버로 전송함으로써 내부 파일의 내용이나 네트워크 자원에 접근할 수 있습니다. 아래는 공격 흐름 예시입니다:

  1. 1️⃣ 클라이언트가 외부 엔티티를 포함한 XML 요청 전송
  2. 2️⃣ 서버가 인증 없이 XML을 파싱 → 외부 파일 참조 허용
  3. 3️⃣ <!ENTITY>가 실제 파일(예: /etc/passwd)을 읽음
  4. 4️⃣ 파싱 결과가 응답 또는 로그를 통해 공격자에게 전달

🔥 실제로는 내부망의 API 호출, AWS 키, Docker 환경변수 등까지 유출되는 사례도 존재합니다.

4. 안전한 시큐어코딩 설정 및 검증법

Java에서는 XML 파서를 사용할 때 외부 엔티티 처리를 명시적으로 비활성화해야 합니다. 아래는 DocumentBuilderFactory를 사용할 때의 안전한 설정 예시입니다:


import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import java.io.StringReader;

String xml = "..."; // 외부에서 입력된 XML 문자열
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setExpandEntityReferences(false); // 엔티티 확장 방지

Document doc = factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));

✅ 이와 같은 설정은 대부분의 XML 파서에서 지원되며, 반드시 모든 XML 입력처에 적용해야 합니다.

5. 자주 묻는 질문 (FAQ)

Q XML만 쓰는데 왜 보안 위험이 생기나요?

XML은 단순한 데이터 포맷처럼 보이지만, 내부에 DTD(문서 유형 정의)를 삽입할 수 있으며 이를 통해 외부 파일이나 네트워크 자원에 접근할 수 있습니다. 그래서 검증되지 않은 XML을 그대로 파싱하는 것은 매우 위험합니다.

Q Spring Boot에서도 XXE 취약점이 발생할 수 있나요?

네. Spring 자체는 기본적으로 XML 파서를 내장하고 있으며, REST 또는 SOAP API 처리 시 XML 입력을 받을 수 있습니다. 이때 Jackson, JAXB, JAXP 등이 연동될 수 있고, 설정이 잘못되면 XXE 취약점이 발생할 수 있습니다.

Q JAXB 사용 시에도 XXE 취약점이 있나요?

JAXB 역시 내부적으로 DocumentBuilderFactory 등을 사용할 수 있으며, 이때 별도의 보안 설정이 없다면 XXE에 취약해질 수 있습니다. 따라서 마샬링/언마샬링 전에 파서 설정을 명시하거나 검증된 입력만 처리해야 합니다.

Q XML 대신 JSON을 사용하면 안전한가요?

상대적으로 JSON은 DTD나 외부 엔티티 개념이 없기 때문에 XXE와 같은 구조적 취약점에서는 더 안전합니다. 하지만 JSON에도 다른 형태의 보안 문제가 존재할 수 있으므로, 입력 검증은 항상 필요합니다.

XML 외부 개체 참조(XXE) 취약점은 작은 실수로도 큰 피해를 초래할 수 있는 고위험 보안 이슈입니다. 특히 Java와 Spring 환경에서 XML을 다룰 때는 반드시 보안 옵션을 명시적으로 설정하고, 입력값에 대한 검증도 병행해야 합니다. 이번 가이드를 통해 XXE에 대한 구조적 이해와 시큐어코딩 방법을 체득하시길 바랍니다.

반응형