热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

APACHEOFBIZXMLRPC远程代码执行漏洞实例分析

这篇文章给大家介绍APACHEOFBIZXMLRPC远程代码执行漏洞实例分析,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。概述

这篇文章给大家介绍APACHE OFBIZ XMLRPC远程代码执行漏洞实例分析,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

概述

研究人员报告了一个存在于Apache OFBiz中的反序列化漏洞。这个漏洞是由多个Java反序列化问题所导致的,当代码在处理发送至/webtools/control/xmlrpc的请求时,便有可能触发该漏洞。未经认证的远程攻击者将能够通过发送精心构造的恶意请求来触发并利用该漏洞,并实现任意代码执行。

漏洞分析

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-Line headers CRLF [message-body]

Request-Line = Method SP Request-URI SP HTTP-Version CRLF

Headers = *[Header]

Header = Field-Name “:” Field-Value CRLF

CRLF代表新的行序列回车符(CR),后跟换行符(LF),SP表示空格字符。参数将以键值对的形式通过Request- URI或message-body由客户端传递给服务器,具体将取决于Method和Content-Type头中定义的参数。比如说在下面的HTTP请求样本中,有一个名为“param”的参数,其值为“1”,使用的是POST方法:

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序列化

Java支持对对象进行序列化操作,使它们额能够被表示为紧凑和可移植的字节流,然后可以通过网络传输这个字节流,并将其反序列化以供接收的servlet或applet使用。下面的示例演示了如何将一个类进行序列化并在随后提取数据:

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

}

所有的Java对象都需要通过Serializable或Externalizable接口来进行序列化,这个接口实现了writeObject()/writeExternal()和readObject()/readExternal()方法,它们会在对象序列化或反序列化时被调用。这些方法能够在序列化和反序列化过程中通过修改代码来实现自定义行为。

XML-RPC

XML-RPC是一个远程过程调用(RPC)协议,它使用XML对其调用进行编码,并使用HTTP作为传输机制。它是一种标准规范,并提供了现成的实现方式,允许运行在不同的操作系统和环境中。在在XML-RPC中,客户机通过向实现XML-RPC并接收HTTP响应的服务器发送HTTP请求来执行RPC。

每个XML-RPC请求都以XML元素“”开头。此元素包含一个子元素“something”。元素“”包含子元素“”,该子元素可以包含一个或多个“”元素。param XML元素可以包含许多数据类型。

如下样例所示,常见的数据类型可以被转换成对应的XML类型:



  

    1404

    Something here

    1

  

各种原语的编码示例如下:

1

-12.53

42

字符串的编码示例如下:

Hello world!

对结构体的编码示例如下:



  

    foo

    1

  

  

    bar

    2

  

序列化数据由””和””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.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 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数据时,便会触发该漏洞。

关于APACHE OFBIZ XMLRPC远程代码执行漏洞实例分析就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。


推荐阅读
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • Servlet多用户登录时HttpSession会话信息覆盖问题的解决方案
    本文讨论了在Servlet多用户登录时可能出现的HttpSession会话信息覆盖问题,并提供了解决方案。通过分析JSESSIONID的作用机制和编码方式,我们可以得出每个HttpSession对象都是通过客户端发送的唯一JSESSIONID来识别的,因此无需担心会话信息被覆盖的问题。需要注意的是,本文讨论的是多个客户端级别上的多用户登录,而非同一个浏览器级别上的多用户登录。 ... [详细]
  • 本文讨论了在shiro java配置中加入Shiro listener后启动失败的问题。作者引入了一系列jar包,并在web.xml中配置了相关内容,但启动后却无法正常运行。文章提供了具体引入的jar包和web.xml的配置内容,并指出可能的错误原因。该问题可能与jar包版本不兼容、web.xml配置错误等有关。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 本文介绍了使用postman进行接口测试的方法,以测试用户管理模块为例。首先需要下载并安装postman,然后创建基本的请求并填写用户名密码进行登录测试。接下来可以进行用户查询和新增的测试。在新增时,可以进行异常测试,包括用户名超长和输入特殊字符的情况。通过测试发现后台没有对参数长度和特殊字符进行检查和过滤。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 目录浏览漏洞与目录遍历漏洞的危害及修复方法
    本文讨论了目录浏览漏洞与目录遍历漏洞的危害,包括网站结构暴露、隐秘文件访问等。同时介绍了检测方法,如使用漏洞扫描器和搜索关键词。最后提供了针对常见中间件的修复方式,包括关闭目录浏览功能。对于保护网站安全具有一定的参考价值。 ... [详细]
  • Apache Shiro 身份验证绕过漏洞 (CVE202011989) 详细解析及防范措施
    本文详细解析了Apache Shiro 身份验证绕过漏洞 (CVE202011989) 的原理和影响,并提供了相应的防范措施。Apache Shiro 是一个强大且易用的Java安全框架,常用于执行身份验证、授权、密码和会话管理。在Apache Shiro 1.5.3之前的版本中,与Spring控制器一起使用时,存在特制请求可能导致身份验证绕过的漏洞。本文还介绍了该漏洞的具体细节,并给出了防范该漏洞的建议措施。 ... [详细]
  • 本文介绍了禅道作为一款国产开源免费的测试管理工具的特点和功能,并提供了禅道的搭建和调试方法。禅道是一款B/S结构的项目管理工具,可以实现组织管理、后台管理、产品管理、项目管理和测试管理等功能。同时,本文还介绍了其他软件测试相关工具,如功能自动化工具和性能自动化工具,以及白盒测试工具的使用。通过本文的阅读,读者可以了解禅道的基本使用方法和优势,从而更好地进行测试管理工作。 ... [详细]
  • Tomcat安装与配置教程及常见问题解决方法
    本文介绍了Tomcat的安装与配置教程,包括jdk版本的选择、域名解析、war文件的部署和访问、常见问题的解决方法等。其中涉及到的问题包括403问题、数据库连接问题、1130错误、2003错误、Java Runtime版本不兼容问题以及502错误等。最后还提到了项目的前后端连接代码的配置。通过本文的指导,读者可以顺利完成Tomcat的安装与配置,并解决常见的问题。 ... [详细]
  • 浅解XXE与Portswigger Web Sec
    XXE与PortswiggerWebSec​相关链接:​博客园​安全脉搏​FreeBuf​XML的全称为XML外部实体注入,在学习的过程中发现有回显的XXE并不多,而 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • java布尔字段用is前缀_POJO类中布尔类型的变量都不要加is前缀详解
    前言对应阿里巴巴开发手册第一章的命名风格的第八条。【强制】POJO类中布尔类型的变量都不要加is前缀,否则部分框架解析会引起序列化错误。反例:定义为基本 ... [详细]
author-avatar
wsl伊人
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有