The Log4Shell vulnerability and how to fix it
Since Friday, the Java and general IT world has been in a bit of turmoil after a zero-day vulnerability in the widely-used logging framework Log4j has been disclosed. The vulnerability enables remote code execution (RCE) and affects, well, a lot of Java applications.
What happens in a nutshell is that one can trigger a remote code execution by providing a string in a certain format that ends up being logged. The strings look as follows: ${jndi:ldap://someurl/somepath}
and as you probably can guess will cause an LDAP lookup to that location, fetch some compiled code, and execute it. In other words, if a malicious user can somehow provide a string that ends up being logged with Log4j they can exploit this possibility. The reason why Log4j performs these lookups to begin with is for legacy features and backwards compatibility.
Now you might say that this doesn’t apply to you since you’re not using Log4j, well, you might actually if any third-party dependency includes log4j-core
, which a lot do. So, and this is not a joke, if you’re responsible for building any Java application and I’m sure most of my readers are, now go and check if your Java builds contain this dependency.
Quickly checking your applications
A quick way to check if you directly or indirectly package log4j-core
is to look at your packaged JAR or WAR application and see which libraries it includes:
# for most Maven projects, after a full build: find target/ "*log4j*" # or look into the lib directories, depending on your build ls -ahl target/lib/ ls -ahl target/quarkus-app/lib/*
You can also generate a directory hierarchy with your IDE or build tool, but be aware that this might include test dependencies as well:
mvn dependency:tree mvn dependency:tree | grep log4j
Closing the vulnerability
Now if you do include log4j-core
and the version is older than 2.15.0
(which it probably is since that version has just been released), you are likely affected by the vulnerability. Likely, because some newer JDK minor versions include a property com.sun.jndi.ldap.object.trustURLCodebase
set to false
, but let’s better be safe than sorry.
The easiest way to fix the situation is to bump the version of log4j-core
to the new 2.15.0
which closes the vulnerability. If that is not easily possible because of dependency hierarchies, or because your project build is too complex, there are other ways to fix this without even rebuilding your application:
In Log4j version 2.10
and newer, you can also either set a Java System property log4j2.formatMsgNoLookups
or the environment variable LOG4J_FORMAT_MSG_NO_LOOKUPS
to true
, e.g. by -Dlog4j2.formatMsgNoLookups=true
, depending on what’s easier in your setup. If you run containerized workloads, e.g. on Kubernetes, it might be the easiest to include the environment variable that can be injected without changing either your container image nor the start command. Make sure you deploy this ASAP (yes, that’s serious) and then you have some time to sort out your dependencies.
If you can’t even easily restart your application (then we definitely need to talk), there are some projects available such as Logout4Shell that fix the vulnerability by executing code via the very same LDAP RCE, literally vaccinating your workloads. You can apply these while your applications are running, at least if your JVM doesn’t prevent the use of the Reflection or Bytecode manipulation features being used; if that’s the case you’d still need to restart.
For all these fixes, please do your own research and take some time to see if they actually mitigate the situation in your setup, depending on your used stack, versions, and dependencies.
How bad is it
Well, in general really bad, in particular, it depends ™. The vulnerability was published with the highest CVSS score of 10 (out of 10), but how much it actually affects your workload depends on a few things.
First of all, an attacker needs to be able to provide input that will be processed by log4j-core
, in other words end up in the logs. But we can likely assume that, even if we don’t log much, since you never can be really sure which user-provided string might end up there. For example, a lot of implementations log error message on invalid HTTP requests, together with the invalid values, etc.
Now, assuming one could inject an arbitrary string and exploit the JNDI lookup to access and execute some compiled code via LDAP. With that, it would be easy to cause harm with brute force, such as killing the application or else. However, in order to exploit it in a more subtle way, such as reading sensitive user data, you then still need certain knowledge about the application and its classes, which in fact can also be done by that exploit — it just needs more time.
This is why we’re seeing exploits for widely-used applications such as Minecraft, since an attacker can produce malicious code tailored for that software, with the sources widely available. But in the mid-term, it’s equally possible to attack a vulnerable workload in a reverse engineering way, and by providing custom code that targets a particular application.
My thoughts
As for my view on it, this somewhat reminds me of some talks I gave in 2016 and 2017 about our enterprise frameworks and how we should deal with the dependencies, and also how much logging we need, if we build apps for a containerized world. Personally, I don’t make much use of logging, mostly only for errors which you can’t handle in any other possible way (e.g. when the database isn’t available anymore), in which case I usually log an error. For all my recent cloud-native Java apps, I literally use System.out
and System.err
to log, sometimes encapsulated by a logger-like facade. Since the output will be aggregated via StdOut
anyway, that’s a direct and easy way.
When I heard about this vulnerability, I did check all my projects that are available in production and none did include log4j-core
. They’re all powered by Quarkus and Quarkus doesn’t include Log4j per default. But still, if you use any third-party dependency they might pull it in, so in any case you should check.
But also, I see a lot of positive things in this situation. First of all, it shows us how healthy the Java ecosystem is. Most of the projects use build tools with rather strict but precise dependency specifications and ways to build in a reproducible way, which makes detecting and changing dependencies and their versions possible and comparably easy. This reminded me of similar situations in other programming language ecosystems and I’m rather pleased to see how smooth one can fix this vulnerability. On the other hand, it also needs to be pointed out, how quickly the Log4j maintainers reacted and pulled out a new release. We should remember that most open-source developers do this in their free time without being paid for it, which frankly sometimes just blows my mind how much of the modern world relies on a few technologies that we all and huge companies use for free, for the most part. And lastly, I was also very glad to see how many people in the open-source world and on Twitter jumped in and provided quick fixes and ways to close this bug if you can’t quickly rebuild your application.
So, I hope this was helpful, to sum it up, quickly check your Java applications, and fix your deployments if you have to. Thanks for reading!
Update 2021-12-13:
Added more information regarding mitigations and in which versions they work.
Published on Java Code Geeks with permission by Sebastian Daschner, partner at our JCG program. See the original article here: The Log4Shell vulnerability and how to fix it Opinions expressed by Java Code Geeks contributors are their own. |