While researching the Apache Log4j Bug Bounty Program on YesWeHack, I came across an issue in Apache Log4net that initially looked like a fairly ordinary XML serialization problem. The vulnerable code was located in XmlLayoutSchemaLog4J, a layout responsible for formatting logging events as XML.
What made the issue interesting was not simply that malformed data could cause XML serialization to fail. The more important behavior appeared when I followed the failure through the rest of the logging pipeline. If a forbidden XML character reached a structured logging property, the XML writer could throw an exception. Log4net would catch that exception internally and allow the application to continue running, but the logging event that triggered the failure would never be written.
The result was a subtle loss of audit information. Data influenced by an attacker could potentially cause the log entry associated with that data to disappear from the XML output. Instead of producing malformed XML or crashing the application, the logging system could silently lose the individual event that was supposed to record what happened.
The issue was eventually assigned CVE 2026 40021 and remained classified as Medium throughout the disclosure process.
Where the problem started
The vulnerable code was inside XmlLayoutSchemaLog4J.FormatXml(), where Log4net processes the properties associated with a LoggingEvent.
When properties were present, the layout iterated over the property dictionary and created an XML element for each entry.
PropertiesDictionary properties = loggingEvent.GetProperties();
if (properties.Count > 0)
{
writer.WriteStartElement("log4j:properties", "log4j", "properties", "log4net");
foreach (KeyValuePair<string, object?> entry in properties)
{
writer.WriteStartElement("log4j:data", "log4j", "data", "log4net");
writer.WriteAttributeString("name", entry.Key);
string? valueStr =
loggingEvent.Repository?.RendererMap.FindAndRender(entry.Value);
if (!string.IsNullOrEmpty(valueStr))
{
writer.WriteAttributeString("value", valueStr);
}
writer.WriteEndElement();
}
writer.WriteEndElement();
}
The important part of this code is the way the property name and rendered property value are passed directly to the XML writer.
writer.WriteAttributeString("name", entry.Key);
writer.WriteAttributeString("value", valueStr);
Neither value passed through Log4net's invalid character masking logic before reaching WriteAttributeString().
This distinction is important because XML escaping and XML validity are two different problems. Characters such as <, > and & can be represented safely in XML through escaping. Some control characters cannot.
A character such as U+0001, for example, is forbidden by XML 1.0. It is not a matter of changing it into an entity or escaping it differently. The character itself cannot legally appear in the XML document.
When XML character validation is enabled, attempting to write such a character can cause the XML writer to throw an exception. Because the property serialization path passed the data directly to that writer, a forbidden character inside a property could reach exactly that condition.
The protection was already present elsewhere
One of the reasons the behavior stood out was that Log4net already had a mechanism designed specifically to deal with invalid XML characters.
The layout hierarchy exposes the following property:
public string InvalidCharReplacement { get; set; } = "?";
The purpose of this setting is to replace characters that cannot be represented safely in XML with another character. By default, the replacement is ?.
This meant the application already had an expected way of handling this type of input. Rather than failing to serialize the entire event, Log4net could replace the unsupported character and continue producing valid XML.
That behavior was already used in other parts of the XML layout. The rendered log message, for instance, passed through a transformation helper before being written.
Transform.WriteEscapedXmlString(
writer,
loggingEvent.RenderedMessage,
InvalidCharReplacement
);
The property path behaved differently. Property names and rendered values were written directly into XML attributes without equivalent invalid character handling.
That made the root cause relatively small. Log4net did not lack the necessary protection. One serialization path simply bypassed a protection that the rest of the layout already understood how to use.
The consequence of that small inconsistency, however, only became clear after following the exception beyond the serializer.
What happens after XML serialization fails
The next part of the investigation was understanding how Log4net behaves when an appender encounters an exception.
Appender execution is protected by exception handling around the code that ultimately writes the logging event.
try
{
_recursiveGuard = true;
if (FilterEvent(loggingEvent) && PreAppendCheck())
{
Append(loggingEvent);
}
}
catch (Exception ex) when (!ex.IsFatal())
{
ErrorHandler.Error("Failed in DoAppend", ex);
}
finally
{
_recursiveGuard = false;
}
This behavior makes sense from a reliability perspective. Logging infrastructure generally should not bring down the application simply because one event could not be formatted correctly.
If XmlLayoutSchemaLog4J throws during formatting, the exception is caught by the appender pipeline. The application can therefore continue running normally.
The problem is that catching the exception does not recover the logging event.
Once serialization has failed, that event has not been successfully written. The exception is handled, execution continues, and the original record is absent from the XML output.
This was the point where the issue stopped looking like a simple formatting bug.
The application remained available. The log file itself did not necessarily become corrupted. There was no requirement for a malicious downstream XML parser. Instead, the individual event containing the problematic data simply disappeared.
If the event represented a request, an authentication attempt, a suspicious action or another security relevant operation, the logging system could lose exactly the record that investigators would later expect to find.
Reproducing the behavior
I reproduced the issue with a minimal Log4net configuration using a FileAppender together with XmlLayoutSchemaLog4J.
The property used for the test contained the forbidden XML 1.0 character U+0001.
data.Properties["user"] = "A\u0001B";
The event was then passed through the normal Log4net logging path.
var evt = new LoggingEvent(null, repo, data);
log.Logger.Log(evt);
Under the expected behavior, Log4net should have recognized the invalid character and replaced it using InvalidCharReplacement.
Since the default replacement value is ?, the property could have been represented in the XML output as something similar to:
A?B
The logging event would still exist, the XML would remain valid, and the surrounding audit trail would remain intact.
Instead, the raw value reached the XML attribute writer. Serialization failed when the forbidden character was processed, and the exception propagated into the appender error handling path.
The application continued running, but the corresponding event was not present in the expected logging output.
This detail was important for establishing the security impact. The proof did not depend on editing an existing log file, corrupting previously written entries or exploiting another system after the XML had been generated.
The record was lost before successful log emission.
Why this can matter in real applications
Modern applications frequently enrich logging events with structured properties. These properties can contain usernames, request identifiers, authentication information, correlation values, request metadata, client supplied fields and other contextual information that helps explain what was happening when the event was generated.
Some of that information can ultimately originate from an external user.
If attacker influenced data reaches a property that is later serialized through XmlLayoutSchemaLog4J, a forbidden XML character can cause the serialization of that event to fail.
The impact is therefore best understood as a loss of audit trail integrity.
This vulnerability does not allow an attacker to modify arbitrary application data. It does not provide arbitrary file access, and it does not allow previously written log entries to be rewritten.
The demonstrated behavior is narrower.
An individual event containing the triggering data can be prevented from reaching the XML logging output.
That becomes relevant when the missing event is itself important from a security perspective. A malicious request might generate exactly the information that an administrator, security analyst or incident responder would later use to understand what happened.
If that record disappears while the rest of the application continues functioning normally, the resulting gap can be difficult to notice.
This is also why logging failures deserve more attention than they sometimes receive. A logging system is not merely an output mechanism. In many environments it is part of the evidence collection process used for monitoring, detection, investigations and incident response.
Severity, disclosure and CVE assignment
I initially submitted the vulnerability as Medium with a CVSS 3.1 score of 5.3.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
During triage, the impact model was adjusted. The scope was considered changed because the security consequence affected the logging environment rather than directly affecting the system where the original input was processed.
Integrity impact was classified as Low, while availability impact was removed.
The resulting YesWeHack vector became:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N
This resulted in a final CVSS 3.1 score of 5.8, keeping the vulnerability within the Medium severity range.
The vulnerability was later assigned CVE 2026 40021.
Apache's final advisory described a broader affected surface that included both XmlLayout and XmlLayoutSchemaLog4J. The advisory covers MDC property keys, property values and the identity field, and confirms that versions before 3.3.0 are affected.
Apache Log4net 3.3.0 contains the fix, and the Apache advisory credits the discovery to f00dat.
The report also received a €600 bounty through YesWeHack.

How the issue can be fixed
The underlying remediation follows the protection that Log4net already used elsewhere in its XML serialization logic.
Before property names or rendered values are passed to the XML writer, characters that are invalid under XML 1.0 should be replaced according to the configured InvalidCharReplacement value.
Instead of passing the property name directly:
writer.WriteAttributeString("name", entry.Key);
the value can first be processed with the existing transformation logic:
writer.WriteAttributeString(
"name",
Transform.MaskXmlInvalidCharacters(
entry.Key,
InvalidCharReplacement
)
);
Rendered property values require the same treatment.
writer.WriteAttributeString(
"value",
Transform.MaskXmlInvalidCharacters(
valueStr,
InvalidCharReplacement
)
);
With this behavior, an unsupported character no longer causes the entire event to fail serialization. The invalid character is replaced, the XML remains valid, and the logging record can still be preserved.
A regression test can verify the fix by creating a logging event that contains a forbidden XML character in one of its properties and passing that event through XmlLayoutSchemaLog4J.
The important assertion is not only that the generated XML is valid. The test should also confirm that the logging event itself remains present and that the unsupported character has been replaced according to the configured behavior.
The part of the bug that mattered most
What I found most interesting about this vulnerability was how easy it would have been to stop the analysis too early.
Looking only at the vulnerable lines, the issue appears to be a small XML validation inconsistency. A property contains a character that XML 1.0 does not allow, the serializer rejects it, and the immediate conclusion could simply be that the output cannot be generated correctly.
Following the execution path further changes the picture.
The serializer throws an exception. The appender catches it. The application keeps running. The logging event disappears.
That sequence is what gives the bug its security relevance.
The important failure was not that Log4net could produce invalid XML. In fact, the XML writer prevented exactly that from happening.
The important failure was that the system responded to an invalid value by losing the complete event instead of replacing the unsupported character and preserving the record.
Security research often comes down to following these small inconsistencies far enough to understand what they actually mean in the context of the complete system.
In this case, a few property serialization calls were enough to affect the reliability of the audit trail.
That behavior ultimately became CVE 2026 40021.
Top comments (0)