Abstract. Many applications in the enterprise world feature thick Java clients. Testing the security of such applications is considered practically more difficult than a similar browser-based client because inspecting, intercepting and altering application data is easy in the browser. With DOM inspection tools like Firebug and WebKit Web Inspector, and HTTP proxy tools such as WebScarab, Fiddler and Burp, assessing the trust boundary between the client and server has become mostly commoditized in web applications.
Security practitioners have been struggling to reach the same level of effectiveness when testing thick Java clients. Researchers have previously tried to statically alter the application code through decompilation and recompilation to add BeanShell script “hooks”. Also, work has been done to create proxies that can parse simple serialized objects, a common way of sending data between a Java client and server. The purpose of this paper is to describe an alternate approach to testing the security of a Java application. This approach utilizes instrumentation and Java agents to make altering traffic, inspecting data and otherwise attacking a Java application endpoint much easier than ever before. The implementation of this approach is a tool called JavaSnoop.
Statically altering the client
Statically altering client programs (or, “hacking the client”) in RIAs is not easy. Many RIAs live out their entire lifecycle in the memory of an already-existing process. Altering this memory to cause malicious application behaviour is not cost-effective. Reverse engineering memory layout and safely altering process memory directly are both extremely difficult. For RIAs that live on disk, altering clients has been shown to be at least marginally more viable. The RIA discussed in the paper, Java, should in theory be accommodating to this approach. If an attacker has access to a binary (such as a JAR file), they should be able to perform the following steps:
1. Decompile the code from binary to pure source
2. Alter the code to either contain stageloading code or attack code directly
3. Recompile the code from the altered source and re-run
Although relatively time consuming given the constraints of a typical security assessment, this approach seems reasonable on paper. Unfortunately, in practice this process turns out to be very error-prone. Decompiling binary Java often results in source code that has a number of compilation errors. These errors are introduced by bugs in the decompilers themselves or the result of special build processes, which show that the compilation and decompilation processes are not, in practice, 100% deterministic. These compilation bugs are costly to chase down and fix, especially when combined with obfuscated code. Overcoming these hurdles make for a protracted process which often ends up not producing the “evil client” due to time restrictions. There is also the unfortunate fact that many security testers are not qualified technically to “recover” such decompiled source. On top of this, test iterations with this method are slow compared to approaches where the application’s generated traffic is altered in transit.
Intercepting application communication
The second option for sending test attacks to the server is to alter the application’s traffic. If the application uses HTTP (as many do), efficient testing is easy as configuring the application to use an HTTP testing proxy as would be done for a normal browser client. The application may have an interface for setting up a proxy, or the default proxy for the Java process can be set with command-line switches to the Java executable. Altering the command line for a Java Web Start (JWS) program or an Applet is possible but not straightforward.
Users can hook public or private Java API as long as the method is not native. Hooking Java API in Applets with anything besides custom-written scripts will fail because of the nature of the Applet class loader and some of the security restrictions on Applets. Most of the time, however, users will be more interested in hooking an application’s custom code.
Searching by method name or class
Users can look up classes to hook methods within if they know or can guess the relevant class name. Many applets have a relatively small number of classes, which makes browsing the entire class tree relatively quick. Alternatively, users can search for methods by name.
Even after searching and guessing, it may be difficult to find what methods to intercept. It’s likely that attackers are interested in methods where data they put into the UI ends up going. If the flow of their data through the class methods could somehow be seen, it may end helping the user find functions to hook. Discovering this lifetime is the purpose of “Canary Mode”, a unique and useful feature of JavaSnoop. In this mode, you define some “canary” value that you want to trace through the system. This should be some unique value that you’re going to enter into the application somewhere, probably through a form field or a properties file. Once this value is chosen, Canary Mode can be started. JavaSnoop will then remove all other hooks currently in use, and then add canary “listeners” to every method in the JVM that has the data type of the canary as a parameter. Each time the canary is found being sent to a method, a “chirp” is sent back to JavaSnoop, letting the user know what method operated on the canary value. In a way, this amounts to a very primitive, clumsy form of data flow analysis. Instrumenting a sizeable percentage of methods in the JVM is a process costly in cycles. And once those methods are instrumented, the application will perform slowly since the canary “listeners” are being executed constantly. Therefore, Canary Mode is a standalone modal that cannot be executed concurrently with other hooks. It is also possible to limit the methods hooked to a certain package. Canary Mode can be used with all primitive data types and the String object.
tricks of the (java-)hacking trade
One of the niftiest tools in the modern Java dev's pocket is Guava. We've mentioned this library before, but it bears mentioning again. It wraps up a ton of useful things into one handy place. Generally, if you want to do something that seems obvious, but that Java doesn't directly support, look in Guava. Their wrappers for all manner of collections are extremely handy, and save a lot of needless boilerplate code, while remaining efficient.