Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/changelog/.2.x.x/4181_routing_appender_security_docs.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://logging.apache.org/xml/ns"
xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
type="fixed">
<issue id="4181" link="https://github.com/apache/logging-log4j2/issues/4181"/>
<description format="asciidoc">Document security considerations for high-cardinality keys with the `Routing` Appender</description>
</entry>
Original file line number Diff line number Diff line change
Expand Up @@ -872,7 +872,7 @@ The appender can be:
--
* A previously declared appender, from the xref:manual/configuration.adoc#main-configuration-elements[`Appenders` section] of the configuration file.
* A new appender that is instantiated based on a nested appender definition, when the route becomes active.
See also <<PurgePolicy>> to learn more about the lifecycle of such an appender.
See also <<PurgePolicy>> to learn more about the lifecycle of such an appender, and <<RoutingAppender-security,Security considerations>> before deriving the key from request data.
--
+
[#Route-attributes]
Expand Down Expand Up @@ -945,6 +945,57 @@ If the `Route` element contains an appender definition, the appender will be ins
* once for each value of the key, if the `Route` has the default key.
====

[#RoutingAppender-security]
=== Security considerations

When a default <<Route,`Route`>> embeds an appender definition, the `Routing` Appender creates **one subordinate appender per distinct routing key value**.
Unlike most appenders, which are fully built when the configuration is loaded, those subordinate appenders are created **at runtime** when a new key appears.
Lookups used in the route (for example `${ctx:userId}`) can therefore still carry attacker-controlled data when appender attributes are resolved.

Review two aspects of any dynamic routing configuration:

[#RoutingAppender-security-resources]
==== Resource allocation
Comment thread
SebTardif marked this conversation as resolved.

The `Routing` Appender is intentionally powerful: a separate log file per tenant or per long-running job, or round-robin routing between a fixed set of appenders, are all supported designs.
The responsibility that comes with that power is provisioning: the system must be able to allocate the resources required by every appender that can be created.

If the key is derived from untrusted or high-cardinality data (for example `${ctx:userId}`, a client IP, or a free-form request header), an attacker or a busy system can force creation of an unbounded number of appenders.

That growth commonly leads to:

* exhaustion of file descriptors (when each route opens a `File` or rolling file appender)
* elevated memory use for appender state, buffers, and managers
* difficulty shutting down or reconfiguring the application cleanly

[#RoutingAppender-security-threat-model]
==== Threat model
Comment thread
SebTardif marked this conversation as resolved.

An untrusted key is not only a resource problem: it is also substituted into the subordinate appender's configuration.
Attributes such as `fileName` therefore inherit whatever the lookup returns.

For example, with `fileName="logs/${ctx:userId}.log"`, a Thread Context value of `../../../../tmp/x` (as a whole path segment) can open `/tmp/x.log` without an error.
Comment thread
SebTardif marked this conversation as resolved.
Embedding the lookup inside a longer fixed segment, such as `logs/user-${ctx:userId}.log`, usually fails to open the file instead of escaping the directory, so the exact `fileName` pattern matters.
That failure is a side effect of how paths are resolved, not a mitigation: it is loud rather than safe, and it does not hold for every value a lookup can return.
The appender is also re-attempted, and the event lost, on every subsequent event with that key.

This matches the project's {logging-services-url}/security.html#threat-common-sources-configuration[threat model for configuration sources]: operators are responsible for ensuring that appender configuration attributes come from trusted data.
Only the application developer knows which Thread Context keys carry validated values and which are entirely attacker-controlled.
See also {logging-services-url}/security/faq.html#path-traversal[path traversal in the security FAQ].

The same substitution reaches non-file sinks: a `Route` that builds an xref:manual/appenders/network.adoc#HttpAppender[HTTP Appender] interpolates the key into its `url`, turning an untrusted routing key into a server-side request forgery (SSRF) vector.

[#RoutingAppender-security-mitigations]
==== Mitigations
Comment thread
SebTardif marked this conversation as resolved.

* Prefer the <<Route-attr-ref,`ref` attribute>> of a `Route`, pointing at a fixed, predeclared set of appenders, when the set of destinations is known.
* When dynamic routes are required, always configure a <<PurgePolicy,`PurgePolicy`>> (typically <<IdlePurgePolicy,`IdlePurgePolicy`>>) so idle route appenders are stopped and released.
+
Note that a purge policy bounds how long an unused appender survives, not the rate at which new ones are created.
* Constrain routing keys to a low-cardinality, validated domain (allow-lists, enums, hashed buckets) instead of raw user input.
* Avoid routing on pure user-controlled identifiers when each value would create a new file-backed appender, or when the key is interpolated into paths, URLs, or other sink configuration.
* Do not create an appender per web request. xref:manual/api.adoc#fish-tagging[Fish tagging] the events and filtering on the tag is a far better use of resources.

[#PurgePolicy]
=== Purge Policy

Expand Down
Loading