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)

Reply via email to