Maison  >  Article  >  Opération et maintenance  >  APACHE OFBIZ XMLRPC Exemple d'analyse de vulnérabilité d'exécution de code à distance

APACHE OFBIZ XMLRPC Exemple d'analyse de vulnérabilité d'exécution de code à distance

PHPz
PHPzavant
2023-05-14 08:10:111771parcourir

Overview

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.

Analyse de vulnérabilité

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

Java Serialization

# 🎜🎜 #Java prend en charge les opérations de sérialisation sur les objets afin qu'ils puissent être représentés sous la forme d'un flux d'octets compact et portable. Ce flux d'octets peut ensuite être transmis sur le réseau et désérialisé pour l'utilisation du servlet ou de l'applet de réception. L'exemple suivant montre comment sérialiser une classe et ensuite extraire des données :

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();

}
Tous les objets Java doivent être sérialisés via l'interface Serialisable ou Externalisable, qui implémente writeObject() /writeExternal() et readObject() Méthodes /readExternal(), qui seront appelées lorsque l'objet est sérialisé ou désérialisé. Ces méthodes permettent un comportement personnalisé en modifiant le code lors de la sérialisation et de la désérialisation.

XML-RPC

XML-RPC est un protocole d'appel de procédure distante (RPC) qui utilise XML pour coder ses appels et HTTP comme mécanisme de transport. Il s'agit d'une spécification standard qui fournit des implémentations prêtes à l'emploi qui lui permettent de fonctionner sur différents systèmes d'exploitation et environnements. Dans XML-RPC, un client effectue RPC en envoyant une requête HTTP à un serveur qui implémente XML-RPC et reçoit une réponse HTTP.

Chaque requête XML-RPC commence par l'élément XML "". Cet élément contient un élément enfant "something". L'élément "" contient un élément enfant "" qui peut contenir un ou plusieurs éléments "". L'élément XML param peut contenir de nombreux types de données.

Comme le montre l'exemple suivant, les types de données courants peuvent être convertis en types XML correspondants :

<array>

  <data>

    <value><i4>1404</i4></value>

    <value><string>Something here</string></value>

    <value><i4>1</i4></value>

  </data>

</array>
Les exemples d'encodage de diverses primitives sont les suivants :

<boolean>1</boolean>

<double>-12.53</double>

<int>42</int>
L'exemple d'encodage de chaîne est le suivant :

<string>Hello world!</string>
L'exemple d'encodage de structure est le suivant :

<struct>

  <member>

    <name>foo</name>

    <value><i4>1</i4></value>

  </member>

  <member>

    <name>bar</name>

    <value><i4>2</i4></value>

  </member>

</struct>
Les données sérialisées sont enveloppées par "" et "" Éléments XML Indique que dans Apache OFBiz, le code de sérialisation est implémenté dans la classe Java org.apache.xmlrpc.parser.SeriallesslyParser.

Cependant, il existe une vulnérabilité de désérialisation non sécurisée dans Apache OFBiz. Cette vulnérabilité est due à la configuration d'OFBiz pour utiliser l'interception XML-RPC et est causée par la conversion des données XML dans le corps HTTP. Les requêtes envoyées à ce point de terminaison sont initialement traitées par la classe Java org.apache.ofbiz.webapp.control.RequestHandler, qui détermine la manière dont l'URL est mappée. Ensuite, la classe org.apache.ofbiz.webapp.event.XmlRpcEventHandler appellera la méthode execute(). L'analyse XML doit d'abord appeler la méthode parse() via la classe XMLReader, et cette méthode doit être dans org.apache. ofbiz.webapp.event. Appelé dans la méthode getRequest() de la classe .XmlRpcEventHandler.

Les éléments des requêtes XML-RPC seront analysés dans les classes suivantes :

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版本,并添加了相应的注释。

org.apache.ofbiz.webapp.control.RequestHandler:

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");

org.apache.ofbiz.webapp.event.XmlRpcEventHandler:

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

    }

}

org.apache.xmlrpc.parser.XmlRpcRequestParser:

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;

         }  

}

org.apache.xmlrpc.parser.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);

    

            //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!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer