소프트웨어(SW) 보안약점 진단원/구현단계 보안약점 제거 기준

구현단계 보안약점 기준 - 부적절한 XML 외부개체 참조

브루노W 2025. 5. 26. 22:46
유형 입력데이터 검증 및 표현
보안약점 부적절한 XML 외부개체 참조
개요
XML 문서에는 DTD(Document Type Definition)를 포함할 수 있으며, DTD는 XML 엔티티(entitiy)*를 정의한다.
 
부적절한 XML 외부개체 참조 보안약점은 서버에서 XML 외부 엔티티를 처리할 수 있도록 설정된 경우에 발생할 수 있다.
 
취약한 XML parser가 외부 값을 참조하는 XML 값을 처리할 때, 공격자가 삽입한 공격 구문이 동작되어 서버 파일 접근, 불필요한 자원 사용, 인증 우회, 정보 노출 등이 발생 할 수 있다.
* 반복적인 문장이나 문자열을 저장해놓고 쉽게 참조할 수 있도록 함
보안대책
로컬 정적 DTD를 사용하도록 설정하고, 외부에서 전송된 XML문서에 포함된 DTD를 완전하게 비활성화 해야 한다.

비활성화를 할 수 없는 경우에는 외부 엔티티 및 외부 문서 유형 선언을 각 파서에 맞는 고유한 방식으로 비활성화 한다.
진단방법
XML 파일을 파싱하고 있는지 확인한 후 신뢰할 수 없는 외부 입력값의 파일에 대한 외부 엔티티를 비활성화 하는 코드가 없을 경우 취약하다고 판정한다.
연관된 설계단계 기준 XML 조회 및 결과 검증

 

코드예제

 

다음의 예제는 XML 소스를 읽어서 분석하는 소스코드이다.
공격자가 아래와 같이 XML 외부 엔티티를 참조하는 recivedXML 데이터를 전송하고, 이를 파싱할 때 /etc/passwd 파일을 참조할 수 있다.

 

receivedXML
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo>

 

 

● 안전하지 않은 코드 예 (Java)

public void unmarshal(File receivedXml) 
throws JAXBException, ParserConfigurationException, SAXException, IOException {
    JAXBContext jaxbContext = JAXBContext.newInstance( Student.class );
    Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
    // 입력받은 receivedXml 을 이용하여 Document를 생성한다.
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    DocumentBuilder db = dbf.newDocumentBuilder();
    Document document = db.parse(receivedXml);
    // 외부 엔티티로 만들어진 document를 이용하여 마샬링을 수행하여 안전하지 않다.
    Student employee = (Student) jaxbUnmarshaller.unmarshal( document );
}

 

 

다음 예제는 class XXE에서 외부개체 참조 제한 설정 없이 secure.xml을 참조하고 있다. 
이 때, secure.xml이 아래와 같을 때 /dev/tty 콘솔이 실행되어 입력 요청을 대기하는 서비스 거부 공격이 발생할 수 있다.

secure.xml
<?xml version="1.0"?>
<!DOCTYPE foo SYSTEM "file:/dev/tty">
<foo>bar</foo>

 

 

● 안전하지 않은 코드 예 (Java)

import javax.xml.parsers.SAXParsers;
import javax.xml.parsers.SAXParserFactory;

class XXE {
    public static void main(String[] args) 
    throws FileNotFoundException, ParserConfigurationException, SAXException, IOException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser saxParser = factory.newSAXParser();
        // 외부개체 참조 제한 설정 없이 secure.xml 파일을 읽어서 파싱하여 안전하지 않다.
        saxParser.parse(new FileInputStream("secure.xml"), new DefaultHandler());
    }
}

 

 

● 안전한 코드 예 (Java)

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// XML 파서가 doctype을 정의하지 못하도록 설정한다.
dbf.setFeature("http://apache.org/xml/featuresdisallow-doctype-decl", true);
// 외부 일반 엔티티를 포함하지 않도록 설정한다.
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
// 외부 파라미터도 포함하지 않도록 설정한다.
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
// 외부 DTD 비활성화한다.
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
// XIncude를 사용하지 않는다.
dbf.setXIncludeAware(false);
// 생성된 파서가 엔티티 참조 노드를 확장하지 않도록 한다.
dbf.setExpandEntityReferences(false);
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.parse(receivedXml);
Model model = (Model) u.unmarshal(document);

 

 

● 안전한 코드 예 (PHP)

value = libxml_disable_entity_loader(true);
$dom = new DOMDocument();
$dom -> loadXML($xml);
libxml_disable_entity_loader($value);

 

 

진단방법
 
 
XML 파일을 파싱하고 있는지 확인한 후(①) 신뢰할 수 없는 외부 입력값의 파일에 대한 외부 엔티티를 비활성화하는 코드가 없을 경우 취약하다고 판정한다.

 

 

● 일반적인 진단의 예

public class G06 extends HttpServlet {
    private ServletFileUpload uploader = null;
    ...
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
    ...
    try {
        List<FileItem> fileItemsList = uploader.parseRequest(request);
        Iterator<FileItem> fileItemsIterator = fileItemsList.iterator();
        while (fileItemsIterator.hasNext()) {
            FileItem fileItem = fileItemsIterator.next();
            File xmlFile = new File( request.getServletContext().getAttribute("FILES_DIR") + File.separator+ fileItem.getName() );
            fileItem.write(xmlFile);
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document document = db.parse(xmlFile); ·········································①
            changeConfiguration(document);
            ...
    }

 

 

● 정탐코드

@RequestMapping(value = "/xmlupload", method = RequestMethod.POST)
public Student xmlUpload(@RequestParam("file") MultipartFile multipartFile) {
    File xmlFile = new File(multipartFile.getOriginalFilename());
    multipartFile.transferTo(xmlFile);
    SAXParserFactory factory = SAXParserFactory.newInstance();
    SAXParser saxParser = factory.newSAXParser();
    SAXParseraxParser.parse(new FileInputStream(xmlFile), new DefaultHandler());

SAXParserFactory 사용하는 예제에서는 사용자 입력값인 XML file을 사용하고 있으나, 외부 엔티티 사용을 비활성화하지 않으므로 취약하다고 판정한다.

 

 

● 오탐코드

String xml = "xxe.xml";
DocumentBuilderFactory df = DocumentBuilderFactory.newInstance();
df.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
df.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
DocumentBuilder builder = df.newDocumentBuilder();
Document document = builder.parse(new InputSource(xml));
DOMSource domSource = new DOMSource(document);

DocumentBuilderFactory를 사용하는 예제에서는 3~4라인과 같은 제한 설정을 추가할 수 있다.

 

 

● 오탐코드

String xml = "xxe.xml";
DocumentBuilderFactory df = DocumentBuilderFactory.newInstance();
df.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
df.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
DocumentBuilder builder = df.newDocumentBuilder();
Document document = builder.parse(new InputSource(xml));
DOMSource domSource = new DOMSource(document);

DocumentBuilderFactory를 사용하는 예제에서는 3~4라인과 같은 제한 설정을 추가할 수 있다.

 

 

● 오탐코드

String xml = "xxe.xml";
SaxHandler handler = new SaxHandler();
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
parser.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
parser.parse(xml, handler);

SAXParserFactory 예제의 경우 5~6라인과 같이 제한 설정을 추가할 수 있다.

 

 

● 오탐코드

SchemaFactory factory = SchemaFactory.newInstance("XMLConstants.W3C_XML_SCHEMA_NS_URI");
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
Schema schema = factory.newSchema(Source);

SchemaFactory의 경우 SchemaFactory 또는 Validator 클래스의 setProperty()를 사용하여 제한 설정을 추가할 수 있다.

 

 

● 오탐코드

SchemaFactory factory = SchemaFactory.newInstance("XMLConstants.W3C_XML_SCHEMA_NS_URI");
Schema schema = factory.newSchema();
Validator validator = schema.newValidator();
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");

SchemaFactory의 경우 SchemaFactory 또는 Validator 클래스의 setProperty()를 사용하여 제한 설정을 추가할 수 있다.

 

 

● 오탐코드

XMLInputFactory factory = XMLInputFactory.newFactory();
factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
XMLEventReadereventReader= factory.createXMLEventReader(new FileReader("xxe.xml"));

XMLInputFactory 클래스의 setProperty()를 사용하여 제한 설정을 추가할 수 있다.