[
https://issues.apache.org/jira/browse/KAFKA-20700?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Mickael Maison resolved KAFKA-20700.
------------------------------------
Fix Version/s: 4.4.0
Resolution: Fixed
> 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
> Priority: Major
> Fix For: 4.4.0
>
>
> 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)