Why the Log4j Vulnerability is so Serious
Deniz Karakaya
Software Consultant at Midgard Technology | Co-Founder of Midgard Technology | Chairman at Raven Technology
Log4j Issue
Everyone by now is likely familiar with the Log4j issue. If you haven't read about CVE-2021-44228, I recommend you immediately do so. I won't delve into the background of the exploit other than to disassemble it at a high level.
What strikes me most is the limitless number of potential exploit permutations that depends solely on what input a given application logs or doesn't log and where that input is ascertained from within the request. The myriad of possibilities is endless.
Where does the vulnerability reside?
The core issue lies in a fundamental logging engine used by many Java frameworks including Struts. Additionally, the popular logger is found in many enterprise applications. What makes this vulnerability so serious is that any element within the application that logs user input of any kind could potentially be an exploit vector. In fact, the component that is exploited may be deep in your application infrastructure, not just you edge web services. It could theoretically be exploited by a non-web interface as well. All your application needs to do is log user generated input.
This mean the vulnerability is in the logging mechanism itself:
logger.info("Expoit Variable {} Input", input)
Imagine the hundreds of lines of JAVA code that logs values which come from user input in your applications!
Where could this vector be used?
While the most obvious place to see this exploit is in HTTP headers,?any portion?of a web request could contain the exploit. In fact, it doesn't even need to be a web request. An RPC protocol could just as easily carry this payload. That is because the issue is not in the transport protocol, but the logger infrastructure itself.
Headers, Cookies, Parameters, URI components, Body elements all could contain the malicious content. All that is required is that the parsed and post-processed element of the request is logged.
This means that compressed payloads, encoded payloads and even encrypted payloads could potentially carry the exploit. For example, if a JWT token was decoded to produce the right string outcome and an element of the post-decoded token values carried the malicious payload and was subsequently logged, then the exploit could be realized.
Additionally, what makes this so insidious is that many encoding methods could also mask the actual exploit including various web encoding formats, UTF encodings, base64 encodings, escape-character encodings, RPC protocol formats, inline obfuscations, etc. All that is required is that post decoding/processing output parameter containing the exploit is logged.
While the HTTP Header techniques are well understood, let's explore the following JWT token carrying the same exploit:
On the wire a JWT payload will look something like this:
After decoding the payload, the internal elements of the purple portion of the JWT token look as follows:
How many places in a Java application will the value of 'name' be potentially logged?
The 'name' exploit payload shown above could alternatively be found in form data or URI parameters. Failed login? Log the?username?value. This would be common design pattern in many applications.
While searching your web input for '${jndi:ldap:' might yield some script-kiddie-level protections, they will not protect you from the capabilities of this exploit vector and a clever attacker. As shown above, a JWT could just as easily carry the exploit in a manner that would obfuscate it from any inline inspection mechanism. Additionally, these types of tokens can also be encrypted, not just encoded and signed.
What this means is that any payload that is post-processed and then logged, anywhere in the execution path of the workload, could potentially carry this exploit. As a result, the permutations of where the exploit could be hidden are essentially infinite.
Inline Exploitation
One of the things that might not be immediately apparent is that it could be possible to chain an attack together that is done entirely inline and is very difficult to detect. For example, if both your web server and application server contain the Log4j vulnerability, one could potentially craft a multistage attack that hops across the nodes using existing / expected connections and then exfiltrates database entries without the need for shells or calls outside of the normal ones used within the application. This is because once the attacker controls both the Web and App Server components they can return any response they want to the original request.
Using the JWT example show above, you could imagine the 'user' parameter being propagated from the Web Server to the App Server and logged in both workloads. Alternatively, another parameter might be used to attack the second stage once the first stage is compromised and then the database information is exfiltrated using entirely existing connections and methods.
It might look something like this:
Inline Lateral Movement
Similar to the example above, an attacker can compromise a node, scan for adjacent vulnerable nodes on the network and then move laterally to that next node. All of which could be done from within the workloads themselves, requiring no external shells or additional function calls outside what the workload already performs. Given the proliferation of HTTP and REST as a communication protocol it would not be that hard to image doing this all with just web requests. Additionally, it has become commonplace to support HTTP health checks in Cloud-Native environments, making the likelihood of some HTTP mechanism being present in a workload probable. Of course, nothing prevents trying different protocols along the way given enough time to explore, scan and iterate.
This might look like the following:
How did this happen?
Log4j 2.0 introduces lookups, including JNDI
The?Java Naming and Directory Interface?is a Java API used for a directory service that allows interfacing with LDAP, DNS, RMI, CORBA or any number of custom JNDI plugins to?lookup?resources and data.?As it turns out, one of the types that can be returned is a URI pointing to a Java class. This means that potentially malicious code could be downloaded and invoked inside your Java application at runtime.
A number of JDK versions will prevent the LDAP attack path as a result of the following parameter being set to false by default.
com.sun.jndi.ldap.object.trustURLCodebase = false
Anatomy of an Exploit
Below is a high-level representation of the exploit being performed using a simple HTTP header payload and LDAP. As noted earlier this payload could be anywhere in the request and not just an HTTP header. Additionally,?LDAP is not the only mechanism?to complete the circuit. The core requirement is that the post-processed element containing the malicious payload is logged by?Log4j?and that a JNDI path is available to complete the circuit.
How to Remediate the Log4j vulnerability
The easiest way to address this is to update Log4j to version 2.15.0 or greater, as this behavior is now disabled by default in newer versions.
As noted, some JDK versions have the property set to prevent the LDAP exploit vector as noted earlier in the post. However, there could be potentially other JNDI paths that are not disabled and might still allow an exploitation to occur.
In versions between 2.10 and 2.15.0 this exploit vector can be prevented by setting the system property?log4j2.formatMsgNoLookups?to?true?by adding the following Java parameter:?-Dlog4j2.formatMsgNoLookups=true to the command line arguments and restarting the application.
Removing the?JndiLookup?class from the classpath would also mitigate the issue, however this might break a number of other components in the process that require this behavior to function.
Conclusion