XXE 취약점이란? XML 외부 개체 공격의 원인과 대응 방법

XXE 취약점이 발생하는 원인과 XML 외부 개체가 처리되는 원리, 취약한 Java 코드 및 DTD·외부 엔티티를 차단하는 방법을 알아봅니다.

XML은 시스템 사이에서 데이터를 주고받거나 설정 정보를 저장할 때 사용하는 문서 형식이다. 최근에는 JSON이 널리 사용되고 있지만, SOAP 기반 웹서비스와 기업용 연계 시스템, 문서 변환 기능에서는 여전히 XML을 사용한다.

XML 데이터를 처리하는 과정에서 파서의 외부 개체 기능이 안전하지 않게 활성화돼 있으면 공격자가 서버 내부 파일이나 외부 주소를 참조하도록 만들 수 있다. 이러한 보안 문제를 XXE 취약점이라고 한다.

XXE 취약점이란?

XXE는 ‘XML External Entity’의 약자로 XML 외부 개체를 의미한다. XML 문서에는 반복적으로 사용하는 값을 개체로 정의하고 문서 내부에서 불러오는 기능이 있다. 또한 DTD라는 문서 구조 정의를 이용해 외부 파일이나 주소를 참조할 수도 있다.

문제는 외부 사용자가 작성한 XML 문서에 포함된 개체 선언을 서버의 XML 파서가 그대로 처리할 때 발생한다. 공격자는 외부 개체가 서버 내부 파일이나 네트워크 주소를 가리키도록 조작할 수 있다.

파서가 해당 개체를 해석하면 웹 애플리케이션은 개발자가 의도하지 않은 파일을 읽거나 외부 주소로 요청을 보낼 수 있다. 처리 결과가 응답에 포함되는 경우에는 파일 내용이 사용자에게 노출될 가능성도 있다.

XXE 취약점의 핵심은 외부에서 전달된 XML 문서가 서버의 파일이나 네트워크 자원을 참조할 수 있다는 점이다.

XML 외부 개체가 필요한 이유

외부 개체 기능이 처음부터 공격을 위해 만들어진 것은 아니다. XML 문서에서 공통으로 사용하는 내용을 외부 파일로 관리하거나, 문서 구조를 별도의 DTD 파일로 정의하기 위해 사용된다.

신뢰할 수 있는 내부 문서만 처리하는 환경에서는 편리한 기능일 수 있다. 하지만 웹 애플리케이션이 인터넷에서 전달된 XML을 처리한다면 상황이 달라진다. 사용자가 외부 개체의 참조 대상을 자유롭게 정할 수 있기 때문이다.

현재 서비스에서 외부 DTD나 외부 개체가 필요하지 않다면 관련 기능을 비활성화하는 것이 가장 안전하다. 사용하지 않는 기능을 그대로 열어두면 공격자가 이를 서버 자원에 접근하는 통로로 이용할 수 있다.

XXE 취약점이 발생하는 원인

XXE는 XML 파서가 DTD와 외부 개체를 허용하도록 설정된 상태에서 신뢰할 수 없는 XML 데이터를 처리할 때 발생한다.

개발자가 XML 파서의 기본 설정을 확인하지 않고 사용하는 경우가 대표적이다. XML 라이브러리와 버전에 따라 기본 보안 설정이 다를 수 있으므로 특정 환경에서 안전하게 동작했다는 이유만으로 다른 환경까지 안전하다고 판단해서는 안 된다.

외부에서 업로드된 XML 파일을 바로 파싱하거나 SOAP 요청 본문을 검증 없이 처리하는 기능에서도 발생할 수 있다. SVG처럼 XML을 기반으로 만들어진 이미지 파일이나 XML 구조가 포함된 문서를 서버에서 분석할 때도 파서 설정을 확인해야 한다.

파일 확장자가 .xml이 아니더라도 내부 처리 과정에서 XML 파서를 사용한다면 XXE 점검 대상이 될 수 있다.

XXE에 취약한 Java 코드

다음은 사용자가 업로드한 XML 파일을 Java의 DocumentBuilderFactory로 처리하는 코드다.

@PostMapping("/xml/upload")
public String uploadXml(@RequestParam MultipartFile file)
        throws Exception {

    DocumentBuilderFactory factory =
            DocumentBuilderFactory.newInstance();

    DocumentBuilder builder =
            factory.newDocumentBuilder();

    Document document =
            builder.parse(file.getInputStream());

    return document.getDocumentElement().getTextContent();
}

코드는 XML 파일을 읽어 최상위 요소의 내용을 반환한다. 하지만 DTD와 외부 개체에 관한 보안 설정이 적용돼 있지 않다.

사용자가 전달한 XML에 외부 개체 선언이 포함돼 있고 파서가 해당 기능을 지원한다면 서버는 외부 개체가 가리키는 자원을 읽으려고 할 수 있다. 그 결과가 getTextContent()에 포함되면 서버 내부의 정보가 HTTP 응답으로 노출될 가능성이 있다.

취약 여부는 사용하는 Java 버전과 XML 파서 구현, 보안 설정에 따라 달라질 수 있다. 따라서 기본 설정을 신뢰하기보다 애플리케이션에서 위험한 기능을 명시적으로 비활성화해야 한다.

XXE 공격이 처리되는 원리

공격자는 서버로 전송할 XML 문서 안에 외부 개체를 정의한다. 외부 개체의 참조 대상에는 서버 내부 파일이나 네트워크 주소가 지정될 수 있다.

웹 애플리케이션이 XML 문서를 파서에 전달하면 파서는 문서 구조를 해석한다. 이때 외부 개체 기능이 활성화돼 있으면 파서는 참조된 자원을 읽기 위해 파일 시스템이나 네트워크에 접근한다.

불러온 내용은 XML 문서의 일부로 치환될 수 있다. 애플리케이션이 처리 결과를 화면이나 오류 메시지에 포함하면 내부 정보가 외부로 전달될 수 있다.

응답에서 결과가 보이지 않는 경우도 있다. 이를 Blind XXE라고 한다. 화면에 파일 내용이 표시되지 않더라도 서버가 외부 주소로 요청을 보낸다면 내부망 탐색이나 정보 유출의 통로로 악용될 수 있다.

XXE 공격으로 발생할 수 있는 피해

XXE 공격이 성공하면 웹 서버 계정이 읽을 수 있는 설정 파일이나 시스템 정보가 노출될 수 있다. 애플리케이션 설정 파일에는 데이터베이스 주소, 계정 정보 또는 외부 서비스 연결 정보가 포함될 수 있어 추가 피해로 이어질 가능성이 있다.

외부 개체에 내부 네트워크 주소가 지정되면 서버가 대신 요청을 보내는 SSRF 형태의 공격도 발생할 수 있다. 외부에서는 접근할 수 없는 내부 API나 관리 서비스가 웹 서버를 통해 호출될 수 있기 때문이다.

XML 개체를 반복적으로 확장하도록 구성하면 CPU와 메모리가 과도하게 사용될 수도 있다. 이를 개체 확장 공격이라고 하며, 서비스 지연이나 장애를 일으킬 수 있다.

피해 범위는 XML 파서의 기능과 웹 서버 권한, 내부 네트워크 접근 범위에 따라 달라진다. 따라서 파서 설정뿐 아니라 최소 권한과 네트워크 접근통제도 함께 적용해야 한다.

XXE를 예방하는 안전한 Java 코드

Java에서 DocumentBuilderFactory를 사용하는 경우 DTD와 외부 개체 처리를 명시적으로 비활성화할 수 있다.

@PostMapping("/xml/upload")
public String uploadXml(@RequestParam MultipartFile file)
        throws Exception {

    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.setFeature(
        "http://apache.org/xml/features/nonvalidating/load-external-dtd",
        false
    );

    factory.setFeature(
        XMLConstants.FEATURE_SECURE_PROCESSING,
        true
    );

    factory.setXIncludeAware(false);
    factory.setExpandEntityReferences(false);

    factory.setAttribute(
        XMLConstants.ACCESS_EXTERNAL_DTD,
        ""
    );

    factory.setAttribute(
        XMLConstants.ACCESS_EXTERNAL_SCHEMA,
        ""
    );

    DocumentBuilder builder =
            factory.newDocumentBuilder();

    Document document =
            builder.parse(file.getInputStream());

    return document.getDocumentElement().getTextContent();
}

disallow-doctype-decl 설정은 XML 문서에 DOCTYPE 선언이 포함된 경우 처리를 거부한다. 일반 외부 개체와 매개변수 외부 개체도 각각 비활성화하고, 외부 DTD 로딩과 XInclude 기능도 차단한다.

ACCESS_EXTERNAL_DTDACCESS_EXTERNAL_SCHEMA에 빈 값을 지정하면 외부 DTD와 스키마에 접근할 수 있는 프로토콜을 허용하지 않겠다는 의미다.

XML 파서에 따라 지원하는 기능과 설정 방식이 다를 수 있다. 보안 설정을 적용하는 과정에서 예외가 발생했는데 이를 무시하고 기본 파서로 계속 처리해서는 안 된다. 필요한 보안 기능을 지원하지 않는다면 XML 처리를 중단하거나 안전한 파서로 교체해야 한다.

입력 데이터와 XML 구조도 검증해야 한다

외부 개체를 비활성화하는 것이 XXE 대응의 핵심이지만 XML 데이터 자체에 대한 검증도 필요하다. 애플리케이션에서 예상하는 요소와 속성만 포함돼 있는지 확인하고, 문서 크기와 요소 개수 및 중첩 깊이를 제한해야 한다.

지나치게 큰 XML 문서나 비정상적으로 깊게 중첩된 구조는 파서의 CPU와 메모리를 소모시킬 수 있다. 업로드 파일과 요청 본문의 최대 크기를 제한하고 처리 시간을 모니터링하는 것이 좋다.

업무에 DTD가 필요하지 않다면 DOCTYPE이 포함된 XML 문서를 거부하는 방식이 가장 단순하고 안전하다. 외부 DTD를 반드시 사용해야 한다면 인터넷 주소를 자유롭게 참조하게 하지 말고, 서버에 등록된 신뢰할 수 있는 로컬 자원만 사용하도록 별도의 해석기를 구성해야 한다.

네트워크와 권한 수준의 추가 대응

XML 파서의 외부 접근 기능을 차단했더라도 서버의 네트워크 권한을 최소화해야 한다. 웹 서버가 불필요한 내부 관리망이나 클라우드 메타데이터 서비스에 접속하지 못하도록 방화벽과 보안 그룹을 설정한다.

웹 애플리케이션은 최소 권한의 전용 계정으로 실행해야 한다. XXE로 파일 접근이 시도되더라도 웹 서버 계정에 읽기 권한이 없다면 중요 파일이 노출되는 것을 막을 수 있다.

DOCTYPE이 포함된 요청, XML 파싱 오류와 비정상적인 외부 연결 시도를 로그로 기록하는 것도 중요하다. 평소 XML을 사용하지 않는 기능에서 XML 형식의 요청이 반복된다면 보안 점검이 필요하다.

XXE 취약점 점검 항목

XXE 취약점을 점검할 때는 먼저 서비스에서 XML을 처리하는 지점을 찾아야 한다. 요청의 Content-Type이 XML인 API뿐 아니라 파일 업로드, SOAP 연동, 문서 변환과 SVG 처리 기능도 확인해야 한다.

소스코드에서는 DocumentBuilderFactory, SAXParserFactory, XMLInputFactory, TransformerFactory처럼 XML을 처리하는 클래스를 검색한다. 외부에서 전달된 데이터가 파서로 들어가는지 추적하고, DTD와 외부 개체가 명시적으로 차단돼 있는지 확인한다.

XML 라이브러리와 프레임워크의 버전도 확인해야 한다. 보안 설정이 적용돼 있더라도 다른 처리 단계에서 새로운 파서를 생성한다면 해당 파서에도 동일한 설정이 필요하다.

운영 환경에서는 웹 서버가 내부 주소나 외부 인터넷으로 불필요한 요청을 보낼 수 있는지 확인한다. 애플리케이션 로그와 네트워크 로그를 함께 분석하면 XML 처리 이후 발생한 비정상적인 연결을 찾는 데 도움이 된다.

자주 묻는 질문

XML을 사용하지 않으면 XXE 취약점이 발생하지 않나요?

애플리케이션이 XML을 전혀 파싱하지 않는다면 일반적인 XXE 공격 가능성은 낮다. 다만 SVG, SOAP, 문서 변환처럼 내부적으로 XML을 사용하는 기능이 있을 수 있으므로 파일 확장자와 API 형식만 보고 판단해서는 안 된다.

DOCTYPE만 차단하면 충분한가요?

일반적인 XXE 공격을 막는 데 효과적이지만 파서의 다른 외부 접근 기능도 함께 확인하는 것이 좋다. 외부 일반 개체, 매개변수 개체, 외부 DTD, XInclude와 외부 스키마 접근을 모두 제한해야 한다.

JSON으로 변경하면 XXE를 막을 수 있나요?

XML 파싱을 완전히 제거하고 JSON으로 처리한다면 XXE 위험은 사라질 수 있다. 하지만 JSON 역직렬화나 입력값 검증 오류처럼 다른 취약점이 발생할 수 있으므로 데이터 형식을 변경하는 것만으로 전체 보안 문제가 해결되는 것은 아니다.

웹 방화벽으로 XXE를 차단할 수 있나요?

일부 공격 형태를 탐지할 수는 있지만 근본적인 대응 방법은 아니다. 인코딩이나 문서 구조에 따라 탐지를 우회할 가능성이 있으므로 애플리케이션의 XML 파서에서 외부 개체 기능을 비활성화해야 한다.

마무리

XXE 취약점은 신뢰할 수 없는 XML 문서에 포함된 외부 개체를 서버의 파서가 처리할 때 발생한다. 공격자는 이를 이용해 서버 내부 파일을 읽거나 내부 주소로 요청을 보내려고 시도할 수 있다.

가장 중요한 대응 방법은 XML 파서에서 DTD와 외부 개체 기능을 명시적으로 비활성화하는 것이다. XML 문서의 크기와 구조를 제한하고, 서버 계정과 네트워크 접근 권한도 최소화해야 한다.

XXE 방어의 핵심은 외부 XML 문서가 서버의 파일과 네트워크 자원을 참조하지 못하게 하는 것이다. 파서의 기본 설정을 그대로 신뢰하지 말고 사용하는 라이브러리와 처리 지점마다 보안 설정이 실제로 적용됐는지 확인해야 한다.

핵심 키워드: XXE 취약점, XML 외부 개체, XML External Entity, XXE 공격 원리, 외부 엔티티 차단, DTD 보안 설정, XML 파서 보안, XXE 대응 방법

댓글 남기기