0

0

APACHE OFBIZ XMLRPC远程代码执行漏洞实例分析

PHPz

PHPz

发布时间:2023-05-14 08:10:11

|

1950人浏览过

|

来源于亿速云

转载

概述

研究人员报告了一个存在于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类型:

A1.art
A1.art

一个创新的AI艺术应用平台,旨在简化和普及艺术创作

下载
<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>

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

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

16

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

23

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

75

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

95

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

218

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

420

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

168

2026.03.04

Swift iOS架构设计与MVVM模式实战
Swift iOS架构设计与MVVM模式实战

本专题聚焦 Swift 在 iOS 应用架构设计中的实践,系统讲解 MVVM 模式的核心思想、数据绑定机制、模块拆分策略以及组件化开发方法。内容涵盖网络层封装、状态管理、依赖注入与性能优化技巧。通过完整项目案例,帮助开发者构建结构清晰、可维护性强的 iOS 应用架构体系。

222

2026.03.03

C++高性能网络编程与Reactor模型实践
C++高性能网络编程与Reactor模型实践

本专题围绕 C++ 在高性能网络服务开发中的应用展开,深入讲解 Socket 编程、多路复用机制、Reactor 模型设计原理以及线程池协作策略。内容涵盖 epoll 实现机制、内存管理优化、连接管理策略与高并发场景下的性能调优方法。通过构建高并发网络服务器实战案例,帮助开发者掌握 C++ 在底层系统与网络通信领域的核心技术。

33

2026.03.03

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号