Des chercheurs ont signalé une vulnérabilité de désérialisation dans Apache OFBiz. La vulnérabilité est causée par plusieurs problèmes de désérialisation Java et peut être déclenchée lorsque le code traite les requêtes adressées à /webtools/control/xmlrpc. Un attaquant distant non authentifié pourrait déclencher et exploiter cette vulnérabilité et réaliser l'exécution de code arbitraire en envoyant une requête malveillante contrefaite.
Apache OFBiz est un système de planification des ressources d'entreprise (ERP) open source qui fournit une série d'applications d'entreprise pour aider les entreprises à automatiser de nombreux processus métier. Il comprend un cadre qui fournit un modèle de données et un processus métier communs que toutes les applications de l'entreprise doivent utiliser pour utiliser des composants communs de données, de logique et de traitement métier. En plus du framework lui-même, Apache OFBiz fournit également des services comprenant la comptabilité (accords contractuels, factures, gestion des fournisseurs, grand livre général), la maintenance des actifs, la classification des articles, la gestion des produits, la gestion des équipements, le système de gestion d'entrepôt (WMS), l'exécution/fabrication de la fabrication. Fonctions de gestion des opérations (MES/MOM) et de traitement des commandes, en plus de la gestion des stocks, du réapprovisionnement automatique des stocks, du système de gestion de contenu (CMS), des ressources humaines (RH), de la gestion des personnes et des équipes, de la gestion de projet, de la force de vente. Diverses fonctionnalités telles que l'automatisation , gestion de la charge de travail, point de vente électronique (ePOS), e-commerce (e-commerce) et scrum (développement).
Apache OFBiz utilise une gamme de technologies et de standards open source, tels que Java, JavaEE, XML et SOAP.
Hypertext Transfer Protocol est un protocole de requête/réponse décrit en détail dans la RFC 7230-7237. Une requête est envoyée du périphérique client au serveur, et une fois que le serveur a reçu et traité la requête, il renvoie une réponse au client. Une requête HTTP se compose du contenu de la requête, de divers en-têtes, de lignes vides et de corps de message facultatifs :
Request = Request-Line headers CRLF [message-body] Request-Line = Method SP Request-URI SP HTTP-Version CRLF Headers = *[Header] Header = Field-Name “:” Field-Value CRLF
CRLF représente le retour chariot (CR) de la nouvelle séquence de ligne, suivi d'un saut de ligne (LF), SP représente un caractère spatial. Les paramètres seront transmis du client au serveur sous la forme de paires clé-valeur via le Request-URI ou le corps du message, en fonction des paramètres définis dans les en-têtes Method et Content-Type. Par exemple, dans l'exemple de requête HTTP suivant, il existe un paramètre nommé "param" avec une valeur de "1" et la méthode POST est utilisée :
POST /my_webapp/mypage.htm HTTP/1.1 Host: www.myhost.com Content-Type: application/x-www-form-urlencoded Content-Length: 7 param=1
public static void main(String args[]) throws Exception{ //This is the object we're going to serialize. MyObject1 myObj = new MyObject1(); MyObject2 myObj2 = new MyObject2(); myObj2.name = "calc"; myObj.test = myObj2; //We'll write the serialized data to a file "object.ser" FileOutputStream fos = new FileOutputStream("object.ser"); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(myObj); os.close(); //Read the serialized data back in from the file "object.ser" FileInputStream fis = new FileInputStream("object.ser"); ObjectInputStream ois = new ObjectInputStream(fis); //Read the object from the data stream, and convert it back to a String MyObject1 objectFromDisk = (MyObject1)ois.readObject(); ois.close(); }
<array> <data> <value><i4>1404</i4></value> <value><string>Something here</string></value> <value><i4>1</i4></value> </data> </array>
<boolean>1</boolean> <double>-12.53</double> <int>42</int>
<string>Hello world!</string>
<struct> <member> <name>foo</name> <value><i4>1</i4></value> </member> <member> <name>bar</name> <value><i4>2</i4></value> </member> </struct>
org.apache.xmlrpc.parser.XmlRpcRequestParser org.apache.xmlrpc.parser.RecursiveTypeParserImpl org.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版本,并添加了相应的注释。
public void doRequest(HttpServletRequest request, HttpServletResponse response, String chain, GenericValue userLogin, Delegator delegator) throws RequestHandlerException, RequestHandlerExceptionAllowExternalRequests { ConfigXMLReader.RequestResponse eventReturnBasedRequestResponse; if (!this.hostHeadersAllowed.contains(request.getServerName())) { Debug.logError("Domain " + request.getServerName() + " not accepted to prevent host header injection ", module); throw new RequestHandlerException("Domain " + request.getServerName() + " not accepted to prevent host header injection "); } boolean throwRequestHandlerExceptionOnMissingLocalRequest = EntityUtilProperties.propertyValueEqualsIgnoreCase("requestHandler", "throwRequestHandlerExceptionOnMissingLocalRequest", "Y", delegator); long startTime = System.currentTimeMillis(); HttpSession session = request.getSession(); ConfigXMLReader.ControllerConfig controllerConfig = getControllerConfig(); Map<String, ConfigXMLReader.RequestMap> requestMapMap = null; String statusCodeString = null; try { requestMapMap = controllerConfig.getRequestMapMap(); statusCodeString = controllerConfig.getStatusCode(); } catch (WebAppConfigurationException e) { Debug.logError((Throwable)e, "Exception thrown while parsing controller.xml file: ", module); throw new RequestHandlerException(e); } if (UtilValidate.isEmpty(statusCodeString)) statusCodeString = this.defaultStatusCodeString; String cname = UtilHttp.getApplicationName(request); String defaultRequestUri = getRequestUri(request.getPathInfo()); if (request.getAttribute("targetRequestUri") == null) if (request.getSession().getAttribute("_PREVIOUS_REQUEST_") != null) { request.setAttribute("targetRequestUri", request.getSession().getAttribute("_PREVIOUS_REQUEST_")); } else { request.setAttribute("targetRequestUri", "/" + defaultRequestUri); } String overrideViewUri = getOverrideViewUri(request.getPathInfo()); String requestMissingErrorMessage = "Unknown request [" + defaultRequestUri + "]; this request does not exist or cannot be called directly."; ConfigXMLReader.RequestMap requestMap = null; if (defaultRequestUri != null) //get the mapping for the URI requestMap = requestMapMap.get(defaultRequestUri); if (requestMap == null) { String defaultRequest; //[...truncated for readability.....] ConfigXMLReader.RequestResponse nextRequestResponse = null; if (eventReturn == null && requestMap.event != null && requestMap.event.type != null && requestMap.event.path != null && requestMap.event.invoke != null) try { long eventStartTime = System.currentTimeMillis(); //call XmlRpcEventHandler eventReturn = runEvent(request, response, requestMap.event, requestMap, "request");
public void execute(XmlRpcStreamRequestConfig pConfig, ServerStreamConnection pConnection) throws XmlRpcException { try { ByteArrayOutputStream baos; OutputStream initialStream; 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; } 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"); } } } protected XmlRpcRequest getRequest(final XmlRpcStreamRequestConfig pConfig, InputStream pStream) throws XmlRpcException { final XmlRpcRequestParser parser = new XmlRpcRequestParser((XmlRpcStreamConfig)pConfig, getTypeFactory()); XMLReader xr = 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); //the parsing of XML in the HTTP body starts in this function xr.parse(new InputSource(pStream)); //truncated } }
public void endElement(String pURI, String pLocalName, String pQName) throws SAXException { //XML-RPC parsing happens here switch(--level) { case 0: break; case 1: if (inMethodName) { if ("".equals(pURI) && "methodName".equals(pLocalName)) { if (methodName == null) { methodName = ""; } } else { throw new SAXParseException("Expected /methodName, got " + new QName(pURI, pLocalName), getDocumentLocator()); } inMethodName = false; } else if (!"".equals(pURI) || !"params".equals(pLocalName)) { throw new SAXParseException("Expected /params, got " + new QName(pURI, pLocalName), getDocumentLocator()); } break; case 2: if (!"".equals(pURI) || !"param".equals(pLocalName)) { throw new SAXParseException("Expected /param, got " + new QName(pURI, pLocalName), getDocumentLocator()); } break; case 3: if (!"".equals(pURI) || !"value".equals(pLocalName)) { throw new SAXParseException("Expected /value, got " + new QName(pURI, pLocalName), getDocumentLocator()); } endValueTag(); break; default: super.endElement(pURI, pLocalName, pQName); break; } }
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); //insecure deserialization happens here 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); } } }
为了触发该漏洞,攻击者需要以XML格式在HTTP请求中携带定制的序列化对象,并发送给存在漏洞的目标应用程序,当服务器端在序列化XML数据时,便会触发该漏洞。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!