溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Confluence 文件讀取漏洞(CVE-2019-3394)分析

發(fā)布時(shí)間:2020-08-10 00:01:44 來源:ITPUB博客 閱讀:158 作者:酷酷的曉得哥 欄目:網(wǎng)絡(luò)安全

作者: Badcode@知道創(chuàng)宇404實(shí)驗(yàn)室  
日期: 2019/08/29  
英文版本:   https://paper.seebug.org/1026/

前言

下午 @fnmsd 師傅發(fā)了個(gè)   Confluence  的預(yù)警給我,我看了下補(bǔ)丁,復(fù)現(xiàn)了這個(gè)漏洞,本篇文章記錄下這個(gè)漏洞的應(yīng)急過程。

Confluence 文件讀取漏洞(CVE-2019-3394)分析

看下描述,Confluence Server 和 Data Center 在頁面導(dǎo)出功能中存在本地文件泄露漏洞:具有“添加頁面”空間權(quán)限的遠(yuǎn)程攻擊者,能夠讀取   <install-directory>/confluence/WEB-INF/  目錄下的任意文件。該目錄可能包含用于與其他服務(wù)集成的配置文件,可能會(huì)泄漏認(rèn)證憑據(jù),例如 LDAP 認(rèn)證憑據(jù)或其他敏感信息。和之前應(yīng)急過的一個(gè)漏洞一樣,跳不出WEB目錄,因?yàn)?confluence 的 web 目錄和 data 目錄一般是分開的,用戶的配置一般保存在 data 目錄,所以感覺危害有限。

漏洞影響

  • 6.1.0 <= version < 6.6.16
  • 6.7.0 <= version < 6.13.7
  • 6.14.0 <= version < 6.15.8

補(bǔ)丁對比

看到漏洞描述,觸發(fā)點(diǎn)是在導(dǎo)出 Word 操作上,先找到頁面的這個(gè)功能。

Confluence 文件讀取漏洞(CVE-2019-3394)分析

接著看下代碼層面,補(bǔ)丁是補(bǔ)在什么地方。

6.13.7是6.13.x的最新版,所以我下載了6.13.6和6.13.7來對比。

去除一些版本號(hào)變動(dòng)的干擾,把目光放在 confluence-6.13.x.jar上,比對一下

Confluence 文件讀取漏洞(CVE-2019-3394)分析

對比兩個(gè)jar包,看到有個(gè) importexport 目錄里面有內(nèi)容變化了,結(jié)合之前的漏洞描述,是由于導(dǎo)出Word觸發(fā)的漏洞,所以補(bǔ)丁大概率在這里。 importexport 目錄下面有個(gè) PackageResourceManager  發(fā)生了變化,解開來對比一下。

Confluence 文件讀取漏洞(CVE-2019-3394)分析

看到關(guān)鍵函數(shù) getResourceReaderresource = this.resourceAccessor.getResource(relativePath);,看起來就是獲取文件資源的, relativePath的值是 /WEB-INF拼接 resourcePath.substring(resourcePath.indexOf(BUNDLE_PLUGIN_PATH_REQUEST_PREFIX))而來的,而 resourcePath是外部傳入的,看到這里,也能大概猜出來了,應(yīng)該是 resourcePath可控,拼接 /WEB-INF,然后調(diào)用 getResource讀取文件了。

流程分析

找到了漏洞最終的觸發(fā)點(diǎn),接下來就是找到觸發(fā)點(diǎn)的路徑了。之后我試著在頁面插入各種東西,然后導(dǎo)出 Word,嘗試著跳到這個(gè)地方,都失敗了。最后我在跟蹤插入圖片時(shí)發(fā)現(xiàn)跳到了相近的地方,最后通過構(gòu)造圖片鏈接成功跳到觸發(fā)點(diǎn)。

首先看到 com.atlassian.confluence.servlet.ExportWordPageServerservice方法。

    public void service(SpringManagedServlet springManagedServlet, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String pageIdParameter = request.getParameter("pageId");
        Long pageId = null;
        if (pageIdParameter != null) {
            try {
                pageId = Long.parseLong(pageIdParameter);
            } catch (NumberFormatException var7) {
                response.sendError(404, "Page not found: " + pageId);
            }
        } else {
            response.sendError(404, "A valid page id was not specified");
        }
        if (pageId != null) {
            AbstractPage page = this.pageManager.getAbstractPage(pageId);
            if (this.permissionManager.hasPermission(AuthenticatedUserThreadLocal.get(), Permission.VIEW, page)) {
                if (page != null && page.isCurrent()) {
                    this.outputWordDocument(page, request, response);
                } else {
                    response.sendError(404);
                }
                ......
    }

在導(dǎo)出 Word 的時(shí)候,首先會(huì)獲取到被導(dǎo)出頁面的 pageId,之后獲取頁面的內(nèi)容,接著判斷是否有查看權(quán)限,跟進(jìn) this.outputWordDocument

    private void outputWordDocument(AbstractPage page, HttpServletRequest request, HttpServletResponse response) throws IOException {
        ......
        try {
            ServletActionContext.setRequest(request);
            ServletActionContext.setResponse(response);
            String renderedContent = this.viewBodyTypeAwareRenderer.render(page, new DefaultConversionContext(context));
            Map<String, DataSource> imagesToDatasourceMap = this.extractImagesFromPage(renderedContent);
            renderedContent = this.transformRenderedContent(imagesToDatasourceMap, renderedContent);
            Map<String, Object> paramMap = new HashMap();
            paramMap.put("bootstrapManager", this.bootstrapManager);
            paramMap.put("page", page);
            paramMap.put("pixelsPerInch", 72);
            paramMap.put("renderedPageContent", new HtmlFragment(renderedContent));
            String renderedTemplate = VelocityUtils.getRenderedTemplate("/pages/exportword.vm", paramMap);
            MimeMessage mhtmlOutput = this.constructMimeMessage(renderedTemplate, imagesToDatasourceMap.values());
            mhtmlOutput.writeTo(response.getOutputStream());
            ......

前面會(huì)設(shè)置一些 header 之類的,然后將頁面的內(nèi)容渲染,返回 renderedContent,之后交給 this.extractImagesFromPage處理

    private Map<String, DataSource> extractImagesFromPage(String renderedHtml) throws XMLStreamException, XhtmlException {
        Map<String, DataSource> imagesToDatasourceMap = new HashMap();
        Iterator var3 = this.excerpter.extractImageSrc(renderedHtml, MAX_EMBEDDED_IMAGES).iterator();
        while(var3.hasNext()) {
            String imgSrc = (String)var3.next();
            try {
                if (!imagesToDatasourceMap.containsKey(imgSrc)) {
                    InputStream inputStream = this.createInputStreamFromRelativeUrl(imgSrc);
                    if (inputStream != null) {
                        ByteArrayDataSource datasource = new ByteArrayDataSource(inputStream, this.mimetypesFileTypeMap.getContentType(imgSrc));
                        datasource.setName(DigestUtils.md5Hex(imgSrc));
                        imagesToDatasourceMap.put(imgSrc, datasource);
                        ......

這個(gè)函數(shù)的功能是提取頁面中的圖片,當(dāng)被導(dǎo)出的頁面包含圖片時(shí),將圖片的鏈接提取出來,交給 this.createInputStreamFromRelativeUrl處理

    private InputStream createInputStreamFromRelativeUrl(String uri) {
        if (uri.startsWith("file:")) {
            return null;
        } else {
            Matcher matcher = RESOURCE_PATH_PATTERN.matcher(uri);
            String relativeUri = matcher.replaceFirst("/");
            String decodedUri = relativeUri;
            try {
                decodedUri = URLDecoder.decode(relativeUri, "UTF8");
            } catch (UnsupportedEncodingException var9) {
                log.error("Can't decode uri " + uri, var9);
            }
            if (this.pluginResourceLocator.matches(decodedUri)) {
                Map<String, String> queryParams = UrlUtil.getQueryParameters(decodedUri);
                decodedUri = this.stripQueryString(decodedUri);
                DownloadableResource resource = this.pluginResourceLocator.getDownloadableResource(decodedUri, queryParams);
                try {
                    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                    resource.streamResource(outputStream);
                    return new ByteArrayInputStream(outputStream.toByteArray());
                } catch (DownloadException var11) {
                    log.error("Unable to serve plugin resource to word export : uri " + uri, var11);
                }
            } else if (this.downloadResourceManager.matches(decodedUri)) {
                String userName = AuthenticatedUserThreadLocal.getUsername();
                String strippedUri = this.stripQueryString(decodedUri);
                DownloadResourceReader downloadResourceReader = this.getResourceReader(decodedUri, userName, strippedUri);
                if (downloadResourceReader == null) {
                    strippedUri = this.stripQueryString(relativeUri);
                    downloadResourceReader = this.getResourceReader(relativeUri, userName, strippedUri);
                }
                if (downloadResourceReader != null) {
                    try {
                        return downloadResourceReader.getStreamForReading();
                    } catch (Exception var10) {
                        log.warn("Could not retrieve image resource {} during Confluence word export :{}", decodedUri, var10.getMessage());
                        if (log.isDebugEnabled()) {
                            log.warn("Could not retrieve image resource " + decodedUri + " during Confluence word export :" + var10.getMessage(), var10);
                        }
                    }
                }
            } else if (uri.startsWith("data:")) {
                return this.streamDataUrl(uri);
            }.....

這個(gè)函數(shù)就是獲取圖片資源的,會(huì)對不同格式的圖片鏈接進(jìn)行不同的處理,這里重點(diǎn)是 this.downloadResourceManager.matches(decodedUri),當(dāng)跟到這里的時(shí)候,此時(shí)的 this.downloadResourceManagerDelegatorDownloadResourceManager,并且下面有6個(gè) downloadResourceManager,其中就有我們想要的 PackageResourceManager。

Confluence 文件讀取漏洞(CVE-2019-3394)分析

跟到 DelegatorDownloadResourceManagermatches方法。

    public boolean matches(String resourcePath) {
        return !this.managersForResource(resourcePath).isEmpty();
    }
    ......
        private List<DownloadResourceManager> managersForResource(String resourcePath) {
        return (List)this.downloadResourceManagers.stream().filter((manager) -> {
            return manager.matches(resourcePath) || manager.matches(resourcePath.toLowerCase());
        }).collect(Collectors.toList());
    }

matches方法會(huì)調(diào)用 managersForResource方法,分別調(diào)用每個(gè) downloadResourceManagermatches方法去匹配 resourcePath,只要有一個(gè) downloadResourceManager匹配上了,就返回 true。來看下 PackageResourceManagermatches方法

    public PackageResourceManager(ResourceAccessor resourceAccessor) {
        this.resourceAccessor = resourceAccessor;
    }
    public boolean matches(String resourcePath) {
        return resourcePath.startsWith(BUNDLE_PLUGIN_PATH_REQUEST_PREFIX);
    }
    static {
        BUNDLE_PLUGIN_PATH_REQUEST_PREFIX = DownloadResourcePrefixEnum.PACKAGE_DOWNLOAD_RESOURCE_PREFIX.getPrefix();
    }

resourcePath要以 BUNDLE_PLUGIN_PATH_REQUEST_PREFIX開頭才返回true,看下 BUNDLE_PLUGIN_PATH_REQUEST_PREFIX,是 DownloadResourcePrefixEnum中的 PACKAGE_DOWNLOAD_RESOURCE_PREFIX,也就是 /packages

public enum DownloadResourcePrefixEnum {
    ATTACHMENT_DOWNLOAD_RESOURCE_PREFIX("/download/attachments"),
    THUMBNAIL_DOWNLOAD_RESOURCE_PREFIX("/download/thumbnails"),
    ICON_DOWNLOAD_RESOURCE_PREFIX("/images/icons"),
    PACKAGE_DOWNLOAD_RESOURCE_PREFIX("/packages");

所以, resourcePath要以 /packages開頭才會(huì)返回true。

回到 createInputStreamFromRelativeUrl方法中,當(dāng)有 downloadResourceManager匹配上了 decodedUri,就會(huì)進(jìn)入分支。繼續(xù)調(diào)用 DownloadResourceReader downloadResourceReader = this.getResourceReader(decodedUri, userName, strippedUri);

    private DownloadResourceReader getResourceReader(String uri, String userName, String strippedUri) {
        DownloadResourceReader downloadResourceReader = null;
        try {
            downloadResourceReader = this.downloadResourceManager.getResourceReader(userName, strippedUri, UrlUtil.getQueryParameters(uri));
        } catch (UnauthorizedDownloadResourceException var6) {
            log.debug("Not authorized to download resource " + uri, var6);
        } catch (DownloadResourceNotFoundException var7) {
            log.debug("No resource found for url " + uri, var7);
        }
        return downloadResourceReader;
    }

跳到 DelegatorDownloadResourceManager中的 getResourceReader

    public DownloadResourceReader getResourceReader(String userName, String resourcePath, Map parameters) throws DownloadResourceNotFoundException, UnauthorizedDownloadResourceException {
        List<DownloadResourceManager> matchedManagers = this.managersForResource(resourcePath);
        return matchedManagers.isEmpty() ? null : ((DownloadResourceManager)matchedManagers.get(0)).getResourceReader(userName, resourcePath, parameters);
    }

這里會(huì)繼續(xù)調(diào)用 managersForResource去調(diào)用每個(gè) downloadResourceManagermatches方法去匹配 resourcePath,如果匹配上了,就繼續(xù)調(diào)用對應(yīng)的 downloadResourceManagergetResourceReader方法。到了這里,就把之前的都串起來了,如果我們讓 PackageResourceManager中的 matches方法匹配上了 resourcePath,那么這里就會(huì)繼續(xù)調(diào)用 PackageResourceManager中的 getResourceReader方法,也就是漏洞的最終觸發(fā)點(diǎn)。所以要進(jìn)入到這里, resourcePath必須是以 /packages開頭。

整個(gè)流程圖大概如下

Confluence 文件讀取漏洞(CVE-2019-3394)分析

構(gòu)造

流程分析清楚了,現(xiàn)在就剩下怎么構(gòu)造了。我們要插入一張鏈接以 /packages開頭的圖片。

新建一個(gè)頁面,插入一張網(wǎng)絡(luò)圖片

Confluence 文件讀取漏洞(CVE-2019-3394)分析

不能直接保存,直接保存的話插入的圖像鏈接會(huì)自動(dòng)拼接上網(wǎng)站地址,所以在保存的時(shí)候要使用 burpsuite 把自動(dòng)拼接的網(wǎng)站地址去掉。

發(fā)布時(shí),抓包

Confluence 文件讀取漏洞(CVE-2019-3394)分析

去掉網(wǎng)址

Confluence 文件讀取漏洞(CVE-2019-3394)分析

發(fā)布之后,可以看到,圖片鏈接成功保存下來了

Confluence 文件讀取漏洞(CVE-2019-3394)分析

最后點(diǎn)擊 導(dǎo)出 Word 觸發(fā)漏洞即可。成功讀取數(shù)據(jù)后會(huì)保存到圖片中,然后放到 Word 文檔里面,由于無法正常顯示,所以使用 burp 來查看返回的數(shù)據(jù)。

Confluence 文件讀取漏洞(CVE-2019-3394)分析

成功讀取到了 /WEB-INF/web.xml的內(nèi)容。

其他

這個(gè)漏洞是無法跳出web目錄去讀文件的, getResource最后是會(huì)調(diào)到 org.apache.catalina.webresources.StandardRoot里面的 getResource方法,這里面有個(gè) validate函數(shù),對路徑有限制和過濾,導(dǎo)致無法跳到 /WEB-INF/的上一層目錄,最多跳到同層目錄。有興趣的可以去跟一下。

參考鏈接

Local File Disclosure via Word Export in Confluence Server - CVE-2019-3394

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI