XML 외부 개체(XML External Entity, XXE) 취약점

XML 외부 엔터티 참조와 파서 보호 설정

설명

XML 외부 개체(XXE) 취약점은 신뢰할 수 없는 XML을 파싱할 때 DTD 또는 외부 엔터티 참조를 허용하면 발생할 수 있습니다. 공격자는 이를 악용해 로컬 파일을 읽거나, 서버가 내부·외부 주소로 요청을 보내게 하거나, 엔터티 확장으로 처리 자원을 소진시킬 수 있습니다.

JAXP에서 XMLConstants.FEATURE_SECURE_PROCESSING을 false로 명시하면 구현체의 보안 한도가 비활성화됩니다. 반대로 JDK에서 이 기능을 API로 true로 명시하면 처리 한도가 활성화되고 외부 접근이 제한되지만, 구현체와 런타임이 다른 환경까지 고려하면 이 기능만으로 완전한 XXE 방어를 보장해서는 안 됩니다. DTD 사용 여부와 외부 DTD·스키마 접근을 별도로 제한해야 합니다.

XMLReaderFactory는 Java 9부터 사용 중단(deprecated)되었으므로 새 코드에서는 SAXParserFactory를 사용합니다. 다만 XMLReaderFactory 사용 자체를 취약점으로 판단해서는 안 되며, 실제 보안 설정과 신뢰 경계를 확인해야 합니다.

잠재적 영향

  • 민감한 파일 유출: 애플리케이션 권한으로 읽을 수 있는 로컬 파일이 노출될 수 있습니다.
  • 서버 사이드 요청 위조(SSRF): 내부 서비스나 공격자 서버로 요청이 전송될 수 있습니다.
  • 서비스 거부(DoS): 과도한 엔터티 확장이나 XML 처리로 CPU와 메모리가 소진될 수 있습니다.

해결 방법

  1. DTD가 필요하지 않으면 거부

    • JDK 25에서는 JAXP 구성 파일 또는 JVM 시스템 속성에 jdk.xml.dtd.support=deny를 설정하는 방식을 우선 사용합니다.
    • 파서별 설정이 필요하면 http://apache.org/xml/features/disallow-doctype-decl을 true로 설정합니다.
  2. 보안 처리와 외부 접근 제한을 명시적으로 설정

    • XMLConstants.FEATURE_SECURE_PROCESSING을 true로 설정합니다.
    • XMLConstants.ACCESS_EXTERNAL_DTD와 XMLConstants.ACCESS_EXTERNAL_SCHEMA를 빈 문자열로 설정해 외부 프로토콜 접근을 거부합니다.
  3. DTD가 필요한 경우 허용 범위를 축소

    • 외부 일반 엔터티, 외부 매개변수 엔터티, 외부 DTD 로딩을 비활성화합니다.
    • 필요한 리소스만 로컬 XML 카탈로그나 검증된 리졸버로 해석하고, 유한한 처리 한도를 유지합니다.

예시

변경 전

java
import java.io.InputStream;
import javax.xml.XMLConstants;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.helpers.DefaultHandler;

public final class UnsafeXXE {
    static void parse(InputStream xml) throws Exception {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);

        SAXParser parser = factory.newSAXParser();
        parser.parse(xml, new DefaultHandler());
    }
}

이 코드는 보안 처리 한도를 명시적으로 끄고 DTD 및 외부 접근도 제한하지 않은 채 입력을 파싱합니다.

인라인 파싱에서 로컬 보안 설정이 생략된 경우도 함께 검토하세요.

java
import jakarta.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilderFactory;

public final class InlineServletXXE {
    static void parse(HttpServletRequest request) throws Exception {
        DocumentBuilderFactory.newInstance()
            .newDocumentBuilder()
            .parse(request.getInputStream());
    }
}

이 코드는 서블릿 요청 본문을 팩터리 생성부터 파싱까지 하나의 표현식으로 전달하므로 코드에서 팩터리나 파서를 보호하도록 구성할 지점이 없습니다. 배포 전체 JAXP 구성에서 DTD와 외부 접근을 이미 차단했다면 실제 런타임은 보호될 수 있습니다.

변경 후

java
import java.io.InputStream;
import javax.xml.XMLConstants;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.helpers.DefaultHandler;

public final class SafeXXE {
    static void parse(InputStream xml) throws Exception {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        factory.setFeature(
            "http://apache.org/xml/features/disallow-doctype-decl",
            true
        );

        SAXParser parser = factory.newSAXParser();
        parser.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
        parser.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
        parser.parse(xml, new DefaultHandler());
    }
}

이 예시는 DTD가 필요하지 않은 경우를 전제로 합니다. 보안 처리만 켜는 데 그치지 않고 DOCTYPE을 거부하며 외부 DTD·스키마 접근도 명시적으로 차단합니다.

참조