How-to Identify Directory Traversal Vulnerabilities with ShiftLeft Ocular: A Detailed Code Example

How-to Identify Directory Traversal Vulnerabilities with ShiftLeft Ocular: A Detailed Code Example with DeepLearning4j and OpenRefine (CVE-2018–19859)

In one of our most recent blog posts Chetan Conikee wrote about a directory traversal caused by carelessly unpacking a zip archive.

He explained the danger of this vulnerability category based on a his finding in DeepLearning4j. While his write up includes a mitigation, he left us with the questions how and if ShiftLeft can handle this vulnerability category.

Today we want to answer this questions by demonstrating how we can find zip archive based directory traversal (thus arbitrary file write) using the interactive ShiftLeft command line tool, ocular.

Same problem, new target

Before we start, let me introduce a new target, containing the unpacking vulnerability we will demo today, OpenRefine. OpenRefine is a a tool that supports the work with messy data by helping to improve it. Due the nature of messy data, it often comes distributed over multiple text files. So what could be better than having them zipped in a file? It is a no-brainer to imagine that this kind of tool relies on importing and exporting data as the mentioned zip format (along other).

Considering the information we already know, we can derive two assumptions that help us searching for the vulnerability!

  1. Our potential source is a data import.
  2. Our data sink is a file write, including a zip unpacking in advance.

Getting our hands dirty

Alright we set our goals, lets start digging for the vulnerability by looking for an import!

We fire up Ocular and load the Code Property Graph (CPG) by using the loadCpg method (creating the CPG is out of scope and explained in our docs)

ocular> loadCpg(“openrefine.bin.zip”) 
[…]

Finding the source

After a few seconds, we are ready to query for information about the applications and since we are in to find the import handler we are filtering all methods that have `Import` in their name.

ocular> cpg.method.fullName(“.*Import.*”).toList.size 
res1: Int = 502

Well, 502 is still too much for a manually review. Based on naming convention often found in Java Servlet code, we can narrow down the list of methods by including only methods accessible via HTTP through the addition of do(Get|Post) to our search pattern: HTTP Get handlers are named doGet, while HTTP POST handlers are named doPost. By executing the query below, the number of results is decreased to only 13 methods, a small enough number to be inspected manually.

ocular> cpg.method.fullName(“.*Import.*do(Get|Post).*”).toList.size 
res2: Int = 13

Some methods are attracting our attention because they are inside a DefaultImportingController! This sounds like a something we want to start from. We adjust our filter and find two methods, with a HttpServletRequest (also commonly found in Java Servlet code) parameter.

ocular> cpg.method
.fullName(".*DefaultImportingController."+
"*do(Get|Post).*")
.fullName.p
com.google.refine.importing.DefaultImportingController.doGet:void(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
com.google.refine.importing.DefaultImportingController.doPost:void(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)

Looks like we found our source with some easy intuitive queries, only. Since the HttpServletRequest is the attacker controlled parameter, we are going to track this parameter to the sink. This can be done by restricting the parameter by its evalType, meaning the type of the parameter (we are not interested in the name).

The final source can be defined by, the following query.

ocular> val source = cpg.method
.fullName(".*DefaultImportingController."+
"*do(Get|Post).*")
.parameter
.evalType(".*HttpServletRequest.*")

It can be rephrased to

Use all parameter of type HttpServletRequest that are parameter of methods containing DefaultImportingController and doPost or doGet in their names

Finding the sink

According to our second assumption, our data flow contains calls to the zip package, more precise to ZipEntry.getName, as we are behind unzipping. We can address both by a query for a method that should contain zip as well as getName in its name and we actually found one.

ocular> cpg.method.fullName(".*zip.*getName.*").caller.fullName.p 
com.google.refine.importing.ImportingUtilities.explodeArchive:boolean(java.io.File,java.io.InputStream,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress)

Great, now we know that the project is calling getName inside a zip package and the call is happening in a method named explodeArchive. Lets try to find a flow from our source to explodeArhive.

ocular> val source = cpg.method
.fullName(".*DefaultImportingController."+
"*do(Get|Post).*")
.parameter
.evalType(".*HttpServletRequest.*")
ocular> val sink = cpg.method.name("explodeArchive").parameter
ocular> sink.reachableBy(source).flows.p

We find a flow that is unfortunately too long to paste it here (but you can find it in our documentation page). This is a nice intermediate step, since we don’t have found the storage of the zip entry to the file system, yet.

One of the rising questions now are, do we find a flow between getName and a file write. For this we look into the methods that are called by explodeArchive and we see a promising method, saveStreamToFile, which takes an java.io.InputStream and java.io.File as parameter.

ocular> cpg.method.name("explodeArchive")
.callee.fullName.l.sorted.distinct

[..]
"com.google.refine.importing.ImportingUtilities.saveStreamToFile:long(java.io.InputStream,java.io.File,com.google.refine.importing.ImportingUtilities$SavingUpdate)",
"java.util.zip.ZipEntry.getName:java.lang.String()",
[..]
)

We can apply the same query for saveStreamToFile and see our sink, FileOutputStream.write!

ocular> cpg.method.name("saveStreamToFile")
.callee.fullName.l.sorted.distinct

[..]
"java.io.FileOutputStream.<init>:void(java.io.File)",
"java.io.FileOutputStream.close:void()",
"java.io.FileOutputStream.write:void(byte[],int,int)",
"java.io.InputStream.read:int(byte[])"
[..]

Searching for the flow

Here we reached a state where we have the source and a sink. Now we can query for the complete vulnerable flow.

Our source are the parameter of type HttpServletRequest of the methods doGet or doPost, inside the DefaultImportingController.

ocular> val source = cpg.method
.fullName(".*DefaultImportingController."+
"*do(Get|Post).*")
.parameter
.evalType(".*HttpServletRequest.*")

Our sink is defined by the first parameter of write.

ocular> val sink = cpg.method
.fullName(“.*FileOutputStream.*write.*”)
.parameter.index(1)

The following query prints all flows from our source to sinks that passes explodeArchive, allocateFile and saveStreamToFile. Before we search for the flow, we set the maxFlowLength to 300.

ocular> config.maxFlowLength = 300
ocular> sink.reachableBy(source)
.flows.passes("explodeArchive")
.passes("allocateFile")
.passes("saveStreamToFile").p

And we find our flow to FileOutputStream.write. Again this flow is too long to paste it here and can be found in our community docs.


How-to Identify Directory Traversal Vulnerabilities with ShiftLeft Ocular: A Detailed Code Example was originally published in ShiftLeft Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.

*** This is a Security Bloggers Network syndicated blog from ShiftLeft Blog - Medium authored by Niko Schmidt. Read the original post at: https://blog.shiftleft.io/how-to-identify-directory-traversal-vulnerabilities-with-shiftleft-ocular-a-detailed-code-example-2dbc471ba115?source=rss----86a4f941c7da---4