From 3c96348a5a151f3bba630f2a6acbad96ff6f9387 Mon Sep 17 00:00:00 2001 From: Eric Seokgon Lee Date: Sun, 9 Aug 2026 00:13:11 +0900 Subject: [PATCH] =?UTF-8?q?XmlUtil=EC=9D=98=20XML=20=EC=99=B8=EB=B6=80=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0(XXE)=20=EC=B7=A8=EC=95=BD=EC=A0=90?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DocumentBuilderFactory에 외부 엔티티 차단 설정을 추가해 CWE-611(XXE)을 방지한다. 정적 팩토리 2개와 지역 인스턴스 3개에 applyXxeProtection 헬퍼를 적용했으며, 저장소 자체 방어 패턴(ComResourceUtils:213)과 동형이다. 검증 파서는 DTD 로딩을 유지하도록 외부 엔티티만 차단한다. --- .../dev/imp/core/utils/XmlUtil.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/egovframework.dev.imp.core/src/egovframework/dev/imp/core/utils/XmlUtil.java b/egovframework.dev.imp.core/src/egovframework/dev/imp/core/utils/XmlUtil.java index 102465d8..027ea416 100644 --- a/egovframework.dev.imp.core/src/egovframework/dev/imp/core/utils/XmlUtil.java +++ b/egovframework.dev.imp.core/src/egovframework/dev/imp/core/utils/XmlUtil.java @@ -21,6 +21,7 @@ import java.io.StringReader; import java.io.StringWriter; +import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -47,6 +48,35 @@ public class XmlUtil { static { factory.setValidating(false); vfactory.setValidating(true); + // XXE(XML External Entity) 취약점 방지 + applyXxeProtection(factory, false); + applyXxeProtection(vfactory, true); + } + + /** + * XXE(XML External Entity Injection, CWE-611) 취약점을 방지하기 위해 + * 외부 엔티티 처리를 제한한다. + * + * 외부 일반/파라미터 엔티티를 차단하면 검증 여부와 무관하게 외부 엔티티를 통한 + * 로컬 파일 읽기·SSRF를 막을 수 있다. 스키마 검증(validating=true)이 필요한 + * 파서는 DTD 로딩을 유지해야 하므로 외부 엔티티 차단만 적용한다. + * + * @param dbf 보호를 적용할 DocumentBuilderFactory + * @param validating 검증 파서 여부(true면 DTD 로딩을 유지) + */ + private static void applyXxeProtection(DocumentBuilderFactory dbf, boolean validating) { + try { + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + if (!validating) { + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + } + } catch (ParserConfigurationException e) { + // 특정 기능을 지원하지 않는 파서에서도 나머지 방어는 적용되도록 무시한다. + } } public static Node getRootNode(String filePath) throws Exception{ @@ -184,6 +214,7 @@ public static void addNode(Node contextNode, String xPath, String xmlStr) throws Node parentsNode = componentList.item(0); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + applyXxeProtection(factory, false); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(new InputSource(new StringReader(xmlStr))); Node node = doc.getDocumentElement(); @@ -202,6 +233,7 @@ public static void addNode(Node contextNode, String xPath, String xmlStr, String Node parentsNode = componentList.item(0); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + applyXxeProtection(factory, false); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(new InputSource(new StringReader(xmlStr))); Node node = doc.getDocumentElement(); @@ -225,6 +257,7 @@ public static void addFirstNode(Node contextNode, String xPath, String xmlStr, S Node parentsNode = componentList.item(0); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + applyXxeProtection(factory, false); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(new InputSource(new StringReader(xmlStr))); Node node = doc.getDocumentElement();