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

PHPz
Libérer: 2023-05-14 08:10:11
avant
1713 Les gens l'ont consulté

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
Copier après la connexion

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
Copier après la connexion

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

}
Copier après la connexion
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>
Copier après la connexion
Les exemples d'encodage de diverses primitives sont les suivants :

<boolean>1</boolean>

<double>-12.53</double>

<int>42</int>
Copier après la connexion
L'exemple d'encodage de chaîne est le suivant :

<string>Hello world!</string>
Copier après la connexion
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>
Copier après la connexion
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
Copier après la connexion

不安全的序列化问题存在于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");
Copier après la connexion

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

    }

}
Copier après la connexion

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;

         }  

}
Copier après la connexion

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

        }

    }  

}
Copier après la connexion

为了触发该漏洞,攻击者需要以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!

Étiquettes associées:
source:yisu.com
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!