애플리케이션 기동 로그 첫머리에 다음과 같은 메시지가 찍힌다.
ERROR StatusLogger Caught javax.xml.parsers.ParserConfigurationException setting feature
http://xml.org/sax/features/external-general-entities to false on DocumentBuilderFactory
oracle.xml.jaxp.JXDocumentBuilderFactory@3703bf3c
StatusLogger 가 찍었다는 것은 Log4j2 가 자신의 설정 XML 을 읽으면서 외부 엔티티 참조를 끄려다 실패했다는 뜻이다. 대개 로깅은 그대로 동작하지만, 같은 원인으로 다른 XML 처리에서 예기치 않은 동작이 생길 수 있다.
DocumentBuilderFactory 구현은 JAXP 의 탐색 순서에 따라 정해진다. 클래스패스에 xmlparserv2.jar(Oracle XML Developer Kit)가 있으면 그 안의 META-INF/services/javax.xml.parsers.DocumentBuilderFactory 선언이 JDK 내장 구현보다 먼저 선택되어, 애플리케이션 전체의 XML 파서가 Oracle 구현으로 바뀐다.
Oracle 구현은 XXE 방어용으로 널리 쓰이는 몇몇 기능 플래그를 지원하지 않는다. 그래서 Log4j2 나 프레임워크가 안전 설정을 시도할 때 예외가 난다.
xmlparserv2.jar 는 대체로 Oracle JDBC 배포물이나 벤더 제품 라이브러리를 통째로 넣을 때 딸려 들어온다.
실제로 어떤 구현이 선택됐는지 출력해 본다.
System.out.println(javax.xml.parsers.DocumentBuilderFactory.newInstance().getClass().getName());
클래스패스에서 해당 jar 를 찾는다.
find /opt/app/lib -name "xmlparserv2*.jar" -o -name "xdb*.jar"
unzip -l /opt/app/lib/xmlparserv2.jar | grep -i services
가장 깔끔한 해결은 필요 없는 jar 를 클래스패스에서 빼는 것이다. Oracle XDK 기능을 실제로 쓰지 않는다면 xmlparserv2.jar 를 제거한다. Maven 프로젝트라면 JDBC 의존성에서 제외한다.
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc11</artifactId>
<version>23.9.0.25.07</version>
<exclusions>
<exclusion>
<groupId>com.oracle.database.xml</groupId>
<artifactId>xmlparserv2</artifactId>
</exclusion>
</exclusions>
</dependency>
jar 를 뺄 수 없다면 시스템 속성으로 구현을 고정한다. JDK 내장 구현을 명시하면 서비스 선언보다 우선한다.
java -Djavax.xml.parsers.DocumentBuilderFactory=com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl \
-Djavax.xml.parsers.SAXParserFactory=com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl \
-Djavax.xml.transform.TransformerFactory=com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl \
-jar app.jar
애플리케이션 코드에서 파서를 직접 만드는 자리라면 구현을 지정해 생성한다.
DocumentBuilderFactory f = DocumentBuilderFactory.newInstance(
"com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl",
null);
f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
지원되지 않는 기능이라고 해서 예외를 삼키고 넘어가면 외부 엔티티 참조가 열린 채로 남는다. XXE 취약점이 되므로, 예외를 무시하는 방식은 파서 구현을 바꿀 수 없는 경우의 임시 조치로만 쓴다. 가능하면 disallow-doctype-decl 처럼 해당 구현이 지원하는 방식으로라도 DOCTYPE 자체를 막는다.