2020年09月29日, 360CERT對Apache ofbiz
組件的反序列化漏洞進行了分析,該漏洞編號爲 CVE-2020-9496
,漏洞等級:高危
,漏洞評分:8.0
。php
Apache ofbiz
存在 反序列化漏洞
,***者
經過 訪問未受權接口,構造特定的xmlrpc http請求
,能夠形成 遠程代碼執行的影響
。html
360CERT對該漏洞的評定結果以下java
評定方式 | 等級 |
---|---|
威脅等級 | 高危 |
影響面 | 通常 |
360CERT評分 | 8.0分 |
- Apache Ofbiz:< 17.12.04git
XML-RPC
是一種遠程過程調用(RPC
)協議,它使用XML
對其調用進行編碼,並使用HTTP
做爲傳輸機制。它是一種規範和一組實現,容許軟件運行在不一樣的操做系統上,運行在不一樣的環境中,經過Internet
進行過程調用。在XML-RPC
中,客戶端經過向實現XML-RPC
並接收HTTP
響應的服務器發送HTTP
請求來執行RPC
。github
客戶端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
。
啓動服務端以後,運行客戶端。
抓取流量。動態代理和普通的流量都是同樣的。
客戶端向/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>
注:`ofbiz 17.12.03`版本的`commons-beanutils`依賴版本是`1.9.3`
在第一次加載ControlServlet
的時候,會調用init
進行初始化,此時會經過getRequestHandler
來初始化RequestHandler
。
調用getControllerConfigURL
方法,
該方法從配置文件/WEB-INF/controller.xml
中獲取定義好的請求映射的列表,這裏會遍歷全部webapp
的controller.xml
配置文件。
其中,定義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
:
而後實例化ViewFactory
和EventFactory
。
先看實例化ViewFactory
,會根據配置文件裏的type
是view
屬性中對應的值,遍歷出所須要的Viewerhandler
,而且進行init
初始化。而後存入map
。
webtools
下的配置文件就存在9
中type
。
接着實例化EventFactory
,一樣遍歷出type
是event
的EventHandler
,並進行init
初始化,而後存入map
。
而後把全部初始化的設置到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
方法進行處理:
首先,調用getRequestHandler
,由於已經初始化了,因此可以直接獲取到。
接着往下走,是處理request
請求的一些東西,而後調用requestHandler.doRequest
方法 根據request
請求獲取當前請求的appname
,這裏是webtools
,而後獲取pathinfo
也就是xmlrpc
。
而後根據pathinfo
從config
裏獲取對應的配置,繼續往下走,requestMap.event
也就是以前根據配置所獲取到的xmlrpc
。
type
,path
,invoke
都不爲null
,因而跟進runEvent
。根據type
從eventFactory
中獲取eventhandler
,
而後調用eventHandler.invoke
,這裏咱們的echo
參數爲null
,因而調用execute
方法。
跟入getRequest
方法。
前幾行是在爲SAX
解析作準備,設置了一個handler
爲XmlRpcRequestParser
,結構以下:
主要的element
解析發生在XmlRpcRequestParser
裏。
而後調用parse
函數,正式進入http xml
解析的流程。
這裏xml
的解析主要採用SAX
方式解析。SAX
解析觸發的事件有
startDocument:開始讀取XML文檔; startElement:讀取到了一個元素,例如<book>; characters:讀取到了字符; endElement:讀取到了一個結束的元素,例如</book>; endDocument:讀取XML文檔結束。
整個scan
流程主要發生在XMLDocumentFragmentScannerImpl#dispatch
,以前的調用棧以下:
用fScannerState
用來標識當前該調用哪一個方法來解析tag
。
剛開始解析的時候,state
爲6
:
調用scanRootElementHook
,用於掃描根元素,Resolver
爲null
,因而進入else
,
接着在AbstractSAXParser#startElement
,會調用ContentHandler.startElement
,這個content
是以前初始化的時候設置的,也就是XmlRpcRequestParser#startElement
方法。
在XmlRpcRequestParser#startElement
函數裏,支持解析methodCall
,methodName
,params
,parma
,value
標籤,若是不是,那麼交給父類RecursiveTypeParserImpl
作進一步處理。
判斷到methodName
會把inMethodName
設置爲true
,以後,在dispatch
根據state
進入分支處理content
,而後會調用XmlRpcRequestParser#characters
,設置methodName
屬性。
下一個標籤是結束標籤</methodName>
調用endElement
,將inMethodName
設置爲false
。
若是解析到是value
裏的子標籤,那麼會調用startValueTag
方法。
會設置inValueTag
屬性爲true
。
後續,好比在解析serializable
標籤的時候,就會進入default
分支。
RecursiveTypeParserImpl#startElement
,剛開始typeParser
爲null
。
在getParser
方法裏會根據標籤從TypeFactoryImpl
裏去建立具體對應的Parser
,而且,想要拿到第一個判斷裏的擴展Parser
,還須要指定pURI
。
咱們poc
裏的value
裏的xml
結構爲:
<struct> <member> <name>test</name> <value> <serializable> {base64codehere} </serializable> </value> </member> </struct>
因而首先實例化的是MapParser
,因而XmlRpcRequestParser
的typeParser
爲MapParser
.
仍是先調用Parser.startDocument
,
接着調用其對應的startElement
方法。
判斷struct
標籤後,而後解析下一個標籤member
重複以前的過程,XmlRpcRequestParser
解析不了,交給RecursiveTypeParserImpl
處理。可是此時typeParser
已經不爲null
,因而直接調用MapParser#startElement
進行處理。
中間其餘tag
省略了,注意serializable
標籤以前還有一個value
標籤,可是不在XmlRpcRequestParser
處理,由於此時level
已經很大了,直接看到MapParser
對value
的處理
調用startValueTag
,從新將typeParser
設置爲null
,可是這裏設置的是MapParser
的typeParser
,這一步很關鍵,typeParser
爲null
才能從新獲取parser
。
在解析serializable
的時候,XmlRpcRequestParser
的typeParser
依然是MapParser
,可是在MapParser
裏處理不了serializable
標籤,此時再交給RecursiveTypeParserImpl
,這時獲取到的就是MapParser
的typeParser
,由於以前被設置爲null
,因此從新獲取Parser
,而這時解析到serializable
標籤,因而getParser
返回爲SerializableParser
。
SerializableParser
繼承ByteArrayParser
,沒有startElement
方法,因而調用父類ByteArrayParser
,設置OutputStream
,且解碼輸入流。
接着處理</serializable>
在Serializable#endElement
,setResult
給result
賦值。
接着是處理</value>
,在MapParser#endElement
。
跟入endValueTag
,typeParser
爲Serializable
。
調用getResult
,取出result
並形成反序列化。
Fixed: Apache OFBiz unsafe deserialization of XMLRPC arguments
https://github.com/apache/ofbiz-framework/commit/4bdfb54ffb6e05215dd826ca2902c3e31420287a#diff-b31806fbf9690361ad449e8f263345d8
直接在controller.xml
配置xmlrpc
須要受權訪問。
xmlrpc
自己是支持對序列化數據的反序列化的,而問題就出如今ofbiz
沒有對xmlrpc
接口的訪問作權限的控制,同時版本較低的狀況下又存在可以被反序列化所利用的依賴。
2020-09-29 360CERT發佈分析
一、 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
推薦閱讀:
三、CVE-2020-14386: Linux內核權限提高漏洞通告
長按下方二維碼關注360CERT!謝謝你的關注!
注:360CERT官方網站提供 《CVE-2020-9496:Apache Ofbiz 反序列化漏洞分析》 完整詳情,點擊閱讀原文