모의해킹&웹취약점진단/OWASP TOP 10

XXE 취약점

sheow13 2023. 12. 7. 09:39
728x90

 

개요

- XXE(XML eXternal Entity) 취약점은 XML 데이터를 제대로 검증하지 않아 공격자가 XML 데이터를 가져올 때 시스템 중요 파일 조회 등이 가능한 취약점이다.

-  OWASP TOP 10 2021에서는 A05(Security Misconfiguration)에 해당

 

사전 지식

XML(eXtensible Markup Language) 정의

- XML은 W3C에서 개발된, 다른 특수한 목적을 갖는 마크업 언어를 만드는데 사용하도록 권장하는 다목적 마크업 언어로, HTML처럼 다양한 유형의 응용프로그램에서 문서를 유연하게 전송하고 저장하도록 설계된 언어  

 

 XML 주요 용어

용어 설명 예시
마크업(markup)
내용(content)
XML 문자는 마크업과 내용으로 나뉨
• 마크업 : '<',  '>', '&', ';' 문자로 구성
• 내용 : 마크업이 아닌 문자열
• 마크업 : <date>, &today;
• 내용 : <date>2023-12-06</date>
Tag •  '<'로 시작하여 '>'로 끝나는 마크업 구조 <date>, </section>, <line-break />
Entity •  XML 변수로 '&', ';'로 감싸져 있음 &today;
Element • 루트 또는 하위 요소이며 시작태그와 끝 태그 사이에 값이 저장되어 있음 <date>2023-12-06</date>
Attribute •  이름/값 짝으로 이루어진 마크업 구조로 시작 태그 또는 빈 엘리먼트 태그 속에 위치. <img src="madonna.jpg" alt='Foligno Madonna, by Raphael' />
Declaration •  주로 XML 문서의 첫 번째 라인에 있고 XML의 버전과 인코딩 정보를 나타낸다. <?xml version=“1.0”/encoding=“UTF-8”?>

 

XML DTD

- XML DTD(Document Type Definition)에는 XML 문서의 구조, 포함될 수 있는 데이터 값 유형 및 기타 항목을 정의할 수 있는 선언이 포함되어 있다.

- DTD는 문서 자체 내에 완전히 자체적으로 정의(내부 DTD)하거나, 다른 곳에서 로드하는(외부 DTD) 방식을 모두 사용가능하다.

  ※ 참고 사이트 : https://tcpschool.com/xml/xml_dtd_intro

- DTD 문법

• <!DOCTYPE 루트요소 DTD식별자 [선업 1 선언2 ..]>

- DTD 예제(내부 DTD)

 

- DTD 예제(외부 DTD)

 

 XML Entity

- XML 엔터티는 데이터 자체를 사용하는 대신 XML 문서 내의 데이터 항목을 나타내는 방법으로, XML 언어 사양에는 다양한 엔터티가 내장되어 있다. XML 태그를 나타내는 데 사용되는 메타 문자이므로 일반적으로 데이터 내에 나타낼 때 해당 엔터티를 사용하여 표현해야 한다.

 

 XML external Entity

XML 외부 엔터티는 선언된 DTD 외부에 정의가 있는 사용자 지정 엔터티 유형

- 외부 엔터티 선언에는 키워드를 사용하며 SYSTEM 엔터티를 통해 URL을 지정하는 방식으로 사용

  Ex) <!DOCTYPE foo [ <!ENTITY ext SYSTEM "http://normal-website.com" > ]>

  Ex) <!DOCTYPE foo [ <!ENTITY ext SYSTEM "file:///path/to/file" > ]>

- 외부 엔터티에 대한 입력값 검증을 하지 않을 경우, 공격자는 이를 이용하여 LFI, SSRF 등의 공격등이 가능하다.

- 예시

<?xml version=“1.0” encoding=“UTF-8”?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM “file:///etc/passwd”> ]>
<stockCheck><productId>&xxe;</productId></stockCheck>

 

<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://internal.vulnerable-website.com/"> ]>

 

실습 환경

- PortSwigger Web Academy

 

XXE 실습1

Step 1) PortSwigger  Web Academy에 XXE Lab 접근. XXE 공격이 가능한 경로를 찾던 도중, 상품 제고 체크하는 버튼을 클릭 시, XML 형식으로 요청을 보내는 것을 확인

[ PortSwigger Academy LAB 접근]

 

[상품을 자세히 볼 수 있는 버튼 클릭]
[재고 조회하는 버튼 클릭]

 

 

Step 2) XML 외부 엔터티를 사용하여 시스템 중요 파일인 '/etc/passwd' 파일을 호출 시, 정상 수집 확인

[외부 엔터티 선언 후 실행 했을 때, 시스템 중요 파일 호출된 것을 확인]

 

 

XXE 실습2

Step 1) PortSwigger  Web Academy에 XXE Lab 접근. 이번 LAB은 XML 입력을 파싱하고 응답에서 예상치 않은 값을 반환하는 재고 확인 기능이 존재하며, 서버는 기본 URL인 'http://169.254.169.254' EC2 메타데이터 엔드포인트를 실행하고 있다.

이 엔트포인트를 사용하여 인스턴스에 대한 데이터를 검색할 수 있는 점을 이용하여 XXE 취약점을 이용해 EC2 메타데이터 엔드포인트에서 서버의 IAM 비밀 엑세스 키를 받아오는 것이 실습 목표.

임의 제품을 클릭 후, 상품 제고 체크하는 버튼을 클릭 시, XML 형식으로 요청을 보내는 것을 확인

[ PortSwigger가 제공하는 LAB 접근]
[상품을 자세히 볼 수 있는 버튼 클릭]

 

[재고 조회하는 버튼 클릭]

 

Step 2) burp suite을 통해 외부 엔터티에 EC2 엔드포인트 경로를 호출 시, 400 Error 코드와 함께 "Invalid product ID" 값이 지정되는 것으로 확인.

표출되는 에러코드 값을 Entity 값으로 추가하여 요청 시, "http://169.254.169.254/latest/meta-data/iam/security-credentials/admin" 경로에서 IAM 비밀 키 획득 성공 

 

[외부 Entity 선언 후 Forwarding 진행 시, 유효하지 않는 ID값으로 에러코드 발생]

 

[응답에서 확인되는 ID값을 외부 Entity에 입력 시, IAM 비밀 키 획득 성공]

 

 

XXE 대응 방안

- 시큐어코딩을 통해 로컬 정적 DTD를 사용하도록 설정하고, 외부에서 전송된 XML 문서에 포함된 DTD를 완전하게 비활성화 진행

from xml.sax import make_parser
from xml.sax.handler import feature_external_ges
from xml.dom.pulldom import parseString, START_ELEMENT
from django.shortcuts import render
from .model import comments

def get_xml(request):
      if request.method == "GET":
         data = comments.objects.all()
         com = data[0].comment
         return render(request, '/xml_view.html', {'com';com})

      elif request.method == "POST":
         parser = make_parser()
         parser.setFeature(feature_external_ges, False)
         doc = parseString(request.body.decode('utf-8'), parser=parser)
         for event, node in doc:
            if event == START_ELEMENT and node.tagName == "foo":
               doc.expandNode(node)
               text = node.toxml()
         comments.objects.filter(id=1).update(comment=txt);
         return render(request, '/xml_view.html')