CVE-2026-34480
Overview
Apache Log4j 2 ships an XmlLayout class (in log4j-core) that formats each log event as an XML element using a Jackson-based serialization stack. The layout backs every appender configured with <XmlLayout>, including file, socket, and rolling-file appenders that feed XML log records to downstream collectors, log shippers, ELK / OpenSearch ingest pipelines, and SIEM platforms.
A medium-severity vulnerability (CVE-2026-34480, classified as CWE-116 Improper Encoding or Escaping of Output) has been identified in Log4jXmlObjectMapper, the XmlMapper subclass that backs XmlLayout. The mapper does not sanitize Unicode code points that XML 1.0 forbids before passing log-event fields (log message, MDC entries, exception text, marker name, thread name, source class/method/file) to the underlying StAX writer. When such a code point appears in any logged field, two failure modes occur depending on which StAX provider is on the classpath: with the JRE's built-in StAX writer, the forbidden character is silently emitted into the XML output and conforming downstream parsers reject the document as malformed, dropping the affected record (and depending on parser recovery, subsequent records). With Woodstox / stax2-api on the classpath (very common on application servers and many Spring Boot WAR deployments), the conformance-checking writer raises XMLStreamException from inside XmlLayout.toSerializable(LogEvent); the exception propagates out of the layout, the appender's append method short-circuits, and the event never reaches disk or the network sink at all.
Per OWASP: "Denial of Service (DoS) is an attack against the availability of a system or service." The practical effect of this defect is denial of service for the logging pipeline: an attacker who can influence any text that ends up logged via an XmlLayout-configured appender can make the resulting XML log file invalid (or make the appender refuse to emit the event at all), causing the very record that would have flagged the attacker's actions to be dropped before it reaches the SIEM.
The CVSS v4.0 base score is 6.9 (Medium) with vector CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:L/SA:N; under CVSS v3.1, NVD scores the vulnerability 7.5 High with vector AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N. There is no impact on the running application's confidentiality, integrity, or availability: no privilege escalation, no code execution, no in-process compromise. The integrity score reflects the integrity of the log-record stream that downstream tooling consumes, which is often a security-critical asset for audit, regulatory compliance, and intrusion detection.
This issue affects Apache Log4j 2 versions 2.0-alpha1 through 2.25.3 (the vulnerable code has been present since the layout's introduction in 2014), and pre-release versions 3.0.0-alpha1 through 3.0.0-beta3.
Details
Module Info
- Product: Apache Log4j 2
- Affected packages: log4j-core
- Affected versions: >=2.0-alpha1 <=2.25.3, >=3.0.0-alpha1 <=3.0.0-beta3
- GitHub repository: https://github.com/apache/logging-log4j2
- Published packages: https://central.sonatype.com/artifact/org.apache.logging.log4j/log4j-core
- Package manager: Maven
- Fixed in:
- NES for Apache Log4j 2 2.17.x
- OSS Apache Log4j 2 2.25.4
Vulnerability Info
The vulnerability is in org.apache.logging.log4j.core.jackson.Log4jXmlObjectMapper in the log4j-core module, the XmlMapper subclass that XmlLayout uses to serialize LogEvent instances to XML. The mapper extends Jackson's XmlMapper and lets Jackson's XmlFactory hand off character data to whichever XMLStreamWriter the StAX provider returns.
An application is vulnerable when all of the following are true:
- The application uses Apache Log4j 2 between 2.0-alpha1 and 2.25.3 inclusive (or a 3.x pre-release in the affected range)
- At least one configured appender uses XmlLayout
- An attacker can influence any text that ends up in a logged LogEvent field (HTTP headers, query parameters, request bodies, exception text, MDC entries, etc.)
When those conditions are met, the attacker can inject a single forbidden code point (U+0000–U+0008, U+000B, U+000C, U+000E–U+001F, an unpaired surrogate U+D800–U+DFFF, U+FFFE, or U+FFFF) into any logged field; the mapper writes that code point through to the StAX writer; and either the downstream parser drops the record, or the appender drops the event in-process.
Prior to the fix, Log4jXmlObjectMapper had no sanitization layer:
public class Log4jXmlObjectMapper extends XmlMapper {
private static final long serialVersionUID = 1L;
public Log4jXmlObjectMapper() {
this(true, false);
}
public Log4jXmlObjectMapper(final boolean includeStacktrace, final boolean stacktraceAsString) {
super(new Log4jXmlModule(includeStacktrace, stacktraceAsString));
this.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
}
}
Jackson's XML serializer asks the StAX implementation it was given to write character data; it does not verify that the characters are valid in XML 1.0. The XML 1.0 specification (Fifth Edition) forbids the C0 control range (except \t \n \r), unpaired surrogates, and U+FFFE / U+FFFF in character content. Pre-fix, those forbidden code points flow straight through to the writer.
The fix wraps every XMLStreamWriter produced by the underlying XmlFactory in a SanitizingWriter that intercepts every write path Jackson uses (writeAttribute, writeCData, writeCharacters, writeComment) and substitutes U+FFFD for any code point not legal in XML 1.0:
public Log4jXmlObjectMapper(final boolean includeStacktrace, final boolean stacktraceAsString) {
super(new SanitizingXmlFactory(), new Log4jXmlModule(includeStacktrace, stacktraceAsString));
this.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
}
private static final class SanitizingWriter extends StreamWriter2Delegate {
private static final char REPLACEMENT_CHAR = '�';
SanitizingWriter(final XMLStreamWriter2 delegate) {
super(delegate);
setParent(delegate);
}
@Override
public void writeCharacters(final String text) throws XMLStreamException {
super.writeCharacters(sanitizeXml10(text));
}
private static String sanitizeXml10(final String input) {
if (input == null) return null;
final int length = input.length();
for (int i = 0; i < length; ) {
final int cp = input.codePointAt(i);
if (!isValidXml10(cp)) {
final StringBuilder out = new StringBuilder(length);
out.append(input, 0, i);
appendSanitized(input, i, length, out);
return out.toString();
}
i += Character.charCount(cp);
}
return input;
}
private static boolean isValidXml10(final int codePoint) {
return (codePoint >= ' ' && codePoint < Character.MIN_SURROGATE)
|| codePoint == '\t'
|| codePoint == '\n'
|| codePoint == '\r'
|| (codePoint > Character.MAX_SURROGATE && codePoint <= 0xFFFD)
|| codePoint >= Character.MIN_SUPPLEMENTARY_CODE_POINT;
}
}
private static final class SanitizingXmlFactory extends XmlFactory {
@Override
protected XMLStreamWriter _createXmlWriter(final IOContext ctxt, final Writer w) throws IOException {
return new SanitizingWriter(Stax2WriterAdapter.wrapIfNecessary(super._createXmlWriter(ctxt, w)));
}
@Override
protected XMLStreamWriter _createXmlWriter(final IOContext ctxt, final OutputStream out) throws IOException {
return new SanitizingWriter(Stax2WriterAdapter.wrapIfNecessary(super._createXmlWriter(ctxt, out)));
}
@Override
public XmlFactory copy() {
_checkInvalidCopy(SanitizingXmlFactory.class);
return new SanitizingXmlFactory();
}
}
Stax2WriterAdapter.wrapIfNecessary(...) adapts the JRE's plain XMLStreamWriter up to a Stax2 XMLStreamWriter2, which is why stax2-api was added to log4j-core/pom.xml as a runtime dependency. sanitizeXml10(...) does not allocate when the input is already clean, which is important because Log4jXmlObjectMapper sits on the per-event hot path.
A new parameterized regression test in XmlLayoutTest builds a LogEvent with the forbidden code points placed in the marker, exception text, MDC, stack frames, thread name, and source-info fields, serializes it via XmlLayout, and asserts the result contains U+FFFD and not the invalid characters. A complementary positive test confirms that valid supplementary-plane characters (e.g. emoji at U+10400 or U+10FFFF) are preserved unchanged.
The vulnerable Log4jXmlObjectMapper class was added in commit 5e48367f90 (interdependent changes for LOG4J2-583 / 584 / 634, "TCP and UDP socket servers should be able to handle XML/JSON log events") authored 2014-05-13, and is present at the very first GA tag log4j-2.0 (August 2014). The defect therefore exists in every Log4j 2.x release from 2.0 through 2.25.3. Apache Log4j 1's standalone XMLLayout is a separate hand-written class that is not in scope for this CVE; the same XML 1.0 invalid-character issue in the Log4j 1-to-Log4j 2 bridge's Log4j1XmlLayout is tracked under CVE-2026-34479.
Mitigation
Only recent versions of Apache Log4j 2 receive community support and updates. Older versions have no publicly available fixes for this vulnerability.
Users of the affected components should apply one of the following mitigations:
- Upgrade to a currently supported version of Apache Log4j 2. The OSS fix ships in Apache Log4j 2.25.4 and later.
- Switch the affected appender's layout from XmlLayout to a layout that performs proper output encoding, such as JsonTemplateLayout or PatternLayout.
- Sanitize attacker-influenced input at the application boundary before it can reach a logger call, stripping XML 1.0 forbidden code points (the C0 control range except \t \n \r, unpaired surrogates, and U+FFFE / U+FFFF) from request headers, query strings, and other untrusted text.
- Leverage a commercial support partner like HeroDevs for post-EOL security support through Never-Ending Support (NES) for Apache Log4j 2.
Credits
- Ap4sh (Samy Medjahed) (finder)
- Ethicxz (Eliott Laurie) (finder)
- jabaltarik1 (independent finder)
- Piotr P. Karwasz (ppkarwasz) (fixer)
- Volkan Yazıcı (vy) (co-fixer)