How to Detect 0-day Vulnerabilities
A 0-day vulnerability is unknown to, or unaddressed by, developers and security researchers and is considered a severe threat. Until an 0-day vulnerability is identified and mitigated, hackers can exploit it.
This use case is based on CVE-2018-19859, a vulnerability allowing an attacker to execute arbitrary file writes through OpenRefine.
CVE-2018-19859
CVE-2018-19859 describes a directory traversal attack that can be exploited as an arbitrary file write. The vulnerability is rooted in an unsafe handling of ZIP files, a vulnerability pattern often seen by security researchers, analysts, and penetration testers.
OpenRefine is described by its authors as "a free, open source power tool for working with messy data and improving it". A common use case for OpenRefine is the sanitization of messy public data sets prior to statistical calculations, for which it provides features such as importing and exporting of data that may be scattered among multiple files or archives.
Creating the CPG
OpenRefine is not distributed as a single JAR file, as expected by the Ocular tool java2cpg
. However, java2cpg
does not require its input file to comply with a specific file structure as long as its input is provided in the ZIP format. This format is basically equivalent to a JAR archive; a ZIP file with a .jar
suffix containing the .class
files of interest is sufficient. Based on the OpenRefine sources, which you can download as a .tar.gz
archive, prepare the .jar
for java2cpg
by executing
wget https://github.com/OpenRefine/OpenRefine/releases/download/3.1/openrefine-linux-3.1.tar.gz
tar xfz openrefine-linux-3.1.tar.gz
find openrefine-3.1 -name "*.class" | zip openrefine.jar -@
Then run java2cpg
with the -w
flag, followed by a comma-separated list of package-names (com.google.refine
, org.openrefine
) to be included in the CPG.
Only the parts of the application with the package prefixes com.google.refine
and org.openrefine
are included in the CPG. For relatively large applications
such as OpenRefine, focusing on specific application parts can save computing and analysis time.
./java2cpg.sh openrefine.jar -w com.google.refine,org.openrefine -nb -o openrefine.bin.zip
Finally, load the newly created CPG into Ocular
sl ocular
importCpg("openrefine.bin.zip")
Identifying Sources
Input sources (importers) represent program points where potentially malicious (attacker-controlled) data may enter the system. Using Ocular, you can search and define an input source.
OpenRefine relies on importing and exporting data. Use the search strategy to look for imports by identifying all methods that contain the substring Import
in their
full method name. This search strategy returns 502 methods, too large a number to be inspected manually. You can filter the search in order to further narrow down the number of methods by
cpg.method.fullName(".*Import.*").toList.size
res1: Int = 502
Based on the naming convention often found in JavaServlet code, the list of methods is narrowed by including only methods accessible via HTTP. This is done through the addition of do(Get|Post)
to the search pattern: HTTP Get handlers are named doGet
, while HTTP POST handlers are named doPost
. By executing this query, the number of results is reduced to only 13 methods, a small enough number to inspect manually.
cpg.method.fullName(".*Import.*do(Get|Post).*").toList.size
res2: Int = 13
Some methods are stored in a class DefaultImportingController
which, as suggested by its name, may be interesting from a security standpoint. The query is enhanced by replacing Import
with DefaultImportingController
. The new search result consists of a doGet
and a doPost
method
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)
Based on these results, and by only issuing three queries, a source is found as the starting point for the security analysis. This following query defines a
source by applying the search filter. To be more specific, the query also specifies the required type of the parameters (which is HttpServletRequest
)
def source = cpg.method.fullName(".*DefaultImportingController.*do(Get|Post).*").parameter.evalType(".*HttpServletRequest.*")
Looking for the Sink
Sinks are security-sensitive program points to which malicious, attacker-controlled input (coming from the sink) may flow.
Based on the OpenRefine CVE, you can find and define sinks with Ocular, specifically unzipping-vulnerabilities.
Using a very basic query, first identify methods which are part of the zip
package; among them calls to methods with getName
as part of their name. As a result, the method explodeArchive
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)
Judging from its name, the function explodeArchive
appears to be worth investigating. This query tags the parameter of the method explodeArchive
as a sink
def sink = cpg.method.name("explodeArchive").parameter
To find a possible data flow between sources and sinks, issue a reachableBy
query, as in the snippet
sink.reachableBy(source).flows.p
Resulting Flow
By issuing the reachableBy
query, a detailed picture about the dataflow is provided, which starts from the doPost
method and the parameter named request
of type
HttpServletRequest
. Reviewing the flow, it is apparent that OpenRefine:
- Retrieves content from a POST request (
retrieveContentFromPostRequest
) - A method named
download
uses a variable namedurlString
saveStream
is consuming aurl
- Files are allocated (
allocateFile
) - The methods
tryOpenAsArchive
are called - Unpacks
explodeArchive
with aZipInputStream
variable namedarchiveIS
.
In summary, OpenRefine downloads data based on a URL, reads it as ZipInputStream
and tries to unpack it with the method explodeArchive
.
________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________
| param | type | method | signature |
|=======================================================================================================================================================================================================================================================================================================================================================================|
| request(1) | javax.servlet.http.HttpServletRequest | doPost | com.google.refine.importing.DefaultImportingController.doPost:void(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) |
| request | javax.servlet.http.HttpServletRequest | doPost | com.google.refine.importing.DefaultImportingController.doPost:void(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) |
| request(1) | javax.servlet.http.HttpServletRequest | doLoadRawData | com.google.refine.importing.DefaultImportingController.doLoadRawData:void(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.util.Properties) |
| request | javax.servlet.http.HttpServletRequest | doLoadRawData | com.google.refine.importing.DefaultImportingController.doLoadRawData:void(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.util.Properties) |
| request(1) | javax.servlet.http.HttpServletRequest | loadDataAndPrepareJob | com.google.refine.importing.ImportingUtilities.loadDataAndPrepareJob:void(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.util.Properties,com.google.refine.importing.ImportingJob,org.json.JSONObject) |
| request | javax.servlet.http.HttpServletRequest | loadDataAndPrepareJob | com.google.refine.importing.ImportingUtilities.loadDataAndPrepareJob:void(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.util.Properties,com.google.refine.importing.ImportingJob,org.json.JSONObject) |
| request(1) | javax.servlet.http.HttpServletRequest | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| request | javax.servlet.http.HttpServletRequest | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| param0(1) | javax.servlet.http.HttpServletRequest | parseRequest | org.apache.commons.fileupload.servlet.ServletFileUpload.parseRequest:java.util.List(javax.servlet.http.HttpServletRequest) |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| tempFiles | java.util.List | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| tempFiles | java.util.List | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| this(0) | java.util.List | iterator | java.util.List.iterator:java.util.Iterator() |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| l12_0 | java.util.Iterator | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| l12_0 | java.util.Iterator | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| this(0) | java.util.Iterator | next | java.util.Iterator.next:java.lang.Object() |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| $r2 | java.lang.Object | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| $r2 | java.lang.Object | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| param1(2) | ANY | <operator>.cast | <operator>.cast |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| fileItem | org.apache.commons.fileupload.FileItem| retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| fileItem | org.apache.commons.fileupload.FileItem| retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| this(0) | org.apache.commons.fileupload.FileItem| getInputStream | org.apache.commons.fileupload.FileItem.getInputStream:java.io.InputStream() |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| stream | java.io.InputStream | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| stream | java.io.InputStream | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| param0(1) | java.io.InputStream | asString | org.apache.commons.fileupload.util.Streams.asString:java.lang.String(java.io.InputStream) |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| urlString | java.lang.String | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| urlString | java.lang.String | retrieveContentFromPostRequest| com.google.refine.importing.ImportingUtilities.retrieveContentFromPostRequest:void(javax.servlet.http.HttpServletRequest,java.util.Properties,java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress) |
| urlString(6)| java.lang.String | download | com.google.refine.importing.ImportingUtilities.download:void(java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$SavingUpdate,java.lang.String) |
| urlString | java.lang.String | download | com.google.refine.importing.ImportingUtilities.download:void(java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$SavingUpdate,java.lang.String) |
| urlString(6)| java.lang.String | download | com.google.refine.importing.ImportingUtilities.download:void(java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$SavingUpdate,java.lang.String,java.lang.String) |
| urlString | java.lang.String | download | com.google.refine.importing.ImportingUtilities.download:void(java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$SavingUpdate,java.lang.String,java.lang.String) |
| param0(1) | java.lang.String | <init> | java.net.URL.<init>:void(java.lang.String) |
| url | java.net.URL | download | com.google.refine.importing.ImportingUtilities.download:void(java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$SavingUpdate,java.lang.String,java.lang.String) |
| url | java.net.URL | download | com.google.refine.importing.ImportingUtilities.download:void(java.io.File,org.json.JSONObject,com.google.refine.importing.ImportingUtilities$Progress,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$SavingUpdate,java.lang.String,java.lang.String) |
| url(2) | java.net.URL | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| url | java.net.URL | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| this(0) | java.net.URL | getPath | java.net.URL.getPath:java.lang.String() |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| localname | java.lang.String | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| localname | java.lang.String | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| param0(1) | java.lang.String | append | java.lang.StringBuilder.append:java.lang.StringBuilder(java.lang.String) |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| $r1 | java.lang.StringBuilder | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| $r1 | java.lang.StringBuilder | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| this(0) | java.lang.StringBuilder | append | java.lang.StringBuilder.append:java.lang.StringBuilder(java.lang.String) |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| $r2 | java.lang.StringBuilder | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| $r2 | java.lang.StringBuilder | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| this(0) | java.lang.StringBuilder | toString | java.lang.StringBuilder.toString:java.lang.String() |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| localname | java.lang.String | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| localname | java.lang.String | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| name(2) | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| this(0) | java.lang.String | substring | java.lang.String.substring:java.lang.String(int,int) |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| param1(2) | java.lang.String | <init> | java.io.File.<init>:void(java.io.File,java.lang.String) |
| file | java.io.File | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| file | java.io.File | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| file | java.io.File | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| file | java.io.File | saveStream | com.google.refine.importing.ImportingUtilities.saveStream:boolean(java.io.InputStream,java.net.URL,java.io.File,com.google.refine.importing.ImportingUtilities$Progress,com.google.refine.importing.ImportingUtilities$SavingUpdate,org.json.JSONObject,org.json.JSONArray,long)|
| file(2) | java.io.File | postProcessRetrievedFile | com.google.refine.importing.ImportingUtilities.postProcessRetrievedFile:boolean(java.io.File,java.io.File,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress) |
| file | java.io.File | postProcessRetrievedFile | com.google.refine.importing.ImportingUtilities.postProcessRetrievedFile:boolean(java.io.File,java.io.File,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress) |
| file(1) | java.io.File | tryOpenAsArchive | com.google.refine.importing.ImportingUtilities.tryOpenAsArchive:java.io.InputStream(java.io.File,java.lang.String,java.lang.String) |
| file | java.io.File | tryOpenAsArchive | com.google.refine.importing.ImportingUtilities.tryOpenAsArchive:java.io.InputStream(java.io.File,java.lang.String,java.lang.String) |
| param0(1) | java.io.File | <init> | java.io.FileInputStream.<init>:void(java.io.File) |
| $r7 | java.io.FileInputStream | tryOpenAsArchive | com.google.refine.importing.ImportingUtilities.tryOpenAsArchive:java.io.InputStream(java.io.File,java.lang.String,java.lang.String) |
| $r7 | java.io.FileInputStream | tryOpenAsArchive | com.google.refine.importing.ImportingUtilities.tryOpenAsArchive:java.io.InputStream(java.io.File,java.lang.String,java.lang.String) |
| param0(1) | java.io.InputStream | <init> | java.util.zip.ZipInputStream.<init>:void(java.io.InputStream) |
| $r6 | java.util.zip.ZipInputStream | tryOpenAsArchive | com.google.refine.importing.ImportingUtilities.tryOpenAsArchive:java.io.InputStream(java.io.File,java.lang.String,java.lang.String) |
| $r6 | java.util.zip.ZipInputStream | tryOpenAsArchive | com.google.refine.importing.ImportingUtilities.tryOpenAsArchive:java.io.InputStream(java.io.File,java.lang.String,java.lang.String) |
| param1(2) | ANY | <operator>.assignment | <operator>.assignment |
| archiveIS | java.io.InputStream | postProcessRetrievedFile | com.google.refine.importing.ImportingUtilities.postProcessRetrievedFile:boolean(java.io.File,java.io.File,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress) |
| archiveIS | java.io.InputStream | postProcessRetrievedFile | com.google.refine.importing.ImportingUtilities.postProcessRetrievedFile:boolean(java.io.File,java.io.File,org.json.JSONObject,org.json.JSONArray,com.google.refine.importing.ImportingUtilities$Progress) |
| archiveIS(2)| java.io.InputStream | explodeArchive | 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) |
Detecting a Vulnerable Flow
After looking for sources and sinks, you can refine your search to detect an actual vulnerability.
A ZipInputSteam
is already controlled, but for the vulnerability you need to find a flow to a File
or better a FileOutputStream
. In this query, use the
FileOutputStream
constructor as a sink, more specifically, as the first parameter of the constructor.
def source = cpg.method.name("explodeArchive").parameter
def sink = cpg.method.fullName(".*FileOutputStream.*init.*").parameter.index(1)
By issuing a reachableBy
query, various flows are found, since there are many FileInputStream
sinks. To reduce the number of flows, provide additional expert knowledge: that an attacker can control the destination path to which a ZIP file is extracted by passing malicious input through the getName
method. After applying the passes
filter on the flows, the number of flows is reduced to 2
!
sink.reachableBy(source).flows.l.size
res28: Int = 1847
sink.reachableBy(source).flows.passes("getName").l.size
res29: Int = 2
The resulting flow is as follows:
___________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________
| param | type | method | signature |
|==========================================================================================================================================================================================================================================================================|
| archiveIS(2)| java.io.InputStream | explodeArchive | 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)|
| archiveIS | java.io.InputStream | explodeArchive | 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)|
| param1(2) | ANY | <operator>.cast | <operator>.cast |
| param1(2) | ANY | <operator>.assignment| <operator>.assignment |
| zis | java.util.zip.ZipInputStream| explodeArchive | 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)|
| zis | java.util.zip.ZipInputStream| explodeArchive | 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)|
| this(0) | java.util.zip.ZipInputStream| getNextEntry | java.util.zip.ZipInputStream.getNextEntry:java.util.zip.ZipEntry() |
| param1(2) | ANY | <operator>.assignment| <operator>.assignment |
| ze | java.util.zip.ZipEntry | explodeArchive | 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)|
| ze | java.util.zip.ZipEntry | explodeArchive | 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)|
| param1(2) | ANY | <operator>.assignment| <operator>.assignment |
| ze | java.util.zip.ZipEntry | explodeArchive | 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)|
| ze | java.util.zip.ZipEntry | explodeArchive | 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)|
| this(0) | java.util.zip.ZipEntry | getName | java.util.zip.ZipEntry.getName:java.lang.String() |
| param1(2) | ANY | <operator>.assignment| <operator>.assignment |
| fileName2 | java.lang.String | explodeArchive | 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)|
| fileName2 | java.lang.String | explodeArchive | 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)|
| name(2) | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| this(0) | java.lang.String | substring | java.lang.String.substring:java.lang.String(int,int) |
| param1(2) | ANY | <operator>.assignment| <operator>.assignment |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| param1(2) | ANY | <operator>.assignment| <operator>.assignment |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| name | java.lang.String | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| param1(2) | java.lang.String | <init> | java.io.File.<init>:void(java.io.File,java.lang.String) |
| file | java.io.File | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| file | java.io.File | allocateFile | com.google.refine.importing.ImportingUtilities.allocateFile:java.io.File(java.io.File,java.lang.String) |
| param1(2) | ANY | <operator>.assignment| <operator>.assignment |
| file2 | java.io.File | explodeArchive | 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)|
| file2 | java.io.File | explodeArchive | 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)|
| file(2) | java.io.File | saveStreamToFile | com.google.refine.importing.ImportingUtilities.saveStreamToFile:long(java.io.InputStream,java.io.File,com.google.refine.importing.ImportingUtilities$SavingUpdate) |
| file | java.io.File | saveStreamToFile | com.google.refine.importing.ImportingUtilities.saveStreamToFile:long(java.io.InputStream,java.io.File,com.google.refine.importing.ImportingUtilities$SavingUpdate) |
| param0(1) | java.io.File | <init> | java.io.FileOutputStream.<init>:void(java.io.File)
Verifying the Vulnerability
A flow from doPost
to explodeArchive
and from explodeArchive
to a FileOutputStream
instance is controlled. In order to verify the existence of a vulnerability, there is only one remaining question: is the file actually written? To answer this question, look at the write
method of the FileOutputStream
, which is used as a sink. In addition to the reachableBy
query, use the passesNot
filter, which ensures that only flows without an uncompressFile
method, a method that that handles .gz
and .bz2
, are considered.
def source = cpg.method.name("explodeArchive").parameter
def sink = cpg.method.fullName(".*FileOutputStream.*write.*").parameter.index(1)
sink.reachableBy(source).passesNot("uncompressFile").p
The result is as follows:
___________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________
| param | type | method | signature |
|==========================================================================================================================================================================================================================================================================|
| archiveIS(2)| java.io.InputStream | explodeArchive | 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)|
| archiveIS | java.io.InputStream | explodeArchive | 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)|
| param1(2) | ANY | <operator>.cast | <operator>.cast |
| param1(2) | ANY | <operator>.assignment| <operator>.assignment |
| zis | java.util.zip.ZipInputStream| explodeArchive | 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)|
| zis | java.util.zip.ZipInputStream| explodeArchive | 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)|
| stream(1) | java.io.InputStream | saveStreamToFile | com.google.refine.importing.ImportingUtilities.saveStreamToFile:long(java.io.InputStream,java.io.File,com.google.refine.importing.ImportingUtilities$SavingUpdate) |
| stream | java.io.InputStream | saveStreamToFile | com.google.refine.importing.ImportingUtilities.saveStreamToFile:long(java.io.InputStream,java.io.File,com.google.refine.importing.ImportingUtilities$SavingUpdate) |
| this(0) | java.io.InputStream | read | java.io.InputStream.read:int(byte[]) |
| bytes | byte[] | saveStreamToFile | com.google.refine.importing.ImportingUtilities.saveStreamToFile:long(java.io.InputStream,java.io.File,com.google.refine.importing.ImportingUtilities$SavingUpdate) |
| bytes | byte[] | saveStreamToFile | com.google.refine.importing.ImportingUtilities.saveStreamToFile:long(java.io.InputStream,java.io.File,com.google.refine.importing.ImportingUtilities$SavingUpdate) |
| param0(1) | byte[] | write | java.io.FileOutputStream.write:void(byte[],int,int) |
Conclusion
The vulnerability CVE-2018-19859 in OpenRefine can be detected with Ocular using explodeArchive
and write
(from FileOutputStream
) as sources and
sinks, respectively. Furthermore, the existence of the vulnerability is validated. However, you would still need to create an exploit to see if it is really exploitable.