Log4Shell (CVE-2021-44228, CVE-2021-45046)

Log4Shell Logo

Logo by LunaSec

Log4Shell

Log4Shell is a series of remote code executions (CVE-2021-44228, CVE-2021-45046) in Log4j 2.x, which is a widely used Java logging library.
These vulnerabilities allow a crafted string to execute arbitrary code when it’s logged, therefore it could be used to achieve unauthenticated remote code execution in various products or services.

Is it serious?

Since CVE-2021-44228 allows arbitrary code execution in default settings of Log4j 2.x, it affected many companies including Microsoft, Google, and Amazon.
Also, attacks of CVE-2021-44228 were started before Apache Foundation release the patched version, so it was a 0-day vulnerability.

Mitigation

As there is an additional denial of service vulnerability (CVE-2021-45105) in Log4j 2.16.0, you must upgrade Log4j to 2.17.0 to be secure.

While 2.15.0 is trying to mitigate these vulnerabilities, there are some incomplete fixes for CVE-2021-44228, which results in CVE-2021-45046.

Details of CVE-2021-44228

CVE-2021-44228 was discovered by Chen Zhaojun of Alibaba Cloud Security Team.

The root cause of CVE-2021-44228 is a feature in Log4j 2.x called “Message Lookups”.
This feature allows log messages to look up some dynamic variables, such as environment variables.
For example, if you logged the following message with unpatched Log4j 2.x, it will print the PATH environment variable instead of the raw message:

${env:PATH} 

In this feature, there was support for JNDI, and it could be used by messages like this:

${jndi:java:comp/env}

Since Log4j 2.x did not validate the JNDI URI, it was possible to use arbitrary JNDI URIs.
As Alvaro Muñoz and Oleksandr Mirosh demonstrated in Black Hat USA 2016, and described in the blog post by Michael Stepankin, allowing arbitrary JNDI URIs can result in arbitrary code execution.

Due to the fact “Message Lookups” was enabled by default in Log4j 2.x, it was possible to execute arbitrary code if user input is printed to log with the default configuration of Log4j 2.x.

Details of CVE-2021-45046

CVE-2021-45046 was initially discovered by Kai Mindermann of iC Consult and separately by 4ra1n as a denial of service vulnerability.
Later, Ash Fox of Google, Anthony Weems of Praetorian, Alvaro Muñoz and Tony Torralba from GitHub, and RyotaK (@ryotkak) figured out independently that CVE-2021-45046 can be used to leak the information or execute arbitrary code remotely in some environments, and privilege escalation in all environments where affected by CVE-2021-45046.
This vulnerability isn’t exploitable in the default configuration of Log4j 2.15.0.

The root cause of CVE-2021-45046 is the incomplete fix for CVE-2021-44228.

Fix for CVE-2021-44228 can be broadly divided into three categories:

  1. Disable message lookups by default.
  2. Restrict hosts that JNDI can connect to.
  3. Restrict classes that JNDI can deserialize.

Disable message lookups by default

The first one, “Disable message lookups by default” was incomplete because there was still a code path in Log4j where message lookups could occur; Context Lookup.
This one was discovered by Kai Mindermann of iC Consult on December 14 and separately by 4ra1n on December 10 and reported to the Log4j team independently.

Restrict hosts that JNDI can connect to

The second one, “Restrict hosts that JNDI can connect to” was incomplete because of the parser difference.
Log4j 2.15.0 uses “java.net.URI” to validate hostnames, but JNDI uses “com.sun.jndi.toolkit.url.Uri” to parse URIs internally.
While “java.net.URI” supports characters such as “@” or “#” to parse URI, “com.sun.jndi.toolkit.url.Uri” does not support them, and resulting in the validation bypass.
Due to the strange behavior of getaddrinfo in macOS, these invalid hostnames will work correctly on macOS.
In other environments, such as Linux, send DNS requests to the DNS server but don’t resolve addresses correctly, so only information leak or privilege escalation is possible.
This one was discovered by Ash Fox of Google, Anthony Weems of Praetorian, Alvaro Muñoz and Tony Torralba from GitHub, and RyotaK (@ryotkak) independently.

Restrict classes that JNDI can deserialize

The third one, “Restrict classes that JNDI can deserialize” was incomplete because of the improper validation of serialized data.
There are two important values in the response of LDAP, called javaClassName javaSerializedData.
Normally, javaClassName contains class name of the serialized data in javaSerializedData, and javaSerializedData contains Java object that is serialized by ObjectOutputStream.

While javaClassName is checked by Log4j 2.x to restrict classes that JNDI can deserialize, Java doesn’t check javaClassName before deserializing javaSerializedData.
Therefore, simply specifying the allowed class name in javaClassName, and specifying a serialized Java object that will trigger the deserialization chain in javaSerializedData will result in unsafe deserialization.
This one was initially disclosed by Jeff Yemin of MongoDB, Inc.
Later, used to achieve remote code execution by combining with the second one.

Márcio Almeida managed to reproduce RCE and disclosed the PoC on Twitter.

References