Rhino Security Labs

Exploiting CVE-2018-1335:
Command Injection in Apache Tika

March 12, 2019

Intro

This post is a walk-through of steps taken to go from an undisclosed CVE for a command injection vulnerability in the Apache tika-server to a complete exploit. The CVE is https://nvd.nist.gov/vuln/detail/CVE-2018-1335. Since Apache Tika is open source, I was able to take some basic information from the CVE and identify the actual issue by analyzing the Apache Tika code. Although a command injection vulnerability is typically straightforward, as you will see in this post there were some hurdles to overcome to achieve full remote code or command execution. This was due to the way Java handles executing operating system commands and also some intricacies of the Apache Tika code itself. In the end, it was still possible to get around these blockers using the Windows Script Host (Cscript.exe).

What is Apache Tika

The Apache Tika™ toolkit detects and extracts metadata and text from over a thousand different file types (such as PPT, XLS, and PDF). All of these file types can be parsed through a single interface, making Tika useful for search engine indexing, content analysis, translation, and much more. (https://tika.apache.org/)

Apache Tika has a few different components: a Java library, command line tool, and a standalone server (tika-server) with its own REST API. This exploit specifically is targeting the standalone server through the REST API it exposes https://wiki.apache.org/tika/TikaJAXRS. The vulnerable version is found here https://archive.apache.org/dist/tika/tika-server-1.17.jar.

Breaking Down The CVE

To start looking for the issue, we need to first read the CVE advisory and see what information can be taken from it to give a starting point of where to look.

The description from the original advisory:

Before Tika 1.18, clients could send carefully crafted headers to tika-server that could be used to inject commands into the command line of the server running tika-server.  This vulnerability only affects those running tika-server on a server that is open to untrusted clients.

Things we can tell from this description:

  • Version 1.18 is patched
  • Version 1.17 is unpatched
  • The vulnerability is command injection
  • The entry point for the vulnerability is “headers”
  • This affects the tika-server portions of the code

With this information, we now have a starting point to try and identify the vulnerability. The next steps would be to perform a diff of the patched and unpatched version of Tika, specifically the tika-server portions. Grepping the code for functions in Java known to perform operating system commands would be another good place to look. Finally, searching for sections of the tika-server code which relate to interpreting headers from what we can assume will be some kind of HTTP request.

Getting Into It

Doing a side-by-side recursive diff of the tika-server 1.17 vs 1.18 source directory only comes back with one file that has been modified. This is shown below cropping to just the important parts.

Diffing tika-1.17/tika-server/src/main/java/org/apache/tika/server/ tika-1.18/tika-server/src/main/java/org/apache/tika/server/

Since the goal is to find command injection in a header field, having the first result be a code block which has been added in the patched version called “ALLOWABLE_HEADER_CHARS” is a pretty good start. The assumption is that this is some kind of patch trying to filter characters which could be used to inject commands into the header field.

Continuing down is a large block of code inside of a function called “processHeaderConfig” which looks interesting and has been removed or changed in 1.18. It is using some variable to dynamically create a method which appears to set properties of some object and it uses the HTTP headers to do this.

apache/tika/tika-server/src/main/java/org/apache/tika/server/resource/TikaResource.java

Here is the description of this function:

apache/tika/tika-server/src/main/java/org/apache/tika/server/resource/TikaResource.java

The prefixes for the different properties were shown in the previous screenshot and are defined as static strings at the beginning of this code.

apache/tika/tika-server/src/main/java/org/apache/tika/server/resource/TikaResource.java

So, we have a couple static strings the can be included as HTTP headers with a request and used to set some property of an object. An example of the final header would look something like “X-Tika-OCRsomeproperty: somevalue”, “someproperty” then gets converted to a function that looks like “setSomeproperty()” and is invoked passing somevalue to it as the value to set.

apache/tika/tika-server/src/main/java/org/apache/tika/server/resource/TikaResource.java

Here you can see this function being used and where the prefix header is checked in the request to determine how to call the function. All the needed arguments are then passed in from the HTTP request to the “processHeaderConfig” function.

Looking at the way the “processHeaderConfig” function is used, you can see the properties are being set on the “TesseractOCRConfig” object. Doing a search for places that may use the “TesseractOCRConfig” object we find: tika-parsers/src/main/java/org/apache/tika/parser/ocr/TesseractOCRParser.java which turned out to be pretty interesting.

Here is the “doOCR” function from “TesseractOCRParser.java” which is passing the config properties from the “TesseractOCRConfig” object, which we just discovered, directly into an array of strings which are used to construct a command for “ProcessBuilder” and then the process is started.

apache/tika/tika-server/src/main/java/org/apache/tika/parser/ocr/TesseractOCRParser.java

This looks promisingif we put together all the information we have found so far we should technically be able to make some kind of HTTP request to the server, set a header that looks like “X-Tika-OCRTesseractPath: <some command>” and have this command be inserted into the cmd string and be executed. The only problem is is the “config.getTesseractPath()” is prepended to another string we cannot control, “getTesseractProg()” which ends up being a static string, “tesseract.exe”. To fix this we can wrap our command we want to execute in double quotes and Windows will ignore whatever is appended to it after the quotes, just executing our injected command.

To put this to the test we can just use an example from the tika-server documentation for retrieving some metadata about a file.

https://wiki.apache.org/tika/TikaJAXRS

Since OCR stands for Optical Character Recognition, used for pulling text and content out of images, we will use an image to upload instead of a docx to hopefully reach the “doOCR” function.

We end up with:

curl -T test.tiff http://localhost:9998/meta --header "X-Tika-OCRTesseractPath: \"calc.exe\""

There you have itthe command injection was identified by wrapping a command in double quotes as the value for the “X-Tika-OCRTesseractPath” HTTP header in a PUT request while uploading an image.

Can you do more than pop calc?

At this point, you can see that we are just directly changing the application name that is executed. Because the command is being passed to Java ProcessBuilder as an array, we cannot actually run more than one command or add arguments to the command as a single string or the execution will fail. This is because passing an array of strings to process builder or runtime.exec in Java works like this:

Characters that are normally interpreted by shells like cmd.exe or /bin/sh such as &,<,>,|,` etc. are not interpreted by ProcessBuilder and will be ignored, so you cannot break out of the command or add any arguments to it as a single string. It is not as simple as doing something like “X-Tika-OCRTesseractPath: \“cmd.exe /c some args\”, or any combination of this.

Coming back to the construction of the “cmd” array you can see we have control over multiple arguments in the command as well, this is each item that looks like “config.get*()” but this is broken up by some other items we do not control.

My first thought was to run “cmd.exe” and then pass in the argument “/C” as “config.getLanguage()” and then insert “||somecommand||” as “config.getPageSegMode()” which would have resulted in “somecommand” being executed. However, this did not work because prior to “doOCR” being called there is another function which is called on the “config.getTesseractPath()” string (the modified command) which simply executes just that command (the purpose was to check if the application being called is a valid application). The problem here is that would just run “cmd.exe” with no arguments and cause the server to hang since “cmd.exe” would never exit and let execution continue to the “doOCR” function.

Coming Up With a Solution

To go beyond running a single command we can take a deeper look at what happens when the “doOCR” function starts the process using Process Monitor. Viewing the properties of the process, when the tika-server starts it, results in the following command line which is constructed with the injected command.

"calc.exe"tesseract.exe C:\Users\Test\AppData\Local\Temp\apache-tika-3299124493942985299.tmp C:\Users\Test\AppData\Local\Temp\apache-tika-7317860646082338953.tmp -l eng -psm 1 txt -c preserve_interword_spaces=0

The portions of the command we control are highlighted in red. There are 3 places we can inject into the command, 1 command and 2 arguments. Another interesting finding here is that Tika is actually creating 2 temp files and one of them is being passed as the first argument.

After some further investigation I was able to confirm that the first temp file passed to the command was the contents from the file I was uploading. This meant maybe I could fill that file with some code or command and execute that.

Now I had to find a native Windows application that will ignore all the random stray arguments created by tika-server and still execute the first files contents as some kind of command or code even though it has a “.tmp” extension. Finding something that would do all this sounded very unlikely to me at first. After clicking around https://github.com/api0cradle/LOLBAS for a while looking at LOLBins thinking maybe I could get lucky, I came across Cscript.exe and it looked somewhat promising. Let’s take a look at what Cscript can do.

Cscript turned out to be just what was needed. It takes the first argument as a script and allows you to use the “//E:engine” flag to specify what script engine you want to use (this could be Jscript or VBS), so the file extension does not matter. Putting this into the new command would now look like the following.

"cscript.exe"tesseract.exe C:\Users\Test\AppData\Local\Temp\apache-tika-3299124493942985299.tmp C:\Users\Test\AppData\Local\Temp\apache-tika-7317860646082338953.tmp -l //E:Jscript -psm 1 txt -c preserve_interword_spaces=0

This is done by setting the following HTTP headers:

X-Tika-OCRTesseractPath: "cscript.exe"
X-Tika-OCRLanguage: //E:Jscript

The “image” file that will be uploaded will contain some Jscript or VBS:

var oShell = WScript.CreateObject("WScript.Shell");
var oExec = oShell.Exec('cmd /c calc.exe');

At first, uploading an image with those contents failed since it was not a valid image and it could not verify the magic bytes of the image. I then found that setting the content-type to “image/jp2” forces Tika to not check magic bytes in the image but still process the image through OCR. This allowed an image containing Jscript to be uploaded.

Finally, putting all this together, we have full command/jscript/vbs execution.

Conclusion

What seemed to be a simple command injection bug turned out to have quite a few blockers to overcome in order to actually exploit it. It was interesting trying to come up with a method of getting around each hurdle. Although this was difficult to exploit it was still possible to do it and reiterates the point that you should never use untrusted input when constructing operating system commands. Apache does not suggest running the Tika-server in an untrusted environment or exposing it to untrusted users. This bug has also been patched and the current version is 1.20 so make sure you update if you are using this service.

You can find the PoC in the Rhino Security Lab’s CVE repo: https://github.com/RhinoSecurityLabs/CVEs/tree/master/CVE-2018-1335