Research & Technique

Adobe Commerce XXE 취약점(CVE-2024-34102)

■ 취약점 개요

Magento는 2008년 3월 31일에 처음 출시된 PHP 기반의 오픈소스 전자상거래 플랫폼이다. 2018년 5월에 Adobe에 인수된 이후, Magento Commerce Enterprise Edition은 Adboe Commerce에 흡수되어 Adobe Commerce로 리브랜딩 [1] 된다. 반면, Magento Community Edition은 여전히 Magento Open Source에 기반한 오픈 소스 전자상거래 플랫폼으로 운영되고 있다. OSINT 검색 엔진을 통해 인터넷 상에 공개된 Adobe Commerce를 조회한 결과, 2024년 8월 9일 기준 미국과 독일을 비롯한 수많은 국가의 4만여 개 사이트에서 Adobe Commerce를 전자상거래 플랫폼으로 사용 중이다.

출처: fofa.info

그림 1. Adobe Commerce 사용 통계

2024년 6월 13일, Adobe Commerce에서 XXE 취약점(CVE-2024-34102)이 공개됐다. 해당 취약점은 REST API [2] 가 JSON 데이터를 객체로 변환하는 과정에서 필터링 로직이 미흡해 악의적인 XML 구문 해석을 통한 악성 행위를 할 수 있기 때문에 발생한다. 공격자는 해당 취약점을 통해 서버 내 중요정보를 탈취할 수 있어 주의가 필요하다.

2024년 7월 13일, Adobe에서는 해당 취약점을 통해 임의 명령 실행, 보안 기능 우회, 권한 상승 공격을 행할 수 있다는 위험을 알렸다. 또한, 판매자를 대상으로 CVE-2024-34102가 제한적으로 악용되었다는 사실을 발견하고 이를 공지했다.

■ 공격 시나리오

그림 2. CVE-2024-34102 공격 시나리오

■ 영향받는 소프트웨어 버전

CVE-2024-34102에 취약한 소프트웨어 버전은 다음과 같다.

■ 테스트 환경 구성 정보

테스트 환경을 구축해 CVE-2024-34102의 동작 과정을 살펴본다.

■ 취약점 테스트

Step1. 환경 구성

피해자 PC에 CVE-2024-34102 취약점이 존재하는 Adobe Commerce를 설치한다. Composer install 을 통해서 설치된 Adobe Commerce인 경우, 설치된 경로 내 최상위 경로의 composer.lock 파일에 사용 중인 애플리케이션의 버전 정보가 기입되어 있다.

해당 환경의 경우 2.4.7 버전을 사용 중이기 때문에 취약한 환경임을 확인할 수 있다.

그림 3. 취약 Adobe Commerce 정보 확인

Step2. 취약점 테스트

우선, 공격자는 악성 XML을 응답하는 서버를 구축한다. 테스트 서버는 SSRF 취약점을 확인하기 위해 사용하는 SSRFUtility를 이용하여 임시로 구축할 수 있다.

먼저, 아래의 New Instance 버튼을 눌러 새로운 인스턴스를 발급받는다.

그림 4. SSRFUtility 인스턴스 발급

발급받은 인스턴스에 다음과 같은 악성 XML payload를 반환하도록 설정한다.

해당 설정은 아래와 같이 Customize HTTP Response 기능을 통해 /etc/hosts 파일을 읽는 악성 XML payload를 반환하도록 지정할 수 있다.

그림 5. 악성 XML 반환 설정

이제 취약한 Adobe Commerce 서버에 CVE-2024-34102 취약점을 이용해 다음과 같은 패킷을 전송한다.

해당 취약한 서버에서 다음과 같은 응답을 확인할 수 있다.

그림 6. CVE-2024-34102 취약점으로 인한 /etc/hosts 파일 유출

위 과정을 자동화한 CVE-2024-34102 취약점 테스트 PoC는 아래 링크에서 확인할 수 있다.

그림 7. CVE-2024-34102 PoC 실행 후 /etc/hosts 파일 유출 확인

■ 취약점 상세 분석

취약점 상세 분석에서는 CVE-2024-34102 취약점이 발생하는 원리를 순차적으로 설명한다. Step 1 에서는 접근 권한 및 클래스 설정 파일인 webapi.xml 과 di.xml 분석을 통해 인증없이 접근 가능한 경로에서 어떤 메서드가 호출되는지 분석한다. Step 2 에서 HTTP body의 JSON 데이터가 객체로 역직렬화 되는 과정을 분석한다. 마지막으로 Step 3 에서는 Adobe Commerce에서 발생하는 XXE(XML External Entity Injection) 취약점을 다루고 위 역직렬화 과정 [3] 에서 호출되는 클래스 추적을 통해 XXE 취약점이 어떻게 발생하는지 분석한다.

Step 1. 인증없이 접근가능한 URL 탐색 및 실행 추적

Magento2는 기능을 사용할 때 UI와 REST API를 통해 접근할 수 있다. 특히, 인증없이 접근할 수 있는 REST API를 우선적으로 탐색해 취약 지점을 살펴볼 수 있다.

1) webapi.xml

Magento2에서 REST API의 접근에 대한 상세 설정은 각 모듈 내의 webapi.xml 파일에 명시한다. Magento를 설치한 경로 내 vendor/magento/module-quote/etc/webapi.xml 파일을 참고하면 다음과 같은 xml 파일의 일부분을 확인할 수 있다.

그림 8. webapi.xml 파일 일부분

위 webapi.xml 파일의 각 노드는 다음을 뜻한다.

①route : API를 호출할 URL을 지정, 어떤 URL에 어떤 메서드로 접근할 때 API를 호출할지 지정한다.

②service : API의 호출될 클래스와 메서드를 지정할 수 있다. 위 예시의 경우 estimatedByExtendedAddress라는 메서드에 cartId가 파라미터로 전달되어 호출된다.

③resources : API를 호출할 권한을 명시한다. ref의 속성값 중, anonymous는 모든 유저, self는 고객을 뜻한다. 위 예시의 경우 별도 인증없이 모든 유저가 접근 가능하다.

resources 노드의 ref 속성값이 anonymous인 webapi.xml을 조사한 결과, 대표적으로 다음 두 경로가 별도의 인증 과정이 필요 없음을 확인할 수 있다.

2) di.xml

Magento2에서 webapi.xml의 service노드에 명시된 클래스는 그 자체의 class명이 호출되지 않는다. di.xml은 인스턴스화 [4] 과정에서 특정 클래스에 대한 설정(preference)을 정의하는 역할을 한다.

위와 마찬가지로 Magento2를 설치한 경로 내 vendor/magento/module-quote/etc/di.xml 파일을 참고하면 다음과 같은 xml파일의 일부분을 확인할 수 있다.

그림 9. di.xml 파일 일부분

1)의 webapi.xml에 따라 /rest/V1/guest-carts/eqst-test/estimate-shipping-methods URL 경로 접근 시 내부에서 호출되는 클래스는 Magento\Quote\Api\Data\AddressInterface지만, di.xml에 따라 해당 클래스의 호출은 Magento\Quote\Model\Quote\Address클래스 호출로 아래와 같이 대체된다.

그림 10. getPreference 함수에서 di.xml에 따라 대체되는 클래스명

3) 호출 메서드 분석

(1) /rest/V1/guest-carts/:cartId/estimate-shipping-methods

1)에서 설명한 인증없이 접근 가능한 경로 중 estimate-shipping-methods 경로에 대한 webapi.xml의 설정은 다음과 같다.

그림 11. estimate-shipping-methods 경로에 대한 webapi.xml 설정

2)에서 설명한 webapi.xml에서 호출되는 클래스의 di.xml 설정 정보는 다음과 같다.

그림 12. 위 webapi.xml 설정에 따른 di.xml 설정 정보

위 두 xml파일 설정으로 /rest/V1/guest-carts/:cartId/estimate-shipping-methods 경로에 HTTP 요청을 보낼 시, Magento\Quote\Model\GuestCart\GuestShippingMethodManagement 클래스의 estimatedByExtendedAddress 메서드가 호출된다. estimatedByExtendedAddress 메서드의 소스코드는 다음과 같다.

그림 13. estimatedByExtendedAddress 메서드

HTTP 요청을 보낸 뒤, 내가 보낸 HTTP body 값이 AddressInterface 클래스 형태의 객체로 변환되어 인수로 들어가는 것을 확인할 수 있다.

(2) /rest/V1/guest-carts/:cartId/billing-address

1)에서 설명한 인증없이 접근 가능한 경로 중 billing-address 경로에 대한 webapi.xml의 설정은 다음과 같다.

그림 14. billing-address 경로에 대한 webapi.xml 설정

2)에서 설명한 webapi.xml에서 호출되는 클래스의 di.xml 설정 정보는 다음과 같다.

그림 15. 위 webapi.xml 설정에 따른 di.xml 설정 정보

위 두 xml파일 설정으로 /rest/V1/guest-carts/:cartId/billing-address 경로에 HTTP 요청을 보낼 시, Magento\Quote\Model\GuestCart\GuestBillingAddressManagement 클래스의 assign 메서드가 호출된다. assign 메서드의 소스코드는 다음과 같다.

그림 16. assign 메서드

HTTP 요청을 보낸 뒤, 내가 보낸 HTTP body 값이 \Magento\Quote\Api\Data \AddressInterface 클래스 형태의 객체로 변환되어 인수로 들어가는 것을 확인할 수 있다. 두 URL 모두 객체 형태로 인수를 받으므로, HTTP Body 요청은 역직렬화 과정을 거쳐 객체를 구성한 뒤, 전달될 것을 예상할 수 있다.

Step 2. 역직렬화 과정

본 취약점을 이해하기 위해서 JSON 형태의 데이터를 HTTP body로 요청을 보낼 때, Magento2 내에서 어떻게 역직렬화 되는지에 대한 상세한 이해가 필요하다. 설치 경로 내에 위치한 vendor/magento/Framework/Webapi/ServiceInputProcessor.php 내 _createFromArray 메서드에서 해당 과정을 일부 수행한다. 해당 메서드의 소스코드는 다음과 같다.

그림 17. _createFromArray 메서드

역직렬화 과정 중 위 _createFromArray 메서드 내의 getConstructorData 메서드는 $data 내 필드에서 $className 클래스의 생성자 인수가 있는지 탐색 후 이를 array형으로 정리해 반환하는 역할을 한다.

그림 18. getConstructorData 메서드

Step1에서 언급한 두 API 경로의 경우, HTTP body의 JSON 필드를 Magento\Quote\Model\Quote\Address클래스(이하 Address 클래스) 형식에 맞게 역직렬화 하기 때문에, getConstructorData 메서드를 거칠 Address 클래스의 생성자 인수들을 확인해야 한다. 해당 클래스의 소스코드를 확인해보면, 생성자가 다음과 같은 37개의 인수들을 갖고 있는 것을 확인할 수 있다.

그림 19. Address 클래스 소스코드 내 생성자(__construct) 인수 확인

따라서 Address 클래스가 호출될 때, 위 코드 내 생성자의 인수에 존재하지 않는 JSON 필드명과 존재하는 JSON 필드명을 만들어 각각 요청을 보내면, 어떤 식으로 HTTP body의 JSON 역직렬화 과정이 달라지는지 확인할 수 있다.

1) JSON 필드가 클래스의 생성자 인수 이름과 일치하지 않는 경우

_createFromArray 메서드 내의 getConstructor 메서드를 통해 탐색한 JSON 필드에 클래스 생성자 인수 이름과 일치하는 이름이 없을 경우, _createFromArray 메서드는 JSON 필드명의 setter(set+필드명) 메서드를 탐색하고 이를 실행한다. 이는 다음 HTTP 요청을 전송해서 확인할 수 있다.

위 과정에 따라 생성자의 Address 클래스 내 인수에는 존재하지 않지만 setter로 존재하는 setRegion 메서드를 탐색하고 이를 실행하는 것을 확인할 수 있다.

그림 20. Address 클래스 내 setRegion 메서드 확인

그림 21. “set” + JSON 필드명 메서드를 탐색 후 이를 실행

2) JSON 필드가 클래스의 생성자 인수 이름과 일치하는 경우

_createFromArray 메서드 내 getConstructorData 메서드에서 탐색 중인 JSON 필드명이 클래스 생성자 인수 이름과 일치하는 것을 확인하면, 해당 데이터를 convertValue 메서드로 데이터와 데이터 타입을 넘겨준다. convertValue 메서드의 소스코드는 다음과 같다.

그림 22. convertValue 메서드

convertValue 메서드에서 $type에 string과 int와 같은 자료형이 들어갈 경우는 두번째 분기를 거쳐 _createFromArray 메서드가 호출없이 반환된다. 반면 convertValue에 인수로 $type이 클래스로 전달될 경우, $type은 array 나 string, int, float, double, boolean과 같은 간단한 타입이 아니므로 processComplexTypes 메서드로 $data와 $type을 인수로 넘겨주게 된다. 인수를 넘겨준 processComplexTypes 메서드의 소스코드는 다음과 같다.

그림 23. processComplexTypes 메서드

$type으로 클래스가 전달된다면 Array 타입이 아니므로 _createFromArray 메서드가 다시 호출되어 재귀적으로 _createFromArray 메서드가 호출되는 과정이 반복된다. 해당 과정을 도식화하면 아래와 같다.

그림 24. _createFromArray 재귀호출 과정

재귀적으로 호출되던 _createFromArray 메서드는 convertValue가 string형이나 int형을 $type 인수로 받으면, _createFromArray 메서드 호출이 아닌 $data를 getConstructorData 메서드 내의 $res array에 저장 후 이를 _createFromArray 메서드 내 $constructorArgs로 반환하게 되므로, _createFromArray의 재귀 호출이 끝나게 된다. _createFromArray의 재귀 호출이 끝나게 되면, _createFromArray 메서드 내에서 $constructorArgs를 반환하며, 해당 변수를 인수로 넘긴 후 $className에 따른 객체를 생성한다.

그림 25. _createFromArray 재귀호출 종료 후 객체 생성 과정

Step 3. XXE(XML External Entity Injection) 취약점 발생

1) XXE(XML External Entity Injection) 취약점

XXE 취약점을 이해하기 위해서는 XML에 관한 기본적인 이해가 필요하다. XML은 eXtensible Markup Language의 약자로 데이터 저장 및 전송을 위해 고안된 언어다. XML 이해에 필요한 주요 용어는 다음과 같다

XML에서는 아래 표와 같이 예약되어 있는 다섯 개의 특별한 기호가 있다. 예약된 기호를 XML 문서에서 사용하면, XML 명세 상 다른 의미로 해석하게 된다. 이처럼 예약된 기호를 기존에 사용하던 문자와 같이 사용하기 위해서 만든 것을 엔티티(entity)라고 한다.

DTD(XML document type definition)는 XML 문서의 구조, 데이터 값의 유형 및 여러 항목을 정의할 수 있다. DTD는 XML 문서 시작 부분에 있는 DOCTYPE 요소 내에 선언된다. DTD는 문서 자체 내에 선언하거나 외부 파일 형태로 정의할 수 있다.

외부 파일 형태로 정의하는 외부 엔티티의 선언은 SYSTEM 키워드를 사용하며, 엔티티 값을 로드해야 하는 URL을 지정한다. 예시는 아래와 같다.

로드 URL은 시스템 내부에서 사용하는 다양한 프로토콜을 사용할 수 있다. file:/, php wrapper, jar: 등이 그 대표적 예시다. 서버 측에서 XML구문 해석 결과를 출력하면, 아래와 같이 php wrapper기능을 활용해 특정 파일을 엔티티로 불러온 뒤 출력할 수 있다.

서버 측에서 XML 구문 해석 결과를 출력하지 않는다면, 외부에서 악성 DTD파일을 호스팅해 내부 파일 정보를 유출할 수 있다. /etc/hosts 파일을 유출하는 파일 XXE 요청은 다음과 같다.

위 XML 구문에 따르면 %sp 외부 엔티티가 정의되며, 이는 외부에서 악성 DTD를 불러오게 된다. 이에 따른 외부에서 호스팅하는 악성 DTD파일의 예시는 다음과 같다.

%data는 /etc/hosts 파일을 php wrapper 기능을 활용해 Base64 형태로 인코딩한다. %param1은 exfil 엔티티를 정의하며, 위 %data의 값을 URL 파라미터로 지정해 공격자 서버로 유출하게 된다.

2) XXE(XML External Entity Injection) 공격 취약 지점

Magento2 내 XXE 취약점을 이용할 수 있는 클래스로 XML 구문을 해석하는 \SimpleXMLElement 클래스가 있다. Step2에서 언급한 바와 같이 _createFromArray 메서드에서 인수로 전달된 클래스명($className)으로부터 생성자 인수를 재귀적으로 호출하므로, Address 생성자 인수로부터 XML 구문을 해석하는 \SimpleXMLElement 클래스가 도달할 수 있는지 살펴, 취약한지 확인할 수 있다.

Address 생성자 인수들을 추적한 결과, 아래와 같이 클래스가 재귀적으로 호출됨을 확인할 수 있다. 마지막 클래스 호출은 Magento\Quote\Model\Quote\Address\Total\Collector 내 SourceData 타입이 \Magento\Framework\Simplexml\Element인 이유로 호출된다.

그림 26. sourceData 타입이 \Magento\Framework\Simplexml\Element 임을 명시한 소스코드

이는 디버거를 통해 확인할 수 있다.

그림 27. sourceData에서 \Magento\Framework\Simplexml\Element 클래스 타입 호출 확인

해당 클래스는 SimpleXMLElement 클래스를 상속받고 있으므로, SimpleXMLElement 클래스와 생성자 용법은 동일함을 알 수 있다.

그림 28. \Magento\Framework\Simplexml\Element 소스코드 내 상속 클래스 확인

php 공식 문서 상의 simpleXMLElement는 다음과 같은 인수들을 생성자로 받는다.

그림 29. php 공식문서 상 simpleXMLElement 클래스 생성자 인수

그림 30. 디버거로 확인한 simpleXMLElement 클래스 생성자 인수

simpleXMLElement 클래스 생성자 중 $data에 악의적인 XML 구문을 전달하면, XXE 취약점이 발생한다. 또한, $options에서는 내·외부 엔티티 치환 옵션을 뜻하는 LIBXML_NOENT(2), 엔티티 재귀 및 노드 크기에 제한을 두지 않는 LIBXML_PARSEHUGE(524288) 옵션을 통해 524290(2+524288)을 설정 값으로 보낼 수 있다.

3) XXE(XML External Entity Injection) 공격 exploit

2) 에서 언급한 클래스 순서대로 호출 후, sourceData로 악의적인 XML 구문을 전달하면 공격이 가능하다. XXE 공격을 통해 /etc/hosts 파일을 유출하는 payload는 다음과 같다.

이후 base64로 인코딩된 /etc/hosts/ 정보를 탈취한 것을 확인할 수 있다.

그림 31. XXE 공격을 통한 /etc/hosts 정보 탈취

4) 공격 영향

(1) 운영자 권한 API 사용

API 인증에 사용되는 JWT의 서명 키가 app/etc/env.php 파일의 crypt 내 key 값으로 생성되기 때문에, admin 권한으로 API 기능을 활용할 수 있는 위험이 있다.

(2) CVE-2024-2961 취약점 체인

CVE-2024-2961 취약점은 GNU C Library인 glibc에서 발생하는 취약점이다. 해당 라이브러리 내 iconv 함수를 사용할 때 발생하는데, ISO-2022-CN-EXT를 사용할 수 있는 환경에서 해당 언어셋으로 문자열을 변환할 때 출력 버퍼를 오버 플로우할 수 있는 취약점이다. 해당 취약점으로 php wrapper의 php://filter를 활용하면, 애플리케이션의 충돌을 발생시키거나 인접 변수를 덮어쓰는 등의 행위가 가능하다.

■ 대응 방안

CVE-2024-34102가 발표된 6월 11일, Magento2에서 해당 취약점을 패치한 2.4.7-p1 버전을 발표했다. 발표된 소스코드는 해당 Release에서 다운로드 받을 수 있다.

패치 이후 변경 내역이 있는 소스코드를 비교하면, 취약점이 발생했던 lib/internal/Magento/Framework/Webapi 내 _createFromArray 메서드에서 다음과 같은 검증 로직이 추가된 것을 확인할 수 있다.

그림 32. 2.4.7-p1 패치에서 추가된 _createFromArray 메서드 검증 로직

이 검증 로직은 XML의 구문을 해석할 수 있는 \SimpleXMLElement, \DOMElement를 상속받는 클래스명을 $className 인수로 받으면 예외 처리하게 만들어, “Invalid data type”을 반환하도록 패치했다. 패치 이후 동일 공격 구문을 전송하면, 다음과 같은 오류 구문과 함께 공격이 실패하는 것을 확인할 수 있다.

그림 33. 2.4.7-p1 패치 이후 공격이 실패하는 것을 확인

패치 작업은 다음 과정과 같이 수행한다.

패치 파일과 상세 과정은 아래에서 확인할 수 있다.

취약한 버전의 Adobe Commerce 사용자는 위 작업 과정에 따라 패치를 수행할 것을 권장한다.



■ 참고 사이트

• Magento Is Now Adobe Commerce : https://business.adobe.com/blog/the-latest/magento-is-now-part-of-adobe

• Magento Community vs. Enterprise Edition Comparison : https://www.mgt-commerce.com/blog/magento-community-vs-enterprise/

• Security update available for Adobe Commerce | APSB24-40 : https://helpx.adobe.com/security/products/magento/apsb24-40.html

• spacewasp github : https://github.com/spacewasp/public_docs/blob/main/CVE-2024-34102.md

• why nested deserialization is harmful magento xxe cve-2024-34102 : https://www.assetnote.io/resources/research/why-nested-deserialization-is-harmful-magento-xxe-cve-2024-34102

• ComsmicSting: critical unauthenticated XXE vulnerability in Adobe Commerce and Magento (CVE-2024-34102) : https://www.vicarius.io/vsociety/posts/cosmicsting-critical-unauthenticated-xxe-vulnerability-in-adobe-commerce-and-magento-cve-2024-34102

• Magento2 how to create custom webapi : https://magento.stackexchange.com/questions/280966/magento-2-how-to-create-custom-webapi

• Magento2 what case I use di.xml and how to use di.xml for module : https://magento.stackexchange.com/questions/111845/magento-2-what-case-i-use-di-xml-and-how-to-use-di-xml-for-module

• php manual simpleXMLElement : https://www.php.net/manual/en/class.simplexmlelement.php

• php manual libxml constants : https://www.php.net/manual/en/libxml.constants.php#constant.libxml-schema-create

• Magento2 github : https://github.com/magento/magento2

• RFC3470 Guidelines for the Use of Extensible Markup Language(XML) within IETF protocols : https://datatracker.ietf.org/doc/html/rfc3470#section-2

• XML external entity (XXE) Injection : https://portswigger.net/web-security/xxe




[1] 리브랜딩(Rebranding): 기존 브랜드에 새로운 이름, 용어, 심볼, 디자인, 개념 또는 이들의 조합으로 차별화된 정체성을 나타내려는 마케팅 전략

[2] REST API(Representational State Transfer API): HTTP 요청을 통해 통신하여 리소스 내에서 레코드를 생성하고 읽기, 업데이트 및 삭제(CRUD)와 같은 표준 데이터베이스 기능을 수행하는 API. 예를 들어, GET은 검색, POST는 생성, PUT은 업데이트, DELETE는 삭제 기능을 수행함

[3] 역직렬화(deserialization): 일련의 바이트로부터 데이터 구조를 추출하는 작업

[4] 인스턴스화(instantiate): 클래스로부터 객체를 만드는 과정