Java applet + serialization in 2024! What could go wrong?

Recently, during a red team engagement with my colleague Maurizio, we came across a website that seemed very outdated. Quickly analyzing the HTML, we noticed something that brought a smile to our faces: a Java Applet.

First thought: the backend will definitely not work anymore… But what if it works! 😈

First, we needed to run the applet. A possible solution would be to run it in an old browser in a VM, but we would leave this option as a last resort. Instead, we installed a version of OpenJDK 8 and used the appletviewer binary to run the applet. We preferred to use a not-very-recent version of OpenJDK to minimize compatibility issues with the old Java version probably used to compile the applet, and if I’m not mistaken, the appletviewer binary is no longer available in the latest Java versions.

Of course, making it work and routing the traffic through Burp Proxy took some time and required fixing various issues related to signatures, certificates, TLS ciphers, proxies, and so on. Without delving into the details of all the issues, this is a summary of how we managed to make things work in our scenario.

First, we created the following Java policy to disable the security manager in the applet (saved in file “java.policy”):

grant {
  permission java.security.AllPermission;
};

Then, we ran the applet as follows (the URL is the URL of the page that starts the applet):

$ /usr/lib/jvm/java-8-openjdk/bin/appletviewer -J-Djava.security.manager -J-Djava.security.policy=java.policy https://<TARGET>/<PAGE>.htm

As I’ve said, making the applet work and routing the traffic through Burp Proxy took some time and required fixing various issues. One command-line option that helped a lot is “-J-Djavax.net.debug=all”, which prints precious debug information:

$ /usr/lib/jvm/java-8-openjdk/bin/appletviewer -J-Djava.security.manager -J-Djava.security.policy=java.policy -J-Djavax.net.debug=all https://<TARGET>/<PAGE>.htm

This way the applet started, but redirecting its traffic through Burp Proxy required a little more work: appletviewer supports HTTP proxy (that can be set directly in the options of the utility or using some Java environment variables), but unfortunately in our specific scenario it did not work correctly. We set up Burp Proxy as an invisible proxy, transparently redirecting the applet traffic to the HTTP port Burp Proxy was listening on.

To do this operation we had to first edit the /etc/hosts file (C:\Windows\System32\drivers\etc\hosts in Windows), adding a resolution of our target to 127.0.0.1 (assume www.target.com as our target):

127.0.0.1 www.target.com

Then we could setup an invisible proxy in the Proxy settings of Burp Suite as follows (you may have to run Burp Suite as root to bind on port 443):

Pay attention: In the second screenshot, I used the hostname of my target instead of its IP address in the “Redirect to host” field because my target had SNI enabled. To use the hostname, however, we need to add the correct DNS resolution of the hostname inside Burp Suite. Otherwise, it will resolve again to 127.0.0.1 due to our rule in the /etc/hosts file. If your target doesn’t have SNI enabled, you can simply put the IP address instead of the hostname in the proxy listener configuration. If your target has SNI enabled, you can add a custom DNS resolution in Burp Suite in the network settings:

The HTTP traffic of our applet reached Burp Proxy, but we got a certificate exception in the applet caused by the fact that Burp Suite’s certificate was self-signed and consequently not valid for the applet. To overcome this issue we simply installed the Burp Suite CA in the Java Keystore used for CA certificates (Burp Suite CA’s certificate can be exported from the Proxy Listener configuration tab in DER format and then can be converted to PEM format using OpenSSL):

$ /usr/lib/jvm/java-8-openjdk/bin/keytool -import -alias example -keystore /usr/lib/jvm/java-8-openjdk/jre/lib/security/cacerts -file <PATH>/certCABurp.pem

In order to prevent any further TLS issues we enabled all TLS protocols and ciphers in Burp Suite, including old and unsafe ones:

After all these configurations, the applet was working correctly and its HTTP traffic could be inspected in Burp Suite. First great thing: the backend was working correctly! It was a great thing because probably nobody was still using that legacy applet and so, it wasn’t a given.

Analyzing the traffic, we found exactly what we believed (and hoped) to find: serialized Java objects in the body of HTTP requests and responses (compressed with GZIP). Burp Suite tip: when you analyze an applet or more generally an application that is not a web application executed in the browser (such as a client-server application or a mobile application) remember that Burp Suite by default decompresses HTTP responses it receives from the backend to simplify the pentester’s analysis. While browsers do not complain if the content they receive from Burp Suite is not compressed, applets and other applications may break (and usually do). You can disable automatic decompression and other operations executed on HTTP requests and responses by default in the Proxy -> Miscellaneous configuration tab.

I believe that by now, almost everyone knows that serialized Java objects often provide an easy win. Well, in my opinion, they usually offer more than one easy win, but let’s go in proper order.

First easy win: RCE, thanks to the great work of Chris Frohoff and many other security researchers that contributed to his project, we have many different serialized objects that we can try to send to the backend in order to achieve RCE. You can generate such payloads using the ysoserial tool or using my fork that adds some additional detection techniques and encodings. Before digging manually with ysoserial, you can try my Java Deserialization Scanner that works quite well, using specific payloads I created with detection purposes (sometimes it gives some issues with the HTTP/2 protocol, I suspect due to the old legacy API, so I’m rewriting the extension with the Montoya API to solve any incompatibility issue with the protocol – Stay tuned!)

After several attempts, I was quite surprised not to have encountered any exploitable serialization issues. 😯 There are many ways to prevent exploitation of such issues but I didn’t expect them to be present in such an outdated application. Well, not so bad.

Let’s switch to the other easy wins that such feature usually offers. Our applet sends back and forth all the data in serialized Java objects. Such objects are in binary format, not easy to tamper without first converting them into a more convenient format. Modifying data in binary structures without understanding it and reconstructing it correctly usually leads to the creation of invalid objects, which are discarded by the backend. This, of course, causes a series of troubles, but on the other hand, we can hypothesize that not everyone who has faced this application in the past managed to handle the problem correctly, potentially leaving exploitable issues unaddressed. In simple terms, it means: if we can solve the format issue here, we probably will uncover some juicy vulnerabilities! 😜

Many years ago (9 😞) I released a Burp Suite extension to handle such objects named BurpJDSer-ng-edited. The extension was based on other two extensions, BurpJDSer-ng by omercnet and BurpJDSer by khai-tran, with some changes, fixes and improvements. The extension uses the XStream library to convert serialized Java objects to XML format, in order to allow easy analysis and tampering. XML objects are converted again to serialized Java objects before they are sent to the backend. This extension requires to run Burp Suite with some additional Java JAR archives in the classpath (the jar of XStream plus all the jar  archives of the applet) in order to allow our extension to deserialize Java objects and convert them to XML using XStream. Honestly, I didn’t think the extension would work without modifications in the new versions of Burp Suite, but it did (using the last available version of XStream)! I only had to modify a little the code because my applet used serialized Java object compressed with GZIP in requests, while my extension only supported compressed responses. But in half an hour, it worked like a charm! You can find the updated extension here.

Only a little final note if you need to use the extension. With old versions of Burp Suite we could simply run Burp Suite with “java -jar”, adding the classpath to the command line. With newer versions , it is better to go to the folder in which Burp Suite is installed (in my environment this is the BurpSuitePro folder in my home directory) and edit the BurpSuitePro.vmoptions file, adding the following line at the end of the file:

-classpath ".:<HOME>/BurpSuitePro/burpsuite_pro.jar:<XSTREAM_FOLDER>/xstream-1.4.20.jar:<APPLET_JAR_FOLDER>/appletJar1.jar:<APPLET_JAR_FOLDER>/appletJar2.jar"

The classpath should contain the current folder, the JAR of Burp Suite, the JAR of XStream and all the JARs of the target applet (in my example, appletJar1.jar and appletJar2.jar). With Windows you have to use the ‘;’ character as separator instead of ‘:’.

Then, you can start Burp as usual, load the extension, and pentest the application!

To conclude the story of my engagement, after dealing with serialization, I still had to address an encryption layer (easily manageable with a bit of reverse engineering on the decompiled applet code) and bingo! Tons of issues with authentication and authorization. A lot of dangerous functionalities exposed by the applet and, of course, some immortal SQL Injections 😁.

One juicy thing: having a decompilable applet as a client often comes with significant advantages. We can analyze the application code and often find functionalities not accessible from the graphical or administrative interface (often without any authorization check), which we can directly call and exploit. To do this, we can proceed in two ways: analyze the serialization mechanisms and the used objects and reconstruct them in the XStream XML format, or create a small Java project, import the applet JARs as dependencies, and directly instantiate and serialize the objects defined by the applet, either in binary format or in XML with Xstream. Finally, using serialized objects as a transport mechanism is also not a good idea for another reason. If the deserialization mechanisms are not carefully implemented, it is often possible to create serialized objects in unexpected and unreachable states compared to the methods defined in the code (for example, a constructor may check the input data, while the deserialization procedure may not, “trusting” that what it is deserializing was created by a legitimate and previously checked object). This further increases the attack surface, making it easier to bypass security measures and reaching unwanted and/or insecure states.

To summarize, what are the key takeways of this article?

  1. Pay attention to web applications. Java applets and more generally applications that run in old third-party browser plugins do not work anymore in modern browsers but can be a veeeery juicy target! Maybe we will release a Burp Suite plugin or some BChecks to detect their presence in the future. Stay tuned!
  2. Old technologies are often difficult to run and analyze but are an easy win if we succeeded in getting them to work! They are often not patched (and usually not maintained at all), forgotten by their owners, and skipped by previous pentesters.
  3. Pay attention to Burp Suite defaults when you work on non-web applications. Many Burp Suite defaults usually break communications with them.

To conclude, a small preview. Maurizio has worked in parallel on a possible different solution to the problem. His solution required a little more work to overcome the issues he faced but he did the job and he will release a very interesting article soon on the blog! Stay tuned!

Cheers!