Skip to content

XmlUtil의 XML 외부 엔티티(XXE) 취약점 수정 - #138

Merged
eGovFrameSupport merged 2 commits into
eGovFramework:mainfrom
EricSeokgon:fix/xmlutil-xxe
Sep 3, 2026
Merged

XmlUtil의 XML 외부 엔티티(XXE) 취약점 수정#138
eGovFrameSupport merged 2 commits into
eGovFramework:mainfrom
EricSeokgon:fix/xmlutil-xxe

Conversation

@EricSeokgon

@EricSeokgon EricSeokgon commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

수정 사유 Reason for modification

  • 버그수정 Bug fixes (보안 취약점, CWE-611 XXE)
  • 기능개선 Enhancements
  • 기능추가 Adding features
  • 기타 Others

수정된 소스 내용 Modified source

XmlUtil이 XML을 파싱할 때 DocumentBuilderFactory에 외부 엔티티 차단 설정을 하지 않아 XXE(XML External Entity Injection, CWE-611) 에 노출됩니다. 미방어 지점은 정적 팩토리 2개(45·46행)와 지역 인스턴스 3개(186·204·227행, 수정 전 기준)입니다.

XmlUtil은 이 저장소의 6개 플러그인에서 호출되며, 그중 HandlePomXMLFileUtil은 프로젝트의 pom.xml을 파싱합니다. 따라서 외부 엔티티가 포함된 XML(예: 악의적 pom.xml)이 든 프로젝트를 IDE로 열면 개발자 PC의 로컬 파일이 읽히거나 외부 URL 요청이 발생할 수 있습니다.

수정 내용

이 저장소는 이미 같은 방어 패턴을 갖고 있습니다 — egovframework.dev.imp.commngtComResourceUtils.java 213행이 setFeature 계열로 외부 엔티티를 차단합니다. 이 PR은 그 패턴을 XmlUtil에 맞춰 넣는 것으로 새 규칙을 도입하지 않습니다.

applyXxeProtection(DocumentBuilderFactory, boolean) private 헬퍼를 추가하고 5개 파싱 지점에 적용했습니다(+33/−0). 외부 일반/파라미터 엔티티를 차단해 검증 여부와 무관하게 XXE 파일 읽기를 막습니다. 스키마 검증용 vfactory(validating=true)는 DTD 로딩이 필요하므로 외부 엔티티 차단만 적용하고, 비검증 파서에는 외부 DTD 로딩 차단과 FEATURE_SECURE_PROCESSING까지 적용했습니다. 파서가 특정 기능을 미지원하면 ParserConfigurationException을 무시하고 나머지 방어를 적용합니다.

JUnit 테스트 JUnit tests

  • JUnit 테스트 JUnit tests
  • 수동 테스트 Manual testing

빌드: 번들된 lib/xalan.jar를 클래스패스로 javac 단독 컴파일 성공(JDK 21, 오류 0).

재현·검증 (수정 전/후 동일 하네스, JDK 21.0.10 — applyXxeProtection의 실제 로직을 복제해 실행). 공격 XML(<!DOCTYPE r [ <!ENTITY xxe SYSTEM "file://…"> ]>)로 로컬 파일 읽기를 시도:

파서 수정 전 수정 후
비검증(non-validating) 파일 유출 v=[TOP-SECRET-EGOV-2026] 차단 v=[]
검증(validating) 파일 유출 v=[TOP-SECRET-EGOV-2026] 차단 v=[]

정상 XML 회귀 확인(파싱은 계속 동작해야 함):

파서 수정 후
비검증 v=[정상데이터] (정상)
검증 v=[정상데이터] (정상)

수정 후 외부 엔티티는 빈 값으로 무력화되고, 정상 XML 파싱과 스키마 검증 경로는 그대로 동작합니다.

테스트 브라우저 Test Browser

  • 기타 Others — UI 변경 없음. XML 파서 설정만 수정했으며 위 하네스로 검증했습니다.

테스트 스크린샷 또는 캡처 영상 Test screenshots or captured video

UI 변경이 없어 스크린샷은 해당하지 않습니다. 위 표의 실측값을 근거로 제시합니다.


참고 — 같은 저장소의 동일 유형 (이 PR 범위 밖): 근본 원인이 같은 XXE 미방어 지점이 dev/mdev ComResourceUtils(각 2곳, 같은 파일의 다른 지점은 이미 방어됨), NexusPropertyPage, QueryIdSearchJob에도 있습니다. 이 PR은 호출자가 가장 많은 XmlUtil 한 파일로 범위를 한정했으며, 나머지도 동일 방식으로 후속 정리할 수 있습니다. 필요하시면 이 PR에 합치겠습니다.

병합된 보안 수정 선례 (참고)

같은 조직(eGovFramework)에서 제가 제출한 동일 성격의 보안 수정들이 최근 병합되었습니다 — egovframe-common-components #1134(뷰 이름 인젝션 Locale 우회), #1156(상위 경로 .. 재생성 차단), #1163(팝업 URL 탭 문자 우회 차단). 본 PR 은 그 연장선에서 개발환경 쪽 XML 파서의 외부 엔티티 해석을 표준 권고(외부 DTD·엔티티 비활성화)대로 차단하는 것입니다.

DocumentBuilderFactory에 외부 엔티티 차단 설정을 추가해 CWE-611(XXE)을 방지한다. 정적 팩토리 2개와 지역 인스턴스 3개에 applyXxeProtection 헬퍼를 적용했으며, 저장소 자체 방어 패턴(ComResourceUtils:213)과 동형이다. 검증 파서는 DTD 로딩을 유지하도록 외부 엔티티만 차단한다.
@swanpark8538

Copy link
Copy Markdown
Collaborator

XmlUtil.java 파일에서 DocumentBuilderFactory를 만들 때 외부 엔티티 차단 설정이 없어서
사용자가 악의적인 <!DOCTYPE ... <!ENTITY xxe SYSTEM "file://...">>가 든 pom.xml을 가진 프로젝트를 IDE로 열고 XmlUtil.java가 실행된다면, 그 pom.xml 파일로부터 XXE(XML External Entity Injection) 위험이 발생할 가능성을 확인했습니다.

egovframe-plugins 내부에 있는 pom.xml은 센터 검증을 거친 것이므로 XXE 위험은 없을 것입니다.
해당 위험이 발생할 가능성은 사용자의 workspace 내 프로젝트의 pom.xml 파일이 오염되었을 경우일 것입니다.

취약점이 실제 공격으로 이어질 가능성은 낮지만 위험 가능성이 존재하므로 해당 취약점에 대한 보안 조치를 취하는 것에 동의합니다.

또한 PR 내용을 검증한 결과 기능상 이상이 없다는 것과 XXE 방어에 성공하는 것을 확인했습니다.

다만 72행에 setExpandEntityReferences(false) 설정은 불필요하거나 오류를 일으킬 가능성을 확인했습니다.
해당 설정은 파일이나 URL과 관련된 설정이 아니라, 파서가 엔티티 참조 &ver;를 만났을 때 값으로 치환한 Text 노드를 만들지(true) 말지(false)를 결정하는 설정입니다.

일반적인 경우 pom.xml의 version에 치환값을 넣는 설정은 하지 않으므로 문제가 없을 것입니다. 문제가 발생할 수 있는 경우는 아래와 같은 케이스를 상정할 수 있습니다.

만약 사용자 프로젝트에 다음과 같은 pom.xml이 있다고 가정한다면

<!DOCTYPE project [ <!ENTITY egovVer "4.3.0"> ]>
<project>
  <dependencies>
    <dependency>
      <artifactId>egovframework-rte-ptl-mvc</artifactId>
      <version>&egovVer;</version>
    </dependency>
  </dependencies>
</project>

&egovVer;는 "여기에 4.3.0을 넣어라"라는 문서 안의 치환 매크로입니다. 파일이나 URL을 가리키지 않으므로 XXE와는 무관합니다.

72행에 setExpandEntityReferences(false) 설정이 없다면 version 태그 속성의 타입을 "Text"로, 값을 "4.3.0"으로 잘 인식할 것입니다. 직렬화 결과는 <version>4.3.0</version> 입니다.
하지만 현재처럼 setExpandEntityReferences(false) 설정이 있다면 version 태그 속성의 타입을 "EntityReference"로, 값을 "null"로 잘못 인식할 것입니다. 직렬화 결과는 <version/> 입니다.

그러면 NullPointerException이 발생해 오류가 발생할 것입니다.

위와 같은 케이스로 pom.xml을 작성할 가능성은 실무에서는 거의 없을 것이지만,
72행에 setExpandEntityReferences(false) 설정이 XXE 위험과는 무관할 뿐더러 위와 같은 오류를 일으킬 수도 있으므로
72행을 지우는 커밋을 추가해 주시기 바랍니다.

전자정부 표준프레임워크에 기여해 주셔서 감사합니다.

setExpandEntityReferences 는 외부 리소스 접근이 아니라 내부 엔티티 참조의 치환 여부를 결정하는 설정이라 XXE 방어와 무관하며, 내부 엔티티를 쓴 pom.xml 에서 값이 null 로 인식돼 오류를 유발할 수 있다는 리뷰를 수용해 제거한다. 외부 일반·파라미터 엔티티 차단, XInclude 비활성화, 비검증 파서의 외부 DTD 차단·보안 처리는 그대로 유지한다.
@EricSeokgon

Copy link
Copy Markdown
Contributor Author

@swanpark8538 상세한 검토와 재현 케이스까지 짚어주셔서 감사합니다. 지적하신 내용에 동의하며 요청대로 반영했습니다.

  • setExpandEntityReferences(false) 를 제거했습니다. 말씀대로 이 설정은 외부 리소스 접근이 아니라 문서 내부 엔티티 참조를 Text 로 치환할지 여부를 정하는 것이라 XXE 방어와 무관하고, 내부 엔티티를 사용한 pom.xml 에서는 값이 EntityReference/null 로 인식되어 오류를 유발할 수 있습니다.
  • XXE 방어에 실제로 기여하는 설정은 그대로 유지했습니다 — 외부 일반 엔티티 차단, 외부 파라미터 엔티티 차단, setXIncludeAware(false), 그리고 비검증 파서에 한해 외부 DTD 로딩 차단과 FEATURE_SECURE_PROCESSING.

검증 결과(기능 이상 없음·XXE 방어 성공)까지 확인해 주셔서 감사합니다. 다시 검토 부탁드립니다.

@eGovFrameSupport eGovFrameSupport left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리뷰 반영(setExpandEntityReferences(false) 제거) 확인했습니다. XXE 차단(외부 일반·파라미터 엔티티, 외부 DTD, secure processing)은 그대로 유지되고 내부 엔티티 DOM 회귀는 해소된 것을 검증했습니다.

전자정부 표준프레임워크에 기여해 주셔서 감사합니다.

@eGovFrameSupport
eGovFrameSupport merged commit 450eb4a into eGovFramework:main Sep 3, 2026
1 check passed
@swanpark8538

Copy link
Copy Markdown
Collaborator

PR에 합치겠다고 말씀하신 추가 작업들은 각각 개별로 PR 해주시기 바랍니다.

같은 XXE 위협에 노출된 미방어 파서

  • rte.rdt의 PomParser
  • dbio의 QueryIdSearchJob
  • dev/mdev ComResourceUtils 183·248행
  • NexusPropertyPage 410행

@EricSeokgon

Copy link
Copy Markdown
Contributor Author

@swanpark8538 님, 리뷰와 병합 감사합니다. 알려주신 미방어 파서 4건을 각각 개별 PR로 올렸습니다.

대상 PR 비고
rte.rdt PomParser #147 JDOM SAXBuilder — 아래 특이사항 있음
dbio QueryIdSearchJob #148
dev·mdev ComResourceUtils 183·248행 #149 미러 파일 2개를 한 PR에
confmngt NexusPropertyPage 410행 #150 파싱 대상이 고정 문자열이라 실제 공격 경로는 없음 (PR 본문에 명시)

각 PR은 변경 전/후를 실제로 실행해 비교했고, 특히 지적해 주셨던 내부 엔티티(&egovVer;) 치환이 그대로 동작하는지를 매번 확인했습니다.

한 가지 공유드릴 실측 결과가 있습니다. PomParser가 쓰는 JDOM 1.1.1의 SAXBuilder에서는

builder.setFeature("http://xml.org/sax/features/external-general-entities", false);

효과가 없습니다. SAXBuilder가 파서를 구성할 때 이 feature를 expandEntities 값(기본 true)으로 덮어쓰기 때문입니다. 이 설정만 넣고 실행하면 file:/// 엔티티가 그대로 치환되는 것을 확인했습니다. 반대로 disallow-doctype-decl을 켜면 말씀하신 내부 엔티티 pom.xml이 파싱 자체에서 실패합니다. 그래서 #147은 EntityResolver로 외부 참조만 무력화하는 방식을 썼습니다 — 내부 엔티티는 영향을 받지 않습니다.

검토 부탁드립니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants