Responding to the Log4shell incident

How an organisation could respond to the Log4Shell (CVE-2021-44228) security vulnerability, if they were using dependency-management-data.

(Note: the Log4Shell security vulnerability predates dependency-management-data, but this is a good opportunity to look into how we could respond to an incident like this)

Context

Via CISA's documentation on Log4Shell:

Log4Shell, disclosed on December 10, 2021, is a remote code execution (RCE) vulnerability affecting Apache’s Log4j library, versions 2.0-beta9 to 2.14.1. The vulnerability exists in the action the Java Naming and Directory Interface (JNDI) takes to resolve variables. Affected versions of Log4j contain JNDI features—such as message lookup substitution—that do not protect against adversary-controlled Lightweight Directory Access Protocol (LDAP), Domain Name System (DNS), and other JNDI-related endpoints.

An adversary can exploit Log4Shell by submitting a specially crafted request to a vulnerable system that causes that system to execute arbitrary code. The request allows the adversary to take full control over the system. The adversary can then steal information, launch ransomware, or conduct other malicious activity.

This was a significantly impactful incident across the industry due to the pervasiveness of Log4J2 being used in many different types of application, from web services, to local developer tools, to toasters!

If an organisation had their dependencies catalogued using a Software Composition Analysis (SCA) platform, or through one of the means that dependency-management-data supports, determining the impact was made much easier.

Problem

Data

In the renovate table, we have:

platform organisation repo package_name version current_version package_manager package_file_path datasource dep_types
github elastic logstash org.apache.logging.log4j:log4j-core 2.17.1 2.17.1 gradle logstash-core/build.gradle maven ["dependencies"]
gitlab jamietanna fake-private-repo org.apache.logging.log4j:log4j-core 2.13.0 2.13.0 gradle blank-java/build.gradle maven ["dependencies"]
gitlab jamietanna fake-private-repo org.apache.logging.log4j:log4j-core 2.13.2 2.13.2 maven blank-java/pom.xml maven ["compile"]
github cucumber cucumber-jvm org.apache.logging.log4j:log4j-core 2.17.1 2.17.1 maven pom.xml maven ["build","missing-data"]
github elastic logstash org.apache.logging.log4j:log4j-core 2.17.1 2.17.1 gradle logstash-core/build.gradle maven ["dependencies","missing-data"]
github elastic examples org.apache.logging.log4j:log4j-core 2.9.1 2.9.1 maven Search/recipe_search_java/pom.xml maven ["compile","missing-data"]
gitlab jamietanna fake-private-repo org.apache.logging.log4j:log4j-core 2.13.0 2.13.0 gradle blank-java/build.gradle maven ["dependencies","missing-data"]
gitlab jamietanna fake-private-repo org.apache.logging.log4j:log4j-core 2.13.0 2.13.0 maven blank-java/pom.xml maven ["compile","missing-data"]
gitlab jamietanna fake-private-repo org.apache.logging.log4j:log4j-core 2.13.0 2.13.0 maven blank-java/pom.xml maven ["build","missing-data"]

In the owners table, we have:

platform organisation repo owner notes updated_at
gitlab jamietanna fake-private-repo Jamie Tanna 2024-01-18T10:54:05Z
github elastic examples Elastic 2024-01-18T10:54:05Z
github elastic logstash Elastic 2024-01-18T10:54:05Z

And in the repository_metadata table:

platform organisation repo is_monorepo is_fork repository_type repository_usage visibility description additional_metadata
gitlab jamietanna fake-private-repo FALSE FALSE service PRIVATE
github elastic examples FALSE FALSE examples PUBLIC
github elastic logstash FALSE FALSE service PUBLIC

Query

To start with, we could use the dependenton report to get a high-level view of all uses of the dependency:

# for Gradle projects
$ dmd report dependenton --db dmd.db --package-manager gradle --package-name org.apache.logging.log4j:log4j-core
+----------+--------------+------------------------------------------+---------+-----------------+---------------------------------+----------------------------+-------------+
| PLATFORM | ORGANISATION | REPO                                     | VERSION | CURRENT VERSION | DEPENDENCY TYPES                | FILEPATH                   | OWNER       |
+----------+--------------+------------------------------------------+---------+-----------------+---------------------------------+----------------------------+-------------+
| github   | elastic      | logstash                                 | 2.17.1  | 2.17.1          | ["dependencies","missing-data"] | logstash-core/build.gradle | Elastic     |
| github   | elastic      | logstash                                 | 2.17.1  | 2.17.1          | ["dependencies"]                | logstash-core/build.gradle | Elastic     |
| gitlab   | jamietanna   | fake-private-repo                        | 2.13.0  | 2.13.0          | ["dependencies","missing-data"] | blank-java/build.gradle    | Jamie Tanna |
| gitlab   | jamietanna   | fake-private-repo                        | 2.13.0  | 2.13.0          | ["dependencies"]                | blank-java/build.gradle    | Jamie Tanna |
+----------+--------------+------------------------------------------+---------+-----------------+---------------------------------+----------------------------+-------------+
# for Maven projects
$ dmd report dependenton --db dmd.db --package-manager maven --package-name org.apache.logging.log4j:log4j-core
+----------+--------------+------------------------------------------+---------+-----------------+----------------------------+-----------------------------------+-------------+
| PLATFORM | ORGANISATION | REPO                                     | VERSION | CURRENT VERSION | DEPENDENCY TYPES           | FILEPATH                          | OWNER       |
+----------+--------------+------------------------------------------+---------+-----------------+----------------------------+-----------------------------------+-------------+
| github   | cucumber     | cucumber-jvm                             | 2.17.1  | 2.17.1          | ["build","missing-data"]   | pom.xml                           |             |
| github   | elastic      | examples                                 | 2.9.1   | 2.9.1           | ["compile","missing-data"] | Search/recipe_search_java/pom.xml | Elastic     |
| gitlab   | jamietanna   | fake-private-repo                        | 2.13.0  | 2.13.0          | ["build","missing-data"]   | blank-java/pom.xml                | Jamie Tanna |
| gitlab   | jamietanna   | fake-private-repo                        | 2.13.0  | 2.13.0          | ["compile","missing-data"] | blank-java/pom.xml                | Jamie Tanna |
| gitlab   | jamietanna   | fake-private-repo                        | 2.13.2  | 2.13.2          | ["compile"]                | blank-java/pom.xml                | Jamie Tanna |
+----------+--------------+------------------------------------------+---------+-----------------+----------------------------+-----------------------------------+-------------+
# and any other JVM ecosystems that you may use

Alternatively, we could use the GraphQL query dependenton:

{
  dependentOn(
    packageManager: "gradle"
    packageName: "org.apache.logging.log4j:log4j-core"
  ) {
    repositories {
      platform
      organisation
      repo
      # other fields could be added, too
    }
  }
}

Which would return the following results:

{
  "data": {
    "dependentOn": {
      "repositories": [
        {
          "platform": "github",
          "organisation": "elastic",
          "repo": "logstash"
        },
        {
          "platform": "github",
          "organisation": "elastic",
          "repo": "logstash"
        },
        {
          "platform": "gitlab",
          "organisation": "jamietanna",
          "repo": "fake-private-repo"
        },
        {
          "platform": "gitlab",
          "organisation": "jamietanna",
          "repo": "fake-private-repo"
        }
      ]
    }
  }
}

Alternatively, we could write the following query:

select
  platform,
  organisation,
  repo,
  current_version
from
  renovate
where
  package_name = 'org.apache.logging.log4j:log4j-core'

This provides the following data:

platform organisation repo current_version
github elastic logstash 2.17.1
gitlab jamietanna fake-private-repo 2.13.0
gitlab jamietanna fake-private-repo 2.13.2
github cucumber cucumber-jvm 2.17.1
github elastic logstash 2.17.1
github elastic examples 2.9.1
gitlab jamietanna fake-private-repo 2.13.0
gitlab jamietanna fake-private-repo 2.13.0
gitlab jamietanna fake-private-repo 2.13.0

However, if we wanted to find affected versions, we'd need a SQL query like so:

select
  platform,
  organisation,
  repo,
  current_version
from
  renovate
where
  package_name = 'org.apache.logging.log4j:log4j-core'
  and current_version in (
    '2.0-beta9',	'2.0-rc1',
    '2.0-rc2',		'2.0.1',
    '2.0.2',		'2.0',
    '2.1',			'2.2',
    '2.3.1',		'2.3.2',
    '2.3',			'2.4.1',
    '2.4',			'2.5',
    '2.6.1',		'2.6.2',
    '2.6',			'2.7',
    '2.8.1',		'2.8.2',
    '2.8',			'2.9.0',
    '2.9.1',		'2.10.0',
    '2.11.0',		'2.11.1',
    '2.11.2',		'2.12.0',
    '2.12.1',		'2.12.2',
    '2.12.3',		'2.12.4',
    '2.13.0',		'2.13.1',
    '2.13.2',		'2.13.3',
    '2.14.0',		'2.14.1',
  )

This then returns us:

platform organisation repo current_version
gitlab jamietanna fake-private-repo 2.13.0
gitlab jamietanna fake-private-repo 2.13.2
github elastic examples 2.9.1
gitlab jamietanna fake-private-repo 2.13.0
gitlab jamietanna fake-private-repo 2.13.0
gitlab jamietanna fake-private-repo 2.13.0

And to list the owners:

select
  renovate.platform,
  renovate.organisation,
  renovate.repo,
  current_version,
  owner
from
  renovate
  left join owners on renovate.platform = owners.platform
  and renovate.organisation = owners.organisation
  and renovate.repo = owners.repo
where
  package_name = 'org.apache.logging.log4j:log4j-core'
  and current_version in (
    '2.0-beta9',	'2.0-rc1',
    '2.0-rc2',		'2.0.1',
    '2.0.2',			'2.0',
    '2.1',				'2.2',
    '2.3.1',			'2.3.2',
    '2.3',				'2.4.1',
    '2.4',				'2.5',
    '2.6.1',			'2.6.2',
    '2.6',				'2.7',
    '2.8.1',			'2.8.2',
    '2.8',				'2.9.0',
    '2.9.1',			'2.10.0',
    '2.11.0',			'2.11.1',
    '2.11.2',			'2.12.0',
    '2.12.1',			'2.12.2',
    '2.12.3',			'2.12.4',
    '2.13.0',			'2.13.1',
    '2.13.2',			'2.13.3',
    '2.14.0',			'2.14.1',
  )

Which then returns:

platform organisation repo current_version owner
gitlab jamietanna fake-private-repo 2.13.0 Jamie Tanna
gitlab jamietanna fake-private-repo 2.13.2 Jamie Tanna
github elastic examples 2.9.1 Elastic
gitlab jamietanna fake-private-repo 2.13.0 Jamie Tanna
gitlab jamietanna fake-private-repo 2.13.0 Jamie Tanna
gitlab jamietanna fake-private-repo 2.13.0 Jamie Tanna

And finally, prioritise which repositories are public:

select
  renovate.platform,
  renovate.organisation,
  renovate.repo,
  current_version,
  owner,
  (
    case visibility when 'PUBLIC' then true
    else false
    end
  ) as is_public
from
  renovate
  left join owners on renovate.platform = owners.platform
  and renovate.organisation = owners.organisation
  and renovate.repo = owners.repo
  left join repository_metadata on renovate.platform = repository_metadata.platform
  and renovate.organisation = repository_metadata.organisation
  and renovate.repo = repository_metadata.repo
where
  package_name = 'org.apache.logging.log4j:log4j-core'
  and current_version in (
    '2.0-beta9',	'2.0-rc1',
    '2.0-rc2',		'2.0.1',
    '2.0.2',		'2.0',
    '2.1',			'2.2',
    '2.3.1',		'2.3.2',
    '2.3',			'2.4.1',
    '2.4',			'2.5',
    '2.6.1',		'2.6.2',
    '2.6',			'2.7',
    '2.8.1',		'2.8.2',
    '2.8',			'2.9.0',
    '2.9.1',		'2.10.0',
    '2.11.0',		'2.11.1',
    '2.11.2',		'2.12.0',
    '2.12.1',		'2.12.2',
    '2.12.3',		'2.12.4',
    '2.13.0',		'2.13.1',
    '2.13.2',		'2.13.3',
    '2.14.0',		'2.14.1',
  )
order by is_public desc

Which then produces:

platform organisation repo current_version owner is_public
gitlab jamietanna fake-private-repo 2.13.0 Jamie Tanna 0
gitlab jamietanna fake-private-repo 2.13.2 Jamie Tanna 0
github elastic examples 2.9.1 Elastic 1
gitlab jamietanna fake-private-repo 2.13.0 Jamie Tanna 0
gitlab jamietanna fake-private-repo 2.13.0 Jamie Tanna 0
gitlab jamietanna fake-private-repo 2.13.0 Jamie Tanna 0

Alternatively, we could leverage writing custom policies which would allow us to write the following policy:

package policy

import rego.v1

default advisory_type := "SECURITY"

# prefer the `version` as versions are usually exactly pinned, and means we don't need to handle the absense of the `current_version` i.e. https://gitlab.com/tanna.dev/dependency-management-data/-/issues/77
versions := split(input.dependency.version, ".")

major := to_number(versions[0])

minor := to_number(versions[1])

patch := to_number(versions[2])

is_log4j2 if {
	# this could be avoided, if you want to avoid having an exhaustive list of all possibilities
	input.dependency.package_manager in {"gradle", "maven"}
	input.dependency.package_name = "org.apache.logging.log4j:log4j-core"

	major == 2
}

# CVE-2021-44228 aka Log4shell affects versions 2.0-beta9 to 2.14.1
is_vulnerable_version if input.dependency.version in {"2.0-beta9", "2.0-rc1", "2.0-rc2"}

# CVE-2021-44228 aka Log4shell affects versions 2.0-beta9 to 2.14.1
is_vulnerable_version if {
	minor > 0
	minor <= 14
}

deny contains msg if {
	is_log4j2
	is_vulnerable_version
	msg := "Dependency is vulnerable to Log4Shell CVE (CVE-2021-44228)"
}

This allows us to simplify the logic for which versions are in-scope, and then outputs the following:

$ dmd policy evaluate --db dmd.db log4shell.rego
Processing log4shell.rego resulted in 6 policy violations, from 146323 dependencies:
+----------+--------------+------------------------------------------+-------------------------------------+----------+---------------------------------+-----------------------------------+-------+---------------+------------------------------------------------------------+
| PLATFORM | ORGANISATION | REPO                                     | PACKAGE                             | VERSION  | DEPENDENCY TYPES                | FILEPATH                          | LEVEL | ADVISORY TYPE | DESCRIPTION                                                |
+----------+--------------+------------------------------------------+-------------------------------------+----------+---------------------------------+-----------------------------------+-------+---------------+------------------------------------------------------------+
| github   | elastic      | examples                                 | org.apache.logging.log4j:log4j-core | 2.9.1 /  | ["compile","missing-data"]      | Search/recipe_search_java/pom.xml | ERROR | SECURITY      | Dependency is vulnerable to Log4Shell CVE (CVE-2021-44228) |
|          |              |                                          |                                     | 2.9.1    |                                 |                                   |       |               |                                                            |
| gitlab   | jamietanna   | localstack-docker-reuse-not-staying-open | org.apache.logging.log4j:log4j-core | 2.13.0 / | ["dependencies","missing-data"] | blank-java/build.gradle           | ERROR | SECURITY      | Dependency is vulnerable to Log4Shell CVE (CVE-2021-44228) |
|          |              |                                          |                                     | 2.13.0   |                                 |                                   |       |               |                                                            |
| gitlab   | jamietanna   | localstack-docker-reuse-not-staying-open | org.apache.logging.log4j:log4j-core | 2.13.0 / | ["dependencies"]                | blank-java/build.gradle           | ERROR | SECURITY      | Dependency is vulnerable to Log4Shell CVE (CVE-2021-44228) |
|          |              |                                          |                                     | 2.13.0   |                                 |                                   |       |               |                                                            |
| gitlab   | jamietanna   | localstack-docker-reuse-not-staying-open | org.apache.logging.log4j:log4j-core | 2.13.0 / | ["build","missing-data"]        | blank-java/pom.xml                | ERROR | SECURITY      | Dependency is vulnerable to Log4Shell CVE (CVE-2021-44228) |
|          |              |                                          |                                     | 2.13.0   |                                 |                                   |       |               |                                                            |
| gitlab   | jamietanna   | localstack-docker-reuse-not-staying-open | org.apache.logging.log4j:log4j-core | 2.13.2 / | ["compile"]                     | blank-java/pom.xml                | ERROR | SECURITY      | Dependency is vulnerable to Log4Shell CVE (CVE-2021-44228) |
|          |              |                                          |                                     | 2.13.2   |                                 |                                   |       |               |                                                            |
| gitlab   | jamietanna   | localstack-docker-reuse-not-staying-open | org.apache.logging.log4j:log4j-core | 2.13.0 / | ["compile","missing-data"]      | blank-java/pom.xml                | ERROR | SECURITY      | Dependency is vulnerable to Log4Shell CVE (CVE-2021-44228) |
|          |              |                                          |                                     | 2.13.0   |                                 |                                   |       |               |                                                            |
+----------+--------------+------------------------------------------+-------------------------------------+----------+---------------------------------+-----------------------------------+-------+---------------+------------------------------------------------------------+