Nilesh Kumar created KAFKA-20700:
------------------------------------
Summary: AllowedPaths should resolve symlinks before validating
paths against allowed.paths
Key: KAFKA-20700
URL: https://issues.apache.org/jira/browse/KAFKA-20700
Project: Kafka
Issue Type: Bug
Reporter: Nilesh Kumar
Assignee: Nilesh Kumar
h2. Problem
`AllowedPaths.parseUntrustedPath()` validates user-supplied paths using lexical
`Path.normalize()` only. It does not resolve symbolic links before checking
whether a path is under a configured `allowed.paths` base directory.
As a result, if a symlink exists inside an allowed directory (e.g.
`/opt/kafka/secrets/link -> /some/other/path`), validation passes because the
lexical path appears under the allowed base, but `FileConfigProvider` and
`DirectoryConfigProvider` later read the symlink target when accessing the
filesystem.
h2. Affected components
* `org.apache.kafka.common.config.internals.AllowedPaths`
* `org.apache.kafka.common.config.provider.FileConfigProvider`
* `org.apache.kafka.common.config.provider.DirectoryConfigProvider`
h2. Example
* `allowed.paths` configured as `/opt/kafka/secrets`
* Symlink present: `/opt/kafka/secrets/test -> /etc/sensitive`
* Config reference: `${file:/opt/kafka/secrets/test:key}`
* Validation allows `/opt/kafka/secrets/test`
* File read follows symlink and accesses `/etc/sensitive`
h2. Current behavior
{code:java}
Path normalisedPath = parsedPath.normalize();
long allowed = allowedPaths.stream().filter(normalisedPath::startsWith).count();
{code}
`normalize()` collapses `.` and `..` but does not resolve symlinks.
h2. Expected behavior
Path validation should use the resolved filesystem path (e.g. `toRealPath()`)
so that a symlink inside an allowed directory pointing outside that directory
is rejected.
h2. Proposed fix
{code:java}
try {
Path realPath = parsedPath.toRealPath();
long allowed = allowedPaths.stream().filter(realPath::startsWith).count();
if (allowed == 0) {
return null;
}
return realPath;
} catch (IOException e) {
return null;
}
{code}
Consider also resolving allowed base paths with `toRealPath()` at configuration
time for consistent `startsWith` comparisons.
h2. Test coverage
`AllowedPathsTest` covers `..` traversal but has no test for symlink
resolution. Add tests for:
* Symlink inside allowed dir pointing outside → rejected
* Symlink inside allowed dir pointing inside → allowed
* Direct path (no symlink) → unchanged behavior
h2. Context
This was reported to the security team. They noted that exploiting this
requires write access to the Connect worker filesystem to create symlinks, and
therefore do not classify it as a security issue. They agreed the behavior is
not intuitive and welcomed a community PR to improve it.
h2. References
*
`clients/src/main/java/org/apache/kafka/common/config/internals/AllowedPaths.java`
*
`clients/src/test/java/org/apache/kafka/common/config/provider/AllowedPathsTest.java`
* Related hardening: CVE-2024-31141 (`allowed.paths`)
--
This message was sent by Atlassian Jira
(v8.20.10#820010)