Skip to content

fix: ComResourceUtils의 XML 외부 엔티티(XXE) 취약점 수정 - #149

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

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

Conversation

@EricSeokgon

Copy link
Copy Markdown
Contributor

문제

ComResourceUtilscheckOldWebXml()(183행 부근)과 modifyWebXml()(248행 부근)은 사용자 프로젝트의 web.xml 을 아무 설정 없는 DocumentBuilderFactory 로 파싱합니다. 오염된 web.xml 이 있으면 XXE(CWE-611) 로 로컬 파일 읽기·SSRF 가 가능합니다.

같은 파일의 makeDocument() 는 이미 동일한 차단 설정을 가지고 있어, 이 두 메서드만 분방어 상태입니다. #138 리뷰에서 @swanpark8538 님이 알려주신 항목이며, 요청하신 대로 개별 PR 로 분리했습니다.

변경

dev / mdev 두 미러 파일의 해당 2개소(총 4개소)에 외부 엔티티·외부 DTD 차단 설정을 추가했습니다. 같은 파일 makeDocument() 가 이미 쓰는 설정과 동일한 목록이며, 파싱 의미를 바꾸는 setValidating·setNamespaceAware 는 건드리지 않았습니다.

검증 (실측)

서블릿 DTD 를 외부 참조하는 전형적인 web.xml 2.3 문서로 변경 전/후를 비교했습니다.

루트 servlet servlet-mapping 파싱 시간
변경 전 web-app 1 1 169ms
변경 후 web-app 1 1 2ms
  • checkServletMapping() 이 사용하는 노드 구조는 동일하며, 외부 DTD 를 내려받지 않아 오프라인 환경에서의 지연도 사라집니다.
  • XXE 픽스처(<!ENTITY xxe SYSTEM "file:///...">): 변경 전에는 파일 내용이 그대로 치환되었고, 변경 후에는 치환되지 않습니다.
  • 내부 엔티티(<!ENTITY ver "4.3.0">)는 변경 전후 모두 Text 노드로 동일하게 치환됩니다 — XmlUtil의 XML 외부 엔티티(XXE) 취약점 수정 #138 리뷰에서 지적해 주신 setExpandEntityReferences(false) 류의 부작용은 없습니다.

관련: #138

web.xml 을 파싱하는 checkOldWebXml·modifyWebXml 의 DocumentBuilderFactory 에 같은 파일의 makeDocument 와 동일한 외부 엔티티 차단 설정을 적용한다.
dev 모듈과 동일한 미러 파일에 같은 설정을 적용한다.
@eGovFrameSupport
eGovFrameSupport merged commit dd7fbc3 into eGovFramework:main Sep 7, 2026
1 check passed
@swanpark8538

Copy link
Copy Markdown
Collaborator

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

swanpark8538 added a commit that referenced this pull request Sep 8, 2026
…로 통합

core 의 XmlUtil.applyXxeProtection 이 private 이라 commngt 와 mdev.commngt 의
ComResourceUtils 는 같은 외부 DTD·외부 엔티티 차단 설정 4줄을 파일마다 9곳에
인라인으로 복사하고 있었고, #149 도 같은 블록을 두 곳에 더 추가했다. 설정을
바꿀 때 18곳을 각각 고쳐야 하고 한 곳만 빠져도 방어 수준이 파일마다 달라진다.

XmlUtil 에 보안 설정이 적용된 비검증 DocumentBuilderFactory 를 돌려주는
public 정적 메서드 newSecureDocumentBuilderFactory 를 두고, 두 ComResourceUtils
의 인라인 블록을 모두 이 호출로 바꾼다. validating, namespaceAware,
load-dtd-grammar 처럼 파싱 의미를 정하는 옵션은 각 사이트에 그대로 남긴다.

헬퍼는 setFeature 의 ParserConfigurationException 을 개별로 삼키므로, 파서가
Apache 전용 feature 를 인식하지 못하는 환경에서 예외가 catch (Exception) 으로
빠져 파싱 전체가 건너뛰어지던 경로(mdev 는 checkOldWebXml 이 false 를 돌려
web.xml 이 병합 대신 템플릿으로 교체됨)가 사라진다. 또한 applyXxeProtection 을
재사용하므로 FEATURE_SECURE_PROCESSING 이 함께 켜지는데, JDK 내장 파서는
이미 기본값이 true 라 동작 차이는 없다.

Refs: #149
@swanpark8538

swanpark8538 commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

이번에 추가한 setFeature 호출은 파서가 해당 feature 를 지원하지 않으면 그 자리에서 예외를 던집니다. 그런데 이 호출이 catch (Exception) 블록 안에 있어서, 예외가 나면 web.xml 파싱 자체가 통째로 건너뛰어집니다. 그러면 checkOldWebXml 이 false 를 돌려주고, 기존 web.xml 이 병합되지 않은 채 템플릿으로 덮어써질 수 있습니다. JDK 내장 파서와 Eclipse 의 Xerces 는 이 feature 를 모두 지원하므로 실제로 발생할 가능성은 낮지만, 방어 코드 때문에 원래 기능이 멈추는 구조는 피하는 편이 좋겠습니다.

그래서 #138XmlUtil 에 있던 방어 설정 코드를 다른 번들에서도 쓸 수 있도록 public 메서드 newSecureDocumentBuilderFactory() 로 열었습니다(cf1407b). 이 메서드는 지원하지 않는 feature 를 만나면 그 항목만 건너뛰고 나머지 방어는 유지합니다. 그리고 dev·mdev ComResourceUtils 에서 같은 설정을 인라인으로 반복하던 9곳씩(이번 PR 의 2곳 포함)을 모두 이 메서드 호출로 바꿨습니다. setValidating 이나 setNamespaceAware 처럼 파싱 방식을 정하는 옵션은 각 자리에 그대로 두었습니다.

한 가지 참고로, dev 번들의 checkOldWebXmlmodifyWebXml 은 호출하는 곳이 주석 처리되어 있어 현재 실행되지 않습니다. mdev 번들 쪽만 공통컴포넌트 설치 시 실제로 사용됩니다.

앞으로 사용자 프로젝트의 XML 을 DocumentBuilderFactory 로 읽는 코드를 추가하실 때는 DocumentBuilderFactory.newInstance() 대신 XmlUtil.newSecureDocumentBuilderFactory() 를 사용해 주시면 됩니다.

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