DEV Community

Damiano
Damiano

Posted on Originally published at damianobianda.hashnode.dev

The handshake only proves the leaf: LXD's CVSS 10.0 certificate mix-up

Mutual TLS is supposed to settle the question of identity in both directions. The server proves itself to the client, the client proves itself to the server, and both sides walk away knowing who they are talking to. This week Canonical disclosed a bug that shows how thin the line is between "we use mTLS" and "we actually verified the client": CVE-2026-112816, a maximum-severity flaw in LXD and MicroCloud that let an attacker authenticate as any trusted client, without ever holding that client's private key.

The short version: LXD checked whether the client's certificate was in its trust store. It did not check whether the client actually owned the key for that certificate.

What LXD and MicroCloud are

LXD is Canonical's system container and virtual machine manager, the thing that sits on a host and runs your containers and VMs behind a clean REST API. MicroCloud ties several machines running LXD into a single cluster, so a small fleet behaves like one private cloud. Both let remote clients talk to that API over HTTPS, and both use mutual TLS to decide who is allowed in.

In mTLS, the client presents a certificate, and the server only accepts clients whose certificate it already trusts. The list of accepted identities is the trust store. If your certificate is in it, you are in. That is the design, and it is a sound one, as long as "presenting the certificate" actually proves you are its owner.

Where the check went wrong

TLS does prove ownership, but only for one certificate: the leaf, the first one in the chain the client sends. The cryptographic handshake demonstrates possession of the private key for that leaf certificate. Everything after the leaf in the chain is just data the client attached. The client chose it. The client could have attached anything.

LXD's HTTPS listener compared every certificate in the presented chain against the trust store, not just the leaf. So the attack is almost embarrassingly simple:

  1. Get the public certificate of a client the server trusts. Public certificates are not secret; they are exchanged in handshakes, pinned in configs, checked into repos.
  2. Generate your own key pair and certificate, which you fully control.
  3. Connect, present your own certificate as the leaf, and attach the trusted client's certificate behind it in the chain.
  4. The server finds a trusted certificate somewhere in the chain and authenticates you as that client.

You have now logged in as a privileged client while proving possession of exactly one private key: your own. If the impersonated client has administrative rights, Canonical says this can mean full access to the cluster and its virtual machines, and from there, root on the host machine.

How exposed is a real install

Two preconditions gate the attack, and they matter.

First, the attacker needs network access to the HTTPS API. A standalone LXD server does not listen on the network by default: it answers on a local Unix socket until an administrator enables the remote API with the core.https_address setting. Plenty of production LXD deployments enable it, because remote management is the point, but a default single-host setup is not reachable this way.

Second, the attacker needs a trusted client's public certificate. Not secret, but not printed on the homepage either.

MicroCloud has the sharper edge. When a new member joins the cluster, its certificate moves across the network. An attacker positioned to observe or interfere with that join can capture the member's public certificate and then authenticate as that member. The cluster's own plumbing becomes the leak.

This is also why Ubuntu's own tracker rates the issue Medium while the CVSS score is 10.0. The 10.0 describes what happens when the preconditions line up: unauthenticated, remote, total compromise. The Medium reflects that the preconditions are real friction, not formalities. Both numbers are honest. They measure different things.

What is affected and what fixes it

Per Canonical's CVE record, the flaw is improper certificate validation in the HTTPS listener, and these LXD versions are affected, each before the fixed release that follows it:

  • LXD 4.0.14, fixed in 4.0.15
  • LXD 5.0.10, fixed in 5.0.11
  • LXD 5.21.8, fixed in 5.21.9
  • LXD 6.9, fixed in 6.10.1

Ubuntu's archive packages of LXD are listed as not affected or not shipped, so the fixed releases that matter are the snaps. On a snap install:

sudo snap refresh lxd
sudo snap refresh microcloud
Enter fullscreen mode Exit fullscreen mode

One caveat from Canonical's own advisory: at the time of disclosure, the fixed point releases were still rolling out from the candidate channel to stable. Refreshing is not the same as being patched. Check the version you actually have after the refresh, and if the fix has not landed on your channel yet, the interim move is to restrict who can reach the API at all, because the attack needs to touch that listener.

The bug did not originate at Canonical. The Incus project, the community fork of LXD, reported it first, and Canonical confirmed LXD shared it. Incus fixed the same flaw in version 7.6.0, along with 17 other security issues, and its advisory is blunt: there is no configuration fix, only restricting network access to the listener until you upgrade.

As of the disclosure there is no evidence of exploitation in the wild. The CVE record carries a CISA assessment dated October 30 that the flaw is not known to be exploited, it is not on the Known Exploited Vulnerabilities list, and no public exploit exists. That is a state of the world, not a safety property. Now that the mechanism is public, the preconditions are all that stand between a lab curiosity and a working attack, and the mechanism is one paragraph long.

The pattern worth keeping

File this bug next to the ones that keep recurring across decades of TLS implementations: the library did the cryptography correctly, and the application trusted the wrong thing. The handshake proved possession of the leaf key, exactly as designed. The application then extended that proof, silently, to every certificate the client happened to attach. Nothing in TLS promised that. The chain is input, not evidence.

If you write code that consumes a peer's certificate chain, the rule is short: authenticate on the leaf, and treat everything else as untrusted data until your own validation says otherwise. If you operate LXD or MicroCloud, the rule is shorter: refresh, verify the version, and know who can reach your API.

The handshake only ever proves the leaf. The moment a system forgets that, anyone holding a public certificate and five minutes becomes someone you trust.

Top comments (0)