CVE-2020-9496:Apache Ofbiz 反序列化漏洞分析

 

 

0x01 漏洞簡述

2020年09月29日, 360CERT對Apache ofbiz組件的反序列化漏洞進行了分析,該漏洞編號爲 CVE-2020-9496,漏洞等級:高危,漏洞評分:8.0php

Apache ofbiz 存在 反序列化漏洞***者 經過 訪問未受權接口,構造特定的xmlrpc http請求,能夠形成 遠程代碼執行的影響html

0x02 風險等級

360CERT對該漏洞的評定結果以下java

評定方式 等級
威脅等級 高危
影響面 通常
360CERT評分 8.0分

0x03 影響版本

- Apache Ofbiz:< 17.12.04git

0x04 漏洞詳情

XML-RPC

XML-RPC是一種遠程過程調用(RPC)協議,它使用XML對其調用進行編碼,並使用HTTP做爲傳輸機制。它是一種規範和一組實現,容許軟件運行在不一樣的操做系統上,運行在不一樣的環境中,經過Internet進行過程調用。在XML-RPC中,客戶端經過向實現XML-RPC並接收HTTP響應的服務器發送HTTP請求來執行RPCgithub

Demo

客戶端web

package org.apache.xmlrpc.demo.client;


import java.net.URL;


import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
import org.apache.xmlrpc.client.XmlRpcSunHttpTransportFactory;


public class Client {
public static void main(String[] args) throws Exception {
// 建立客戶端實例
        XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
        config.setServerURL(new URL("http://127.0.0.1:8081/xmlrpc"));
        XmlRpcClient client = new XmlRpcClient();
        client.setConfig(config);
// 設置傳輸工廠類
        client.setTransportFactory(new XmlRpcSunHttpTransportFactory(client));
// 建立遠程方法的參數數組,經過指定遠程方法名稱進行調用
        Object[] params = new Object[]{new Integer(33), new Integer(9)};
        Integer result = (Integer) client.execute("Calculator.add", params);
        System.out.println(result);
}
}

或者客戶端使用動態代理的方式,經過ClientFactory,須要客戶端和服務端都要有Adder的接口,具體實現類在服務端。apache

但要使用XML-RPC的動態代理功能,相應的服務器端的處理器類名稱必須是Client端接口類的全名(含包名,該名稱通常應該與Server端接口類全名一致),不然將會致使調用失敗。數組

public class Client_Proxy {
public static void main(String[] args) throws MalformedURLException {
        XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
        config.setServerURL(new URL("http://127.0.0.1:8081/xmlrpc"));
        XmlRpcClient client = new XmlRpcClient();
        client.setConfig(config);
        ClientFactory factory = new ClientFactory(client);
        Adder adder = (Adder) factory.newInstance(Adder.class);
int sum = adder.add(2, 4);
        System.out.println(sum);
}
}

Adder接口安全

package org.apache.xmlrpc.demo.proxy;


public interface Adder {
public int add(int pNum1, int pNum2);
}

服務端服務器

package org.apache.xmlrpc.demo.webserver;


import org.apache.xmlrpc.server.PropertyHandlerMapping;
import org.apache.xmlrpc.server.XmlRpcServer;
import org.apache.xmlrpc.server.XmlRpcServerConfigImpl;
import org.apache.xmlrpc.webserver.WebServer;


public class Server {
private static final int port = 8081;


public static void main(String[] args) throws Exception {
        WebServer webServer = new WebServer(port);


        XmlRpcServer xmlRpcServer = webServer.getXmlRpcServer();


        PropertyHandlerMapping phm = new PropertyHandlerMapping();
/* Load handler definitions from a property file.
         * The property file might look like:
         *   Calculator=org.apache.xmlrpc.demo.Calculator
         *   org.apache.xmlrpc.demo.proxy.Adder=org.apache.xmlrpc.demo.proxy.AdderImpl
         */
        phm.load(Thread.currentThread().getContextClassLoader(),
"MyHandlers.properties");


/* You may also provide the handler classes directly,
         * like this:
         * phm.addHandler("Calculator",
         *     org.apache.xmlrpc.demo.Calculator.class);
         * phm.addHandler(org.apache.xmlrpc.demo.proxy.Adder.class.getName(),
         *     org.apache.xmlrpc.demo.proxy.AdderImpl.class);
         */


        phm.addHandler("Calculator",
                     org.apache.xmlrpc.demo.Calculator.class);


        xmlRpcServer.setHandlerMapping(phm);


        XmlRpcServerConfigImpl serverConfig =
(XmlRpcServerConfigImpl) xmlRpcServer.getConfig();
        serverConfig.setEnabledForExtensions(true);
        serverConfig.setContentLengthOptional(false);


        webServer.start();
}
}

服務端調用類

package org.apache.xmlrpc.demo;


public class Calculator {
public int add(int i1, int i2) {
return i1 + i2;
}
public int subtract(int i1, int i2) {
return i1 - i2;
}
}

在服務端還須要建立一個MyHandlers.properties

啓動服務端以後,運行客戶端。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

抓取流量。動態代理和普通的流量都是同樣的。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

客戶端向/xmlrpc發了一個POST請求,請求的內容爲:

<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>Calculator.add</methodName>
<params><param>
<value>
<i4>33</i4>
</value>
</param><param>
<value>
<i4>9</i4>
</value>
</param></params>
</methodCall>

每一個XML-RPC請求都以XML元素<methodCall> </methodCall>開頭。該元素包含單個子元素<methodName>xxx</methodName>。元素<methodName>包含子元素<params>,該子元素能夠包含一個或多個<param>元素。 param XML元素能夠包含許多數據類型。

服務端響應的內容爲

<?xml version="1.0" encoding="UTF-8"?>
<methodResponse xmlns:ex="http://ws.apache.org/xmlrpc/namespaces/extensions">
<params><param>
<value>
<i4>42</i4>
</value>
</param></params>
</methodResponse>

初始化RequestHandler

注:`ofbiz  17.12.03`版本的`commons-beanutils`依賴版本是`1.9.3`

在第一次加載ControlServlet的時候,會調用init進行初始化,此時會經過getRequestHandler來初始化RequestHandler

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

調用getControllerConfigURL方法,

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

該方法從配置文件/WEB-INF/controller.xml中獲取定義好的請求映射的列表,這裏會遍歷全部webappcontroller.xml配置文件。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

其中,定義xmlrpc的配置,沒有設置對應的auth選項,默認爲false,不須要身份驗證,這也是以後修復的一個點。

<request-map uri="xmlrpc" track-serverhit="false" track-visit="false">
<security https="false"/>
<event type="xmlrpc"/>
<response name="error" type="none"/>
<response name="success" type="none"/>
</request-map>

官方文檔給出的配置文件的tag

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

而後實例化ViewFactoryEventFactory

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

先看實例化ViewFactory,會根據配置文件裏的typeview屬性中對應的值,遍歷出所須要的Viewerhandler,而且進行init初始化。而後存入map

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

webtools下的配置文件就存在9type

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

接着實例化EventFactory,一樣遍歷出typeeventEventHandler,並進行init初始化,而後存入map

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

而後把全部初始化的設置到servletContext裏。

處理請求

根據公開的zdi文章,xml的執行是在webtools/control/xmlrpc,因而去/webtools/webapp/webtools/WEB-INF/web.xml中查看相關路由的處理。

<servlet-mapping>                              
<servlet-name>ControlServlet</servlet-name>
<url-pattern>/control/*</url-pattern>
</servlet-mapping>

請求/control下的資源都由ControlServlet來進行處理,是全部請求處理的核心。post請求也會由ControlServlet#doGet方法進行處理:

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

首先,調用getRequestHandler,由於已經初始化了,因此可以直接獲取到。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

接着往下走,是處理request請求的一些東西,而後調用requestHandler.doRequest方法 根據request請求獲取當前請求的appname,這裏是webtools,而後獲取pathinfo也就是xmlrpc

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

而後根據pathinfoconfig裏獲取對應的配置,繼續往下走,requestMap.event也就是以前根據配置所獲取到的xmlrpc

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

typepathinvoke都不爲null,因而跟進runEvent。根據typeeventFactory中獲取eventhandler

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

XmlRpcEventHandler的調用

而後調用eventHandler.invoke,這裏咱們的echo參數爲null,因而調用execute方法。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

跟入getRequest方法。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

前幾行是在爲SAX解析作準備,設置了一個handlerXmlRpcRequestParser,結構以下:

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

主要的element解析發生在XmlRpcRequestParser裏。

而後調用parse函數,正式進入http xml解析的流程。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

XML解析

這裏xml的解析主要採用SAX方式解析。SAX解析觸發的事件有

startDocument:開始讀取XML文檔;
startElement:讀取到了一個元素,例如<book>;
characters:讀取到了字符;
endElement:讀取到了一個結束的元素,例如</book>;
endDocument:讀取XML文檔結束。

整個scan流程主要發生在XMLDocumentFragmentScannerImpl#dispatch,以前的調用棧以下:

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

fScannerState用來標識當前該調用哪一個方法來解析tag

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

剛開始解析的時候,state6

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

調用scanRootElementHook,用於掃描根元素,Resolvernull,因而進入else

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

接着在AbstractSAXParser#startElement,會調用ContentHandler.startElement,這個content是以前初始化的時候設置的,也就是XmlRpcRequestParser#startElement方法。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

XmlRpcRequestParser#startElement函數裏,支持解析methodCall,methodName,params,parma,value標籤,若是不是,那麼交給父類RecursiveTypeParserImpl作進一步處理。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

判斷到methodName會把inMethodName設置爲true,以後,在dispatch根據state進入分支處理content,而後會調用XmlRpcRequestParser#characters,設置methodName屬性。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

下一個標籤是結束標籤</methodName>調用endElement,將inMethodName設置爲false

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

value裏標籤的解析

若是解析到是value裏的子標籤,那麼會調用startValueTag方法。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

會設置inValueTag屬性爲true

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

後續,好比在解析serializable標籤的時候,就會進入default分支。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

RecursiveTypeParserImpl#startElement,剛開始typeParsernull

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

getParser方法裏會根據標籤從TypeFactoryImpl裏去建立具體對應的Parser,而且,想要拿到第一個判斷裏的擴展Parser,還須要指定pURI

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

咱們poc裏的value裏的xml結構爲:

<struct>
<member>
<name>test</name>
<value>
<serializable>
            {base64codehere}
</serializable>
</value>
</member>
</struct>

因而首先實例化的是MapParser,因而XmlRpcRequestParsertypeParserMapParser.

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

仍是先調用Parser.startDocument

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

接着調用其對應的startElement方法。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

判斷struct標籤後,而後解析下一個標籤member

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

重複以前的過程,XmlRpcRequestParser解析不了,交給RecursiveTypeParserImpl處理。可是此時typeParser已經不爲null,因而直接調用MapParser#startElement進行處理。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

第二個value標籤

中間其餘tag省略了,注意serializable標籤以前還有一個value標籤,可是不在XmlRpcRequestParser處理,由於此時level已經很大了,直接看到MapParservalue的處理

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

調用startValueTag,從新將typeParser設置爲null,可是這裏設置的是MapParsertypeParser,這一步很關鍵,typeParsernull才能從新獲取parser

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

serializable標籤

在解析serializable的時候,XmlRpcRequestParsertypeParser依然是MapParser,可是在MapParser裏處理不了serializable標籤,此時再交給RecursiveTypeParserImpl,這時獲取到的就是MapParsertypeParser,由於以前被設置爲null,因此從新獲取Parser,而這時解析到serializable標籤,因而getParser返回爲SerializableParser

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

SerializableParser繼承ByteArrayParser,沒有startElement方法,因而調用父類ByteArrayParser,設置OutputStream,且解碼輸入流。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

接着處理</serializable>Serializable#endElementsetResultresult賦值。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

接着是處理</value>,在MapParser#endElement

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

跟入endValueTag,typeParserSerializable

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

調用getResult,取出result並形成反序列化。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

版本修復

Fixed: Apache OFBiz unsafe deserialization of XMLRPC arguments

https://github.com/apache/ofbiz-framework/commit/4bdfb54ffb6e05215dd826ca2902c3e31420287a#diff-b31806fbf9690361ad449e8f263345d8

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

直接在controller.xml配置xmlrpc須要受權訪問。

總結

xmlrpc 自己是支持對序列化數據的反序列化的,而問題就出如今ofbiz沒有對xmlrpc接口的訪問作權限的控制,同時版本較低的狀況下又存在可以被反序列化所利用的依賴。

0x05 時間線

 
  

2020-09-29 360CERT發佈分析

 
  

0x06 參考連接

一、 Index of /dist/ofbiz

https://archive.apache.org/dist/ofbiz/

二、 About Apache XML-RPC

https://ws.apache.org/xmlrpc/index.html

三、 How to migrate OFBiz from Derby to MySQL database

https://cwiki.apache.org/confluence/display/OFBIZ/How+to+migrate+OFBiz+from+Derby+to+MySQL+database

四、 Control Servlet Guide

https://cwiki.apache.org/confluence/display/OFBIZ/Control+Servlet+Guide

五、 Apache OFBiz XML-RPC Java Deserialization

https://packetstormsecurity.com/files/158887/Apache-OFBiz-XML-RPC-Java-Deserialization.html

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=推薦閱讀:

一、安全事件週報 (09.21-09.27)

二、安全運營週刊第十期

三、CVE-2020-14386: Linux內核權限提高漏洞通告

長按下方二維碼關注360CERT!謝謝你的關注!

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

注:360CERT官方網站提供 《CVE-2020-9496:Apache Ofbiz 反序列化漏洞分析》 完整詳情,點擊閱讀原文

相關文章
相關標籤/搜索