How a single request can kill your enterprise
This post was originally posted at dev.karakun.com
In this post, I will show a possible exploit of a small Java based enterprise application. The bugs that I use for the exploit are already fixed since 2017 and new versions of the libraries are available for more than a year. I do not want to blame any library or developer with this blog post. My main goal is to show you how important it is to know the dependencies of your application and the importance of having an up-to-date version of your dependencies. By updating to the latest releases you will always benefit from security fixes and avoid being attackable by exploits that were made available to the public.
Creating a small server application
To give you an example of a security issue and an exploit that will use this issue to do really bad things I will create a minimalistic server application in Java. The application will handle some data items and will provide an HTTP endpoint so that a client can receive the current item set and mutate the data.
To create such an application you can choose between several Java based frameworks like Spring (Boot) or JakartaEE. In this specific example, I decided to use one of the smallest frameworks that I know for such a use case. By using spark I can easily define a small HTTP server in just 1 Java class. To make life easier I will add jackson and xalan as additional dependencies to provide automatic JSON-Object-Mapping for my application. Since I decided to use Maven for this example the
pom.xml file might look like this:
As you can see in the maven definition our application will only need 3 dependencies. Having a deeper look you realize that we already depend on 23 libraries as the 3 dependencies defined in the pom file depend on several other libraries. All these dependencies are transitive dependencies of our application and will be added to the classpath. The following graph shows all libraries our application depends on based on the above pom:
Most dependencies in the graph are transitive dependencies from spark. Even if this library is one of the smallest HTTP server libs available it already brings a lot of things with it. In our example this is much more than we need since we do not want to add security, use websockets or program against the servlet API. This should not be any blame against spark. I just want to show you that your applications often depend on many more things than you might know. ;)
Let’s start coding
The first thing that we want to define is a plain POJO. I decided to call it “Product” and defined it as a small Java bean with some properties/fields. As you can see in the following definition of the class there is absolutely no magic in this code. I even do not define
hashcode() (which could be done but does not change anything in the sample). This “Product” data type will be the only data that our application can handle. Here is the complete code of the
In our application, we want to manage the above products. Instead of using a database and maybe JPA (as most of us would do for real applications) I have chosen the easiest way to manage such data: a
List. By defining and managing collections in our application we can easily hold a list of products in memory and work with them at runtime. Since several clients might access our sever I decided to use a
CopyOnWriteArrayList as a concrete list type in my application. Thus, we won’t end in any
ConcurrentModificationException. The following code snippet shows how a collection with some initial data can be created:
randomData() method used in this snippet creates some random metadata for the
Product instances. You will see the code of this method later. For our example, these few lines of code are really everything we need as a small in-memory data store. By using a list we can easily add new products to our “database” or return the complete content.
In the next step, we will use spark to provide HTTP/rest endpoints for exactly this functionality. We want to define an endpoint that can be reached by a
GET request to return all our products and a second endpoint that can be reached by a
POST request. The POST endpoint will add a new product as defined in the body of the HTTP request to our database.
The following code snippet shows in a simplified way how this can be achieved by using spark:
For sure the post lambda needs null checks and exception handling. Plus, the
deserializer objects that are used in the snippet are instances of the
ObjectMapper class from jackson. These objects will be used to transform the JSON of the HTTP requests and responses to Java objects. Let’s put the database and server code together in a single Java class. As you can see in the following snippet you can easily create such simple servers with only 50 lines of code:
At this point, we can start our server and call the defined endpoints. To do such calls you can use any tool that provides the functionality to execute HTTP requests. I use PAW on my Mac but you can use any other tool like curl or postman for example. Also, all major browsers offer plugins or extensions to trigger HTTP requests.
The following snippet shows the raw content of a GET request to receive the product list from the server
When doing the request you will receive a response that contains the product list in JSON format as you can see in the following screenshot of Paw:
Once you added a new product by doing a
POST HTTP request you see the new product in this JSON list when doing a new
GET request. For posting a new product the body of your HTTP requests needs to look like this:
Thanks to the jackson and xalan dependency our server application will automatically create a new
Product instance from the given JSON definition.
Based on the current state of our application and the information we have about its implementation and endpoints we could consider our application as bulletproof that cannot be hacked in any way. Reviewing the following points might support this opinion:
- The application can only be accessed by 2 endpoints
- The endpoints are well defined and based on the HTTP standard
- Internally exception handling is added to the endpoints
- Internally the endpoints only have access to the data list
- The application is very small and therefore we understand the complete code
- We only use well-known dependencies
- All dependencies are open source
With all this in mind, you might be shocked when I tell you that I can get access to the native file system by only doing 1 HTTP request against this application. And: the file system is only one example. I showed the very same sample at a JUG session and created a request against the application that changed the wallpaper of my operation system. While this is not really dangerous it had a nice effect on the audience to visualize that I can really do more or less everything on the system that hosts the given application.
All this can be easily achieved by using a simple exploit. Everything I need to do is a
POST request against the endpoint that our application offers to add a new
Product instance. Instead of just sending a JSON based description of a product (which I want to add), the body of my HTTP request looks like this:
The content of the
transletBytecodes property is much longer than shown in this snippet. But since you cannot really read the content it doesn’t make sense to show it completely. Much more interesting is the general workflow that I used to create this HTTP body and how it will be handled in our server application.
The content of the
transletBytecodes property is a base64 encoded byte array. This byte array is the byte representation of a compiled Java class. To create the base 64 string I wrote a java class compiled it and simply converted the content of the class file to base64.
If you want to try this on your own just compile a Java class and use any converter to create a base64 based string out of the content of the class file. You can even create such tool by yourself with some lines of Java code:
The given code directly prints the base64 encoded string as the output of the program to your terminal. If you want to do this with a class that you can use for the exploit you need to extend a specific class that is defined by xalan. Simply add
org.apache.xalan.xsltc.runtime.AbstractTranslet class. The 2 abstract methods can be implemented with an empty body they are not used for the exploit. The really interesting part is the constructor of your class. Here you can easily add some custom code like I did in the following sample:
If you transform such a class to a base64 string as described above and send it to your server application the constructor of your custom class will be called on the server. With the given example the server would print “BOOOOOOOOM!” as output in the terminal. Before we have a look at the internals and how this is even possible think about the evil potential of this security issue. Instead of just printing a funny string we could write an algorithm that does horrible things on your system as you can see in the following code snippet:
Some background to the vulnerability
The exploit that I used in the sample is a security issue that occurred in several versions of the jackson-databind library (All versions before 126.96.36.199, 188.8.131.52 and 2.8.9 are affected). The issue was reported in 2017 and is fixed in new releases of the library. Internally the problem is defined like this:
jackson-databind will perform code execution by sending maliciously crafted input to the readValue method of the
You can find more information about the vulnerability here
This small example shows that even common and popular libraries can have security issues which can be used for exploits. Sometimes such bugs (and the exploits) can be harmless but the given sample shows how such exploits could kill your complete system. We as developers need to know about such problems in the libraries our systems depend on and try to get rid of them. The following thoughts can help us not to end in creating insecure applications or get hacked:
- The most important step is to know the internal dependencies and remove frameworks and libraries that you don’t really need.
- You should keep all your dependencies up-to-date.
- All security issues are captured in databases and tools can help us to check our applications and their dependencies against these databases.
I plan to write an article in the near future about helpful tools that will notify you automatically about possible security issues in your software stack.