Log4jShell
All computer professionals should be aware of the Log4jShell (CVE-2021-44228) and it follow on defects. There is no shortage of opinions and lessons to be be learned:
- The difficulty of performing safe interpretation
- The problems when assumptions are not clearly documented. I, for one, was completely shocked to find out that a logging system would actually attempt to do variable substitution in an actual message.
- The difficulty of finding and resolving issues with such a common library that is not provided by an OS package manager.
IT'S A LOG4J CHRISTMAS
One of my favorite podcasts, Security Now - episode 850, discussed an analysis by Google regarding the depth of log4j dependencies. From the show notes:
One contributing reason is because Log4j is, more often than not, an indirect dependency. Java libraries are built by writing some code which uses functions from other Java libraries, which are built by writing some code which uses functions from other Java libraries... and so on. That’s really how it happens. So, the problem is more often not that a Java library directly uses Log4j (though 17% do). It’s that the other 83% instead use another Java library which might use Log4j, or might use another library that does, and so on. And as a result of this multiple depth inheritance, maintainers of vulnerable Java packages are forced to wait until other developers and maintainers of the packages they use have updated their own libraries.
Consumer Focus Alert
Steve's analysis assumes a passive approach to dealing with Java dependencies. One option is indeed to wait for various packages to update to newer versions. And, in some environments, it may be the right call, but that doesn't mean you should not explore additional options.
As a test, I took an old sample program I had that was coded against Apache Camel 2.23.0. The maven pom.xml contains
...
<dependencyManagement>
<dependencies>
<!-- Camel BOM -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-parent</artifactId>
<version>2.23.0</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
</dependency>
<!-- logging -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<scope>runtime</scope>
</dependency>
...
This gives a dependency hierarchy of:
Note that the dependent version of log4j (from Camel) is 2.11.1 and is a vulnerable version.
Passive update
The passive approach would be to update Apache Camel. If we do that, we could update to, say version 3.7.7. If we change:
<version>2.23.0</version>
to
<version>3.7.7</version>
then we get a totally different dependency hierarchy (clipped) that shows a dependency now on version 2.16.0 of log4j:
As well as all of the restructuring that was done. In my case, this application won't even compile without additional changes because one of the classes (org.apache.camel.main.Main) my sample depended on isn't available.
This means that I have to alter my code. It's not a hard change, it's a simple StackOverflow search away showing my how to restructure the code.
But what if it isn't my code? What happens if the problem is a library in a jar file that doesn't have a version available yet? What if I want to use log4j 2.17.0 (because 2.16.0 also has problems)?
Active update
Rather than force an update to the top level libraries, we can exclude the "bad" lower level libraries ( log4j) and then specify the exact version we wish to use:
...
<dependencyManagement>
<dependencies>
<!-- Camel BOM -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-parent</artifactId>
<version>2.23.0</version>
<scope>import</scope>
<type>pom</type>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
</dependency>
<!-- logging -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.17.0</version>
<scope>runtime</scope>
</dependency>
...
and then we get the dependency hierarchy we want:
Note that the 2.11.0 versions are omitted due to my overriding the excluded log4j 2.11.0 dependency and are replaced with log4j 2.17.0.
Analysis
The main reason to follow the active approach is time. You don't have to wait for anyone else to release updates. On the other hand, there are disadvantages you need to factor into your decision:
- You can only swap out versions if they are binary compatible with the JDK you are using. With a mature product like log4j, compatibility is quite high, but that may not be the case for other libraries.
- You now have a manual version override that you have to track. Each and every time you change a version of the dependent library you MUST reconsider library versions. At some point you will want to use a library that is dependent with a log4j version newer than 2.17.0.
Conclusion
Don't wait - if you are using Maven, please review https://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.html to explore your options.
If you are using Gradle, the equivalent documentation is at https://docs.gradle.org/current/userguide/dependency_downgrade_and_exclude.html with a nice discussion at https://discuss.gradle.org/t/how-do-i-exclude-specific-transitive-dependencies-of-something-i-depend-on/17991
Comments
Post a Comment