Learning from Log4j

With Log4j very much in the news, if I could update my new book by magic it would make a terrific real world example to write about because it ties together a number of topics in the book. This vulnerability stems from failure to sanitize untrusted inputs, enabling an injection attack that potentially can access arbitrary targets using authentication credentials held by the target server. All the attacker has to do is craft an attack string that manages to get logged somehow, and the widely used Apache Log4j 2 component executes whatever the attacker commands.

Typical attacks send requests containing strings something like this ${jndi:ldap://...} aiming to get this untrusted input logged. Log4j processing expands the JNDI expression in the string, thereby performing privileged operations at the behest of the attacker. From a quick look it appears that this dangerous feature was implemented with hopes that callers of the API would use sufficient care avoiding problems, and/or that the library would and prevent detect malicious usage. The fix seems to be disabling JNDI by default1, suggesting that it can be turned back on by people who should know what they are doing, which really pushes the problem out to the millions2 of applications using it to deal with.

The book includes a sample software design document (Appendix A) that just happens to be a logging subsystem. While in that writeup I don’t explicitly warn against exactly this kind of bug, I do specify an API based on JSON which (if used correctly) should make such attack strings harmless. Also, the API structures log structured data, rather than the usual one-size-fits-all plain text string, and so handling typed data objects will naturally be coded in a safer manner. Arguably software designs should stick to that level of abstraction and not anticipate specific implementation flaws like this Log4j bug, but trust that coders are competent to handle the job. Clearly there is a fat gray line between design and implementation, but a designer doesn’t know exactly how implementation will proceed and so the potential range of vulnerabilities introduced later is virtually unbounded. Exceeding the design layer with coding precautions would also result in a litany of precautions that every design document would reproduce like boilerplate: avoid buffer overflows, don’t let fixed width integer calculations overflow, and so on.

The bad news with Log4j is that any system that uses it will offer a unique attack surface that feeds the logging component. Depending on how individual apps implement logging — specifically, how and where untrusted inputs flow into logs — Updating Log4j to safely handle malicious inputs (the latest update should do that in theory) should stem the damage, but it’s important to bear in mind that other parts of any of these apps may have other issues that such attack strings can manipulate.

The good news is that my book talks about all of these issues and offers many ways to mitigate the problem: handling untrusted inputs; input validation; injection attacks; secure use of components; security code reviews; logging best practice; security testing and regression testing.

Disclaimer: I have not researched this vulnerability independently but am relying on analysis by others, primarily Log4Shell Hell: anatomy of an exploit outbreak from Sophos.

  1. Based on the Mitigation section of the two CVE sections at the Apache Log4j 2 home page (Last Published: 2021-12-13 | Version: 2.16.0) ↩︎

  2. That it’s difficult to know what applications do depend on Log4j is itself a major factor in why it’s so difficult to quickly shut down attacks. ↩︎