XML 파서를 사용하기 위해 구글링을 하다가 'XXE Injection Attack(XML External Entity Injection 공격)'에 대해서 알게되었다. XXE Injection 공격은 OWASP Top 10 - 2017에도 선정된 웹 애플리케이션 취약점이다. (OWASP는 The Open Web Application Security Project의 약자로 웹 애플리케이션에서 흔히 발생할 수 있는 대표적인 취약점을 모아서 4년마다 발표하는 프로젝트다.)
1. XML Entity
XML 엔티티(Entity)는 반복적으로 나오는 문자열이나 특별처리가 필요한 특수 문자를 XML 문서에서 사용하기 위해 미리 정의해놓고 사용하는 개체다. XML Entity는 앰퍼샌드('&') 문자로 시작하고 세미콜론(';') 문자로 끝난다.
예를 들어
<node>You&I</node>
위 XML에서 "&" 부분이 XML Entity에 해당하며 다음과 같이 해석된다.
<node>You&I</node>
앰퍼샌드 문자('&')는 XML에서 특수한 역할을 하기 때문에 앰퍼샌드 문자 자체는 "&"라는 XML 엔티티를 통해서 사용한다.
비슷한 이유로 XML에는 5개의 기본 엔티티가 정의되어 있다.
- <
- >
- &
- "
- '
이들은 각각 다음 문자로 치환된다.
- <
- >
- &
- "
- '
다섯개의 기본 엔티티를 제외한 다른 엔티티들은 DTD에서 선언되어야 한다. 엔티티를 선언하는 DTD는 XML 파일 내부에 있을 수도 있고 다른 파일에 있을 수도 있다. (다른 파일에 있는 경우 불러와서 포함시켜야한다.)
다음은 "user"라는 XML 엔티티를 정의해서 사용하는 XML 문서다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE node [
<!ENTITY user "Dave">
]>
<node>&user;</node>
XML 엔티티는 DOCTYPE 태그에 ENTITY 태그를 통해 정의할 수 있다. 위 문서에서 "&user;" 엔티티는 "Dave"라는 문자열로 치환된다.
XML 엔티티는 위에서 봤던 것처럼 XML 내부에 선언될 수도 있고, 외부에 선언한 다음 불러들일 수도 있다. (여러 XML에서 공통적으로 사용되는 엔티티들을 하나의 파일로 모아서 여러 XML에 사용하는 식으로 재사용 할 수 있다.)
XML 엔티티는 XML 내부 DTD에서 다음과 같이 선언할 수 있다.
<!ENTITY 엔티티이름 "엔티티값">
이렇게 선언된 엔티티는
<node>&엔티티이름;</node>
처럼 앰퍼샌드('&')로 시작하는 엔티티로 사용할 수 있다. 이 XML은
<node>엔티티값</node>
으로 해석된다.
반면 XML 파일 외부에 선언된 엔티티는 다음과 같이 불러올 수 있다.
<!ENTITY 엔티티이름 SYSTEM "http://web.com/entity.dtd">
<!ENTITY 엔티티이름2 SYSTEM "file:///temp/entity.dtd">
파일이나 외부 URL을 입력하면 그에 해당하는 내용이 엔티티의 값으로 사용된다. 외부 엔티티를 사용하는 방법은 내부 엔티티와 동일하게 앰퍼샌드와 세미콜론을 사용하면된다.
2. XML External Entity Injection 취약점 PoC
XXE Injection 공격(XML External Entity Injection Attack)은 외부 엔티티(External Entity) 정의의 보안 취약점을 이용한 공격이다. XXE Injection 공격은 다음과 같이 재현해 볼 수 있다.
사용자로부터 XML을 입력받아서 파싱한 다음 특정 태그 내용을 화면으로 출력하는 애플리케이션을 작성해보자.
import java.io.File;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
public class XXEInjection {
public static void main(String []args) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File("/tmp/test.xml"));
Node node = doc.getDocumentElement().getFirstChild();
System.out.println(node.getNodeValue());
}
}
"/tmp/test.xml" 파일을 읽어서 파싱한 다음 DOM 트리의 특정 태그의 내용을 출력하는 프로그램이다. 프로그램 코드를 보면 아무런 문제가 없어보인다.
"/tmp/test.xml" 파일을 다음과 같이 작성해보자.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE node [
<!ENTITY passwd SYSTEM "file:///etc/passwd">
]>
<node>&passwd;</node>
"passwd"라는 엔티티를 정의했다. 문제는 "/etc/passwd" 파일의 내용을 "&passwd;" 엔티티에 할당했다는데에 있다. 만약 네트워크를 통해 XML을 전달받고 파싱한 다음 결과를 네트워크로 다시 전송해주는 경우를 생각해보자. 이 애플리케이션이 동작하는 서버의 passwd 파일의 내용이 네트워크를 통해 전송될 수 있다. (물론 이 애플리케이션이 passwd 파일에 접근할 권한이 있는 경우)
3. XML External Entity Injection 대응방안
OWASP에서는 깃허브 리파지토리를 통해 해결 방안을 가이드하고 있다. (링크 : OWASP 깃허브 - XML_External_Entity_Prevention_Cheat_Sheet)
Java 파서의 경우 'DOCTYPE' 태그를 사용하지 못하도록 하거나, External Entity를 Expand하지 않도록 설정하는 예제 코드를 볼 수 있다. 아마도 오래전에 작성된 XML 파서 코드 모듈들은 이 옵션을 설정하지 않아서 위험에 노출되어 있을지도 모르겠다.
Reference
- https://www.owasp.org/index.php/Top_10-2017_A4-XML_External_Entities_(XXE)
댓글