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

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 CRLFHeaders = *[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.1Host: www.myhost.comContent-Type: application/x-www-form-urlencodedContent-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元素“<methodCall></methodCall>”开头。此元素包含一个子元素“<methodName>something</methodName>”。元素“<methodName>”包含子元素“<params>”,该子元素可以包含一个或多个“<param>”元素。param XML元素可以包含许多数据类型。

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

<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.XmlRpcRequestParserorg.apache.xmlrpc.parser.RecursiveTypeParserImplorg.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数据时,便会触发该漏洞。

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

文章标题:APACHE OFBIZ XMLRPC远程代码执行漏洞实例分析,发布者:亿速云,转载请注明出处:https://worktile.com/kb/p/24541

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
亿速云的头像亿速云认证作者
上一篇 2022年9月13日 下午11:30
下一篇 2022年9月13日 下午11:32

相关推荐

  • 电脑键盘不能打字变成快捷键了怎么解决

    电脑键盘不能打字变成快捷键解决方法 名列前茅种方法 最简单的就是重启一下电脑,方便快捷的解决问题。 第二种方法 1,一般情况下是电脑键盘上的“windows“键出问题了,应该是压下后未弹起,可以先检查一下。 “windows“键就是ctrl和alt键之间的 2,WIN键(也就是那个开机四个方块图样的…

    2022年9月6日
    5.9K00
  • cad块名称如何修改

    cad块名称修改方法: 1、首先打开CAD软件。 2、然后点击上面任务选项栏中的“格式”。 3、然后点击菜单中的“重命名”。 4、最后点击左侧的“块”然后编辑右侧的名字即可。 以上就是“cad块名称如何修改”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家…

    2022年8月30日
    93400
  • Vue.js 前端路由和异步组件是什么

    目录 文章目标 P6 P6+ ~ P7 一、背景 二、前端路由特性 三、面试!!! 四、Hash 原理及实现 1、特性 2、如何更改 hash 3、手动实现一个基于 hash 的路由 五、History 原理及实现 1、HTML5 History 常用的 API 2、pushState/replac…

    2022年9月13日
    53200
  • Mysql体系化之JOIN运算实例分析

    SQL中的JOIN SQL是如何理解JOIN运算 SQL对JOIN的定义 两个集合(表)做笛卡尔积后再按某种条件过滤,写出来的语法就是A JOIN B ON …。 理论上讲,笛卡尔积的结果集应该是以两个集合成员构成的二元组作为成员,不过由于SQL中的集合也就是表,其成员总是有字段的记录,而且也不支持…

    2022年9月6日
    61400
  • MySQL外键约束知识点有哪些

    一、MySQL外键约束作用 外键约束(Foreign Key)即数据库中两个数据表之间的某个列建立的一种联系。这种联系通常是以实际场景中含义完全相同的字段所造成的。MySQL通过外键约束的引入,可以使得数据表中的数据完整性更强,也更符合显示情况。下面,我举一个例子来说明MySQL外键约束的作用。 假…

    2022年9月15日
    1.2K00
  • 电脑黑白打印机打印图片底色黑如何解决

    解决方法 方法一: 1、首先在“Photoshop”中打开想要打印的图片。 2、打开后,点击左上角“图像”,选择“自动色调” 3、软件会自动为你调整背景色,如果还是觉得黑,可以继续调节。 4、继续点击“图像”,打开“调整”下的“色阶”选项。 5、打开后,按住图示位置的滑块,向左滑动。(根据预览情况自…

    2022年9月24日
    1.1K00
  • 狩猎内网信息侦察工具Goddi怎么用

    Goddi Goddi 是 NetSPI 使用 Go 语言编写的工具,该工具有助于收集 Active Directory 域信息,被认为是 BloodHound、ADInfo、PowerSploit 和 windapsearch 等其他几种常见工具的替代方案。 Goddi 依赖对域控进行一系列自定义…

    2022年9月26日
    36900
  • internet中怎么达到共享传输线路的目的

    internet中主要采用路由技术来达到共享传输线路的目的;路由技术主要是指路由选择算法、因特网的路由选择协议的特点及分类,其中路由选择算法可以分为静态路由选择算法和动态路由选择算法,因特网的路由选择协议的特点是属于自适应的选择协议,是分布式路由选择协议,采用分层次的路由选择协议。 本教程操作环境:…

    2022年8月30日
    30600
  • mysql的2002错误怎么解决

    在mysql中,2022错误指的是编译的时候没有指定socket,所以mysql命令连接的时候还是使用的默认值,因为socket位置变了,而mysql命令不知道,所以就出现了这样的错误,可以修改“/etc/my.cnf”文件来解决该错误。 本教程操作环境:windows10系统、mysql8.0.2…

    2022年9月1日
    1.1K00
  • MySQL约束与多表查询实例分析

    本篇内容主要讲解“MySQL约束与多表查询实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“MySQL约束与多表查询实例分析”吧! 1.约束 概述 概念:约束是作用于表中字段上的规则,用于限制存储在表中的数据。 目的:保证数据库中数据的正确、有效性和…

    2022年9月18日
    79200
站长微信
站长微信
电话联系

400-800-1024

工作日9:30-21:00在线

分享本页
返回顶部