Traefik's Client Plugin is Vulnerable to Path Traversal, Arbitrary File Overwrites and Remote Code Execution
Description
Traefik is an HTTP reverse proxy and load balancer. In versions 2.11.27 and below, 3.0.0 through 3.4.4 and 3.5.0-rc1, a path traversal vulnerability was discovered in WASM Traefik’s plugin installation mechanism. By supplying a maliciously crafted ZIP archive containing file paths with ../ sequences, an attacker can overwrite arbitrary files on the system outside of the intended plugin directory. This can lead to remote code execution (RCE), privilege escalation, persistence, or denial of service. This is fixed in versions 2.11.28, 3.4.5 and 3.5.0.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/traefik/traefik/v2Go | < 2.11.28 | 2.11.28 |
github.com/traefik/traefik/v3Go | < 3.4.5 | 3.4.5 |
github.com/traefik/traefik/v3Go | >= 3.5.0-rc1, < 3.5.0 | 3.5.0 |
Affected products
1Patches
15ef853a0c530Fix client arbitrary file access during archive extraction zipslip
1 file changed · +32 −6
pkg/plugins/client.go+32 −6 modified@@ -240,6 +240,8 @@ func (c *Client) Unzip(pName, pVersion string) error { return nil } + // Unzip as a generic archive if the module unzip fails. + // This is useful for plugins that have vendor directories or other structures. return c.unzipArchive(pName, pVersion) } @@ -280,24 +282,48 @@ func unzipFile(f *zipa.File, dest string) error { defer func() { _ = rc.Close() }() + // Split to discard the first part of the path, which is [organization]-[project]-[release commit sha1] when the archive is a Yaegi go plugin with vendoring. pathParts := strings.SplitN(f.Name, "/", 2) - p := filepath.Join(dest, pathParts[1]) + if len(pathParts) < 2 { + return fmt.Errorf("no root directory: %s", f.Name) + } + + // Validate and sanitize the file path. + cleanName := filepath.Clean(pathParts[1]) + if strings.Contains(cleanName, "..") { + return fmt.Errorf("invalid file path in archive: %s", f.Name) + } + + filePath := filepath.Join(dest, cleanName) + absFilePath, err := filepath.Abs(filePath) + if err != nil { + return fmt.Errorf("resolving file path: %w", err) + } + + absDest, err := filepath.Abs(dest) + if err != nil { + return fmt.Errorf("resolving destination directory: %w", err) + } + + if !strings.HasPrefix(absFilePath, absDest) { + return fmt.Errorf("file path escapes destination directory: %s", absFilePath) + } if f.FileInfo().IsDir() { - err = os.MkdirAll(p, f.Mode()) + err = os.MkdirAll(filePath, f.Mode()) if err != nil { - return fmt.Errorf("unable to create archive directory %s: %w", p, err) + return fmt.Errorf("unable to create archive directory %s: %w", filePath, err) } return nil } - err = os.MkdirAll(filepath.Dir(p), 0o750) + err = os.MkdirAll(filepath.Dir(filePath), 0o750) if err != nil { - return fmt.Errorf("unable to create archive directory %s for file %s: %w", filepath.Dir(p), p, err) + return fmt.Errorf("unable to create archive directory %s for file %s: %w", filepath.Dir(filePath), filePath, err) } - elt, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + elt, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) if err != nil { return err }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-q6gg-9f92-r9wgghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-54386ghsaADVISORY
- github.com/traefik/plugin-service/pull/71ghsax_refsource_MISCWEB
- github.com/traefik/plugin-service/pull/72ghsax_refsource_MISCWEB
- github.com/traefik/traefik/commit/5ef853a0c53068f69a6c229a5815a0dc6e0a8800ghsax_refsource_MISCWEB
- github.com/traefik/traefik/pull/11911ghsax_refsource_MISCWEB
- github.com/traefik/traefik/releases/tag/v2.11.28ghsax_refsource_MISCWEB
- github.com/traefik/traefik/security/advisories/GHSA-q6gg-9f92-r9wgghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.