漏洞分析ApacheOFBiz是一个开源的企业资源规划(ERP)系统,它提供了一系列的企业应用程序来帮助企业自动化许多业务流程。它包括一个提供通用数据模型和业务流程的框架,企业中的所有应用程序都需要采用这个框架来使用通用的数据、逻辑和业务处理组件。除了框架本身,ApacheOFBiz还提供包括会计(合同协议、计费、供应商管理、总账)、资产维护、物品分类、产品管理、设备管理、仓库管理系统(WMS)、制造执行/制造等服务运营管理(MES/MOM)和订单处理功能,除其他外,库存管理、自动库存补货、内容管理系统(CMS)、人力资源(HR)、人员和团队管理、项目管理、销售队伍自动化、工作量管理、电子销售点(ePOS)、电子商务(e-commerce)和scrum(开发)等等。ApacheOFBiz使用了一系列开源技术和标准,如Java、JavaEE、XML和SOAP。超文本传输??协议是一种请求/响应协议,在RFC7230-7237中有详细描述。请求从客户端设备发送到服务器,服务器接收并处理请求后,将响应发送回客户端。一个HTTP请求由请求内容、各种Headers、空行和一个可选的消息体组成:Request=Request-LineheadersCRLF[message-body]Request-Line=MethodSPRequest-URISPHTTP-VersionCRLFHeaders=*[Header]Header=Field-Name":"Field-ValueCRLFCRLF表示一个回车符(CR)后跟一个换行符(LF)的新行序列,SP表示一个空格字符。参数将以键值对的形式通过Request-URI或消息体从客户端传递到服务器,具体取决于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=1JavaserializationJava支持对象的序列化,这样它们就可以表示为紧凑且可移植的字节流,然后可以通过网络字节流传输并反序列化为由接收servlet或applet消费。下面的例子演示了如何序列化一个类,然后提取数据:.test=myObj2;//We'llwritetheseserializeddatatoafile"object.ser"FileOutputStreamfos=newFileOutputStream("object.ser");ObjectOutputStreamos=newObjectOutputStream(fos);os.writeObject(myObj);os.close();//从文件中读取序列化数据"object.ser"FileInputStreamfis=newFileInputStream("object.ser");ObjectInputStreamois=newObjectInputStream(fis);//从数据流中读取object,并转换回aStringMyObject1objectFromDisk=(MyObject1)ois.readObject();ois.close()对象需要通过Serializable或Externalizable接口,实现了writeObject()/writeExternal()和readObject()/readExternal()方法,在对象序列化或反序列化时调用。这些方法通过在序列化和反序列化期间修改代码来启用自定义行为。XML-RPCXML-RPC是一种远程过程调用(RPC)协议,它使用XML对其调用进行编码,并使用HTTP作为传输机制。它是一个标准规范,并提供现成的实现,使其可以在不同的操作系统和环境中运行。在XML-RPC中,客户端通过向实现XML-RPC的服务器发送HTTP请求并接收HTTP响应来执行RPC。每个XML-RPC请求都以XML元素“”开头。该元素包含一个子元素“something”。元素“”包含子元素“”,子元素“”可能包含一个或多个“”元素。paramXML元素可以包含多种数据类型。如下例所示,可以将常见的数据类型转换成对应的XML类型:1404Somethinghere1各种原语的编码示例如下:1-12.5342字符串的编码示例如下:Helloworld!结构体的编码示例如下:foo1bar2序列化数据由“”和“”XML元素表示。在ApacheOFBiz中,序列化代码在Java类org.apache.xmlrpc.parser.SerializableParser中实现。然而,ApacheOFBiz存在一个不安全的反序列化漏洞,这是由于OFBiz配置为使用XML-RPC拦截和转换HTTP正文中的XML数据发送到“/webtools/control/xmlrpc”URL时导致的。发送到此端点的请求最初由org.apache.ofbiz.webapp.control.RequestHandlerJava类处理,它决定了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.MapParserorg中存在不安全的序列化问题在getResult().apache.xmlrpc.parser.SerializableParser类的方法。未经身份验证的远程攻击者可以利用此漏洞发送包含自定义XML负载的恶意HTTP请求。由于OFBiz使用易受攻击的ApacheCommonsBeanUtils库和ApacheROME库,攻击者将能够使用ysoserial工具构建XML格式的恶意负载。成功利用此漏洞可能允许攻击者在目标应用程序中执行任意代码。源码分析以下代码片段摘自ApacheOFBizv17.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("requestLocalRequestingHandler","throwLocalRequestingHandler")","Y",delegator);longstartTime=System.currentTimeMillis();HttpSessionsession=request.getSession();ConfigXMLReader.ControllerConfigcontrollerConfig=getControllerConfig();MaprequestMapMap=null;StringstatusCodeString=null;try{requestMap=controllerConfig.getRequestMapMap();statusCodeString=controllerConfig.getStatusCode();}catch(WebAppConfigurationExceptione){Debug.logError((Throwable)e,"Exceptionthrowwhileparsingcontroller.xmlfile:",module);thrownewRequestHandlerException(e);}if(UtilValidate.isEmpty(statusCodeString))statusCodeString=this.defaultStatusCodeString;Stringcname=UtilHttp.getApplicationName(请求);StringdefaultRequestUri=getRequestUri(请求.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;尝试(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);}最后{if(pConnection!=null)try{pConnection.close();}catch(IOExceptione){Debug.logError(e,"Unabletoclosestreamconnection");}}}protectedXmlRpcStreamRequestgetRequest(finalXmlRpcStreamRequestConfigpConfig,InputStreampStream)throwsXmlRpcException{finalXmlRpcRequestParserparser=newXmlRpcRequestParser((XmlRpcStreamConfig)pConfig,getTypeFactory());XMLReaderxr=SAXParsers.newXMLReader();xr.setContentHandler((http"ContentHandler{x.set:parser);//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));//截断}}org.apache.xmlrpc.parser.XmlRpcRequestParser:publicvoidendElement(StringpURI,StringpLocalName,StringpQName)throwsSAXException{//XML-RPCparsinghappenshereswitch(--level){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("预期/值,得到"+newQName(pURI,pLocalNa我),getDocumentLocator());}endValueTag();中断;默认:super.endElement(pURI,pLocalName,pQName);中断;}}org.apache.xmlrpc.parser.SerializableParser:publicclassSerializableParserextendsByteArrayParser{publicObjectgetResult()throwsXmlRpcException{try{byte[]res=(byte[])super.getResult();ByteArrayInputStreambais=newByteArrayInputStream(res);ObjectInputStreamois=newObjectInputStream(bais);//insecuredeserializationhappensherreturnois.readObject();}catch(IOExceptione){thrownewXmlRpcException("失败"+e.getMessage(),e);}catch(ClassNotFoundExceptione){thrownewXmlRpcException("Failedtoloadclassforresultobject:"+e.getMessage(),e);}}}为了触发该漏洞,攻击者需要携带自定义的序列化对象在文件中并将其发送到易受攻击的目标应用程序。当服务器序列化XML数据时,会触发该漏洞