漏洞分析
Apache OFBiz是企业资源规划的开源(ERP)该系统提供了一系列企业应用程序,以帮助企业自动化许多业务流程。它包含了一个可以提供常见数据模型和业务流程的框架,企业中的所有应用程序都需要使用常见数据、逻辑和业务处理组件。除了框架本身,Apache OFBiz还提供会计(合同协议、票据、供应商管理、总账)、资产维护、项目分类、产品管理、设备管理、仓库管理系统(WMS)、制造执行/制造经营管理(MES/MOM)此外,还实现了库存管理、自动库存补充、内容管理系统(CMS)、人力资源(HR)、人员和团队管理、项目管理、销售人员自动化、工作量管理、电子销售点(ePOS)、电子商务(电子商务)和scrum(开发)等多种功能。
Apache OFBiz一系列开源技术和标准,如Java、JavaEE、XML和SOAP。
超文本传输协议是 RFC 7230-7237详细描述。请求由客户端设备发送到服务器。服务器接收并处理请求后,响应将发送回客户端。HTTP请求由请求内容组成,各种请求Header、由空行和可选消息体组成:
Request=Request-LineheadersCRLF[message-body]Request-Line=MethodSPRequest-URISPHTTP-VersionCRLFHeaders=*[Header]Header=Field-Name“:”Field-ValueCRLFCRLF代表新的行序列回车符(CR),后跟换行符(LF),SP表示空格字符。参数将以键值对的形式通过Request- URI或message-body由客户端传输给服务器取决于Method和Content-Type头部定义的参数。例如,在下面HTTP请求样本中,有一个名为“param”参数,其值为“1”,使用的是POST方法:
POST/my_webapp/mypage.htmHTTP/1.1Host:www.myhost.comContent-Type:application/x-www-form-urlencodedContent-Length:7param=1Java序列化
Java支持对象的序列化操作,使其数量表示为紧凑和可移植的字节流,然后通过网络传输字节流,并将其反序列化以供接收servlet或applet使用。以下示例展示了如何序列化一个类,然后提取数据:
publicstaticvoidmain(Stringargs[])throwsException{//Thisistheobjectwe'regoingtoserialize.MyObject1myObj=newMyObject1();MyObject2myObj2=newMyObject2();myObj2.name="calc";myObj.test=myObj2;//We'llwritetheserializeddatatoafile"object.ser"FileOutputStreamfos=newFileOutputStream("object.ser");ObjectOutputStreamos=newObjectOutputStream(fos);os.writeObject(myObj);os.close();//Readtheserializeddatabackinfromthefile"object.ser"FileInputStreamfis=newFileInputStream("object.ser");ObjectInputStreamois=newObjectInputStream(fis);//Readtheobjectfromthedatastream,andconvertitbacktoaStringMyObject1objectFromDisk=(MyObject1)ois.readObject();ois.close();}所有的Java对象需要通过Serializable或Externalizable接口序列化,实现了这个接口writeObject()/writeExternal()和readObject()/readExternal()该方法在对象序列化或反序列化时被调用。这些方法可以通过修改代码来实现自定义行为。
XML-RPC
XML-RPC是远程过程调用(RPC)使用协议XML编码并使用它的调用HTTP作为一种传输机制。它是一种标准规范,并提供了现成的实现方法,允许在不同的操作系统和环境中运行。XML-RPC中间,客户机通过实现XML-RPC并接收HTTP发送响应服务器HTTP请求来执行RPC。
每个XML-RPC请求都以XML元素“<methodCall></methodCall>”开始。这个元素含有一个子元素。“<methodName>something</methodName>”。元素“<methodName>”包含子元素“<params>”,可以包含一个或多个子元素“<param>”元素。param XML元素可以包含多种数据类型。
例如,常见的数据类型可以转换为相应的数据类型XML类型:
<array><data><value><i4>1404</i4></value><value><string>Somethinghere</string></value><value><i4>1</i4></value></data></array>各种原语的编码示例如下:
<boolean>1</boolean><double>-12.53</double><int>42</int>字符串的编码示例如下:
<string>Helloworld!</string>结构体的编码示例如下:
<struct><member><name>foo</name><value><i4>1</i4></value></member><member><name>bar</name><value><i4>2</i4></value></member></struct>序列化数据由””和””XML在Apache OFBiz序列化代码在中org.apache.xmlrpc.parser.SerializableParser这个Java类中实现。
但是,Apache OFBiz有一个不安全的反序列化漏洞,这是由于OFBiz配置为发送“/webtools/control/xmlrpc”URL时使用XML-RPC拦截和转换HTTP主体中的XML由数据引起的。发送到此端点的请求最初是由于org.apache.ofbiz.webapp.control.RequestHandler这个Java它确定了类来处理URL映射方法。接下来,org.apache.ofbiz.webapp.event.XmlRpcEventHandler类将调用execute()方法,XML首先需要通过分析XMLReader类来调用parse()方法,法需要org.apache.ofbiz.webapp.event.XmlRpcEventHandler类的getRequest()调用方法。
XML-RPC以下类别将分析请求中的元素:
org.apache.xmlrpc.parser.XmlRpcRequestParserorg.apache.xmlrpc.parser.RecursiveTypeParserImplorg.apache.xmlrpc.parser.MapParser不安全的序列化问题存在于org.apache.xmlrpc.parser.SerializableParser类的getResult()方法。未经身份验证的远程攻击者可以利用该漏洞发送包含定制的漏洞XML Payload的恶意HTTP请求。由于OFBiz有漏洞的使用Apache Commons BeanUtils库和Apache ROME攻击者将能够使用库ysoserial工具以XML构建恶意的格式Payload。该漏洞的成功利用将导致攻击者在目标应用程序中执行任何代码。
源代码分析
取自以下代码段Apache OFBiz v17.12.03版本,并添加相应的注释。
org.apache.ofbiz.webapp.control.RequestHandler:
publicvoiddoRequest(HttpServletRequestrequest,HttpServletResponseresponse,Stringchain,GenericValueuserLogin,Delegatordelegator)throwsRequestHandlerException,RequestHandlerExceptionAllowExternalRequests{ConfigXMLReader.RequestResponseeventReturnBasedRequestResponse;if(!this.hostHeadersAllowed.contains(request.getServerName())){Debug.logError("Domain" request.getServerName() "notacceptedtopreventhostheaderinjection",module);thrownewRequestHandlerException("Domain" request.getServerName() "notacceptedtopreventhostheaderinjection");}booleanthrowRequestHandlerExceptionOnMissingLocalRequest=EntityUtilProperties.propertyValueEqualsIgnoreCase("requestHandler","throwRequestHandlerExceptionOnMissingLocalRequest","Y",delegator);longstartTime=System.currentTimeMillis();HttpSessionsession=request.getSession();ConfigXMLReader.ControllerConfigcontrollerConfig=getControllerConfig();Map<String,ConfigXMLReader.RequestMap>requestMapMap=null;StringstatusCodeString=null;try{requestMapMap=controllerConfig.getRequestMapMap();statusCodeString=controllerConfig.getStatusCode();}catch(WebAppConfigurationExceptione){Debug.logError((Throwable)e,"Exceptionthrownwhileparsingcontroller.xmlfile:",module);thrownewRequestHandlerException(e);}if(UtilValidate.isEmpty(statusCodeString))statusCodeString=this.defaultStatusCodeString;Stringcname=UtilHttp.getApplicationName(request);StringdefaultRequestUri=getRequestUri(request.getPathInfo());if(request.getAttribute("targetRequestUri")==null)if(request.getSession().getAttribute("_PREVIOUS_REQUEST_")!=null){request.setAttribute("targetRequestUri",request.getSession().getAttribute("_PREVIOUS_REQUEST_"));org.apache.ofbiz.webapp.event.XmlRpcEventHandler:
publicvoidexecute(XmlRpcStreamRequestConfigpConfig,ServerStreamConnectionpConnection)throwsXmlRpcException{try{ByteArrayOutputStreambaos;OutputStreaminitialStream;Objectresult=null;booleanfoundError=false;try(InputStreamistream=getInputStream(pConfig,pConnection)){XmlRpcRequestrequest=getRequest(pConfig,istream);result=execute(request);}catch(Exceptione){Debug.logError(e,module);foundError=true;}if(isContentLengthRequired(pConfig)){baos=newByteArrayOutputStream();initialStream=baos;}else{baos=null;initialStream=pConnection.newOutputStream();}try(OutputStreamostream=getOutputStream(pConnection,pConfig,initialStream)){if(!foundError){writeResponse(pConfig,ostream,result);}else{writeError(pConfig,ostream,newException("FailedtoreadXML-RPCrequest.Pleasechecklogsformoreinformation"));}}if(baos!=null)try(OutputStreamdest=getOutputStream(pConfig,pConnection,baos.size())){baos.writeTo(dest);}pConnection.close();pConnection=null;}catch(IOExceptione){thrownewXmlRpcException("I/Oerrorwhileprocessingrequest:" e.getMessage(),e);}finally{if(pConnection!=null)try{pConnection.close();}catch(IOExceptione){Debug.logError(e,"Unabletoclosestreamconnection");}}}protectedXmlRpcRequestgetRequest(finalXmlRpcStreamRequestConfigpConfig,InputStreampStream)throwsXmlRpcException{finalXmlRpcRequestParserparser=newXmlRpcRequestParser((XmlRpcStreamConfig)pConfig,getTypeFactory());XMLReaderxr=SAXParsers.newXMLReader();xr.setContentHandler((ContentHandler)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);//theparsingofXMLintheHTTPbodystartsinthisfunctionxr.parse(newInputSource(pStream));//truncated}}org.apache.xmlrpc.parser.XmlRpcRequestParser:
publicvoidendElement(StringpURI,StringpLocalName,StringpQName)throwsSAXException{//XML-RPCparsinghappenshereswitch(case0:break;case1:if(inMethodName){if("".equals(pURI)&&"methodName".equals(pLocalName)){if(methodName==null){methodName="";}}else{thrownewSAXParseException("Expected/methodName,got" newQName(pURI,pLocalName),getDocumentLocator());}inMethodName=false;}elseif(!"".equals(pURI)||!"params".equals(pLocalName)){thrownewSAXParseException("Expected/params,got" newQName(pURI,pLocalName),getDocumentLocator());}break;case2:if(!"".equals(pURI)||!"param".equals(pLocalName)){thrownewSAXParseException("Expected/param,got" newQName(pURI,pLocalName),getDocumentLocator());}break;case3:if(!"".equals(pURI)||!"value".equals(pLocalName)){thrownewSAXParseException("Expected/value,got" newQName(pURI,pLocalName),getDocumentLocator());}endValueTag();break;default:super.endElement(pURI,pLocalName,pQName);break;}}org.apache.xmlrpc.parser.SerializableParser:
publicclassSerializableParserextendsByteArrayParser{publicObjectgetResult()throwsXmlRpcException{try{byte[]res=(byte[])super.getResult();ByteArrayInputStreambais=newByteArrayInputStream(res);ObjectInputStreamois=newObjectInputStream(bais);//insecuredeserializationhappensherereturnois.readObject();}catch(IOExceptione){thrownewXmlRpcException("Failedtoreadresultobject:" e.getMessage(),e);}catch(ClassNotFoundExceptione){thrownewXmlRpcException("Failedtoloadclassforresultobject:" e.getMessage(),e);}}}为了触发漏洞,攻击者需要XML格式在HTTP当服务器端在序列化中时,将定制的序列化对象带到有漏洞的目标应用程序中XML当数据时,会触发漏洞。