THE L33T VULN JOURNAL
0.5 seconds to 'disaster' — The XZ Utils backdoor

// CASE FILE 001 · CVE-2024-3094

The XZ Utils backdoor

// THIS CASE ON SOCIAL POST LINKS WILL APPEAR HERE

In March 2024, a delay of roughly half a second in SSH logins led Andres Freund to a malicious release of one of Linux's most familiar compression libraries.

It was a supply-chain attack with a specific destination: the SSH authentication path on selected Linux builds. The payload was hidden in release archives of XZ Utils, reconstructed during packaging, and inserted into liblzma. This case is about how the pieces connected and what can actually be verified.

Result sought
Pre-authentication remote code execution (RCE)
Entry point
Compromised release tarballs
Affected releases
XZ Utils 5.6.0 and 5.6.1
Public disclosure
29 March 2024

// PEOPLE BEHIND THIS CASE

Andres Freund found the backdoor; Lasse Collin maintains XZ Utils. Sam James documented the response; Gynvael Coldwind analysed the build script, Solar Designer circulated that work and Filippo Valsorda shared early findings about the payload. Their work and public profiles are collected below.

01 / THE CASE

The short version

The attacker controlled the release process closely enough to place a build-time trigger inside the published .tar.xz archives for XZ Utils 5.6.0 and 5.6.1. The trigger used files masquerading as compression tests to alter the resulting liblzma. A source checkout alone could miss the critical build logic.

The target was sshd, but OpenSSH did not need a direct XZ dependency. On certain distribution builds, a patched SSH daemon linked to libsystemd, which brought liblzma into the process.

sshdlibsystemdliblzma

The malicious build path was selective: the original disclosure described checks for x86-64 Linux, GCC and GNU ld, and distribution package builds. Having XZ installed did not by itself mean that the running SSH daemon was compromised.

02 / DISCOVERY

A half-second clue

Freund noticed slow SSH logins and unusually high CPU use on a Debian development system. Valgrind also reported errors. Tracing the behaviour back through the process led him to liblzma and the malicious release archives. He posted the findings to the oss-security mailing list on 29 March 2024.

03 / MECHANISM

Where the payload hid

  1. Release artifact. The malicious m4/build-to-host.m4 macro was present in the published tarballs, rather than the same Git source snapshot.
  2. Disguised inputs. Special files under tests/files/ carried obfuscated material that looked like test data.
  3. Conditional build. The build logic checked for a suitable environment and reconstructed the payload while distribution packages were being prepared.
  4. Altered library. The resulting liblzma could change behaviour inside a targeted sshd process, affecting SSH authentication.

Subsequent analysis identified a gated route to remote command execution before SSH authentication: the malicious handler checked attacker-controlled key material and could pass a supplied command to system(). This describes the intended capability on targeted builds, not proof that every machine running XZ 5.6.x was accessible.

What would an attacker actually send?

On a system where the modified library is loaded by sshd, the malicious code redirects an OpenSSL RSA_public_decrypt call through a loader-time hook. The attacker then starts an SSH authentication attempt using a crafted OpenSSH certificate. Its CA signing key has an unusual RSA modulus (N) carrying an encrypted command and an Ed448 signature.

  1. Connect. The attacker presents the crafted certificate during the SSH public-key authentication exchange. No normal user password or successful login is required.
  2. Reach the hook. While parsing the certificate, the affected sshd reaches RSA_public_decrypt; the handler in liblzma reads the data inside N.
  3. Prove control. The handler decrypts the payload and checks its Ed448 signature against a public key embedded in the backdoor. The signed data includes a hash of the server host key, so a captured trigger is bound to that server.
  4. Run the command. With a valid signature, it passes the attacker-supplied command to system() in the privileged pre-authentication process. An invalid payload follows the normal OpenSSL path.

04 / THE HUMAN PART

Trust became the route in

The public identity Jia Tan / JiaT75 contributed to XZ Utils over a long period and obtained release access. The malicious releases came through the project's normal publication route. The real-world identity behind that account, and who else may have been involved, remain unconfirmed publicly. This is an attribution to a project account, not a verified personal profile.

The decisive distinction is between a repository review and a release review. If a distributor builds from a generated release tarball, inspecting only the corresponding Git tree does not establish what went into the package.

05 / LAB · TWO LEVELS

Inspect it. Then trigger it.

The first three steps inspect the release artifact without executing it. The second part runs the backdoor inside isolated virtual machines and tests a harmless command. For static inspection, use copies of the original 5.6.1 release tarball and the matching Git source archive from a trusted research archive; keep them as files and do not run their build scripts on your normal machine.

1. Record what is installed

Run the applicable package command for your distribution. Version output alone is an inventory clue; package provenance, build options and the library loaded by sshd determine exposure.

xz --version | head -n 1
dpkg-query -W xz-utils liblzma5 2>/dev/null
rpm -q xz xz-libs 2>/dev/null

If you are investigating a specific host, you can inspect the linked libraries too. Paths vary by distribution, so adjust the libsystemd path and check the daemon's actual configuration. No match from ldd means this direct dependency chain is not shown by that command; it is not a complete forensic verdict.

ldd /usr/sbin/sshd | rg "libsystemd|liblzma"
ldd /usr/lib/x86_64-linux-gnu/libsystemd.so.0 | rg "liblzma"

2. Inspect the release archive

From the directory containing xz-5.6.1.tar.xz, list the trigger macro and the two relevant test-file names. Then print selected lines from the macro without extracting or executing it. The second command is a starting point for reading, not a full deobfuscation.

tar -tf xz-5.6.1.tar.xz | rg "m4/build-to-host[.]m4$|tests/files/(bad-3-corrupt_lzma2[.]xz|good-large_compressed[.]lzma)$"
tar -xOf xz-5.6.1.tar.xz xz-5.6.1/m4/build-to-host.m4 | rg -n "gl_.*config|gl_path_map|xz|tr"

Expected observation: the release archive lists m4/build-to-host.m4 and the named test artifacts. Inspect the surrounding macro text if the filtered lines are hard to interpret; the trigger is intentionally obfuscated.

3. Compare with a Git source archive

Place the matching Git-generated archive alongside the release tarball and compare the macro's presence. The second search is expected to return no match (exit status 1): the Git snapshot does not contain that generated malicious macro. Archive naming can differ depending on where you obtained it.

tar -tf xz-5.6.1.tar.xz | rg "m4/build-to-host[.]m4$"\ntar -tf xz-v5.6.1-source.tar.gz | rg "m4/build-to-host[.]m4$"

4. Build an isolated target and a control

For a working simulation, follow the three-VM walkthrough by Steve Henderson, based on Anthony Weems's xzbot. It requires a Linux host with git, make and Multipass capable of attaching VMs to an isolated bridge; its setup needs host privileges to create that bridge. Use a disposable lab host and read its security notes before starting. This workflow has been reviewed against the project's documentation, but has not been run by this journal.

These commands pin the lab version reviewed here. make lab2 provisions an analyst VM holding a fresh Ed448 private key, a compromised VM with the real backdoored library patched to trust the analyst's public key, and a normal VM as a control. The project checks the downloaded package and removes the VMs' default routes after setup. The original attacker's signing key remains unknown.

git clone https://github.com/stevehenderson/lab_xz_backdoor.git\ncd lab_xz_backdoor\ngit checkout 8dc1c350d169fea457d64069fecc3f8fd32e3413\nmake setup\nmake lab2

Once provisioning has finished, enter the analyst VM. The lab's default private bridge uses 10.77.0.20 for the compromised target and 10.77.0.30 for the normal one; no external SSH target is needed.

multipass shell analyst

5. Send the trigger from the analyst VM

The latency helper compares ordinary SSH attempts. The trigger helper then uses xzbot to send a crafted certificate to each lab target. Its default command is deliberately observable: id > /tmp/pwned; uname -a >> /tmp/pwned. This writes a file inside the guest; it does not establish an interactive SSH session.

~/demo/demo-latency.sh\n~/demo/demo-trigger.sh

Expected observation: the helper reports a /tmp/pwned file on the compromised VM containing uid=0(root), and no such file on the normal VM. A failed SSH handshake is consistent with the trigger: the proof is the command's effect before authentication, not a login prompt.

You can check both hosts independently from the analyst VM:

ssh root@10.77.0.20 "cat /tmp/pwned"\nssh root@10.77.0.30 "test ! -e /tmp/pwned && echo No marker on normal host"

6. Remove the lab

Leave the analyst shell, then run the cleanup target from the lab repository on the host.

make clean

06 / SCOPE & RESPONSE

Who needed to act?

The malicious versions reached some development, testing and rolling distribution channels, including Debian testing/unstable, Fedora pre-release builds and affected openSUSE Tumbleweed snapshots. Exposure depended on the package, architecture, build conditions and whether the targeted dependency chain reached sshd. Widespread compromise of stable distribution releases was not established.

For a potentially affected host, follow the distribution's security advisory: replace affected packages with its corrected or downgraded versions, restart relevant services as instructed, and review package and SSH service history. Compare installed package versions against the distribution's advisory, not just an upstream version string.

07 / TAKEAWAY

The release is part of the code

This case joined patient access to a project, a mismatch between Git and release artifacts, hidden build logic and an indirect library dependency. The small SSH slowdown made the chain visible before the backdoor spread further. The practical lesson for reviewing software is to inspect the artifact that is actually shipped and the process that builds it.

08 / THE PEOPLE

The people behind the case

A case is more useful when you can follow the people who found it, maintain the affected software and investigated the mechanics. These links lead to their own work or public project profiles.

Discoverer · PostgreSQL developer

Andres Freund

Followed abnormal SSH login times and CPU use to the compromised liblzma; published the original disclosure on 29 March 2024.

XZ Utils maintainer

Lasse Collin

Maintains XZ Utils and published the project's incident information and subsequent recovery work. The project is the right place to follow fixes and official releases.

Maintainer & incident documentation

Sam James

Helped document the incident through a detailed, evolving FAQ; is also listed as a contact for the XZ project.

Security researcher · build-stage analysis

Gynvael Coldwind

Dissected the obfuscated Bash stages that unpacked the malicious material from files disguised as tests. His own write-up provides the detailed walkthrough behind this article's lab.

Security researcher · Openwall

Solar Designer

Alexander Peslyak brought Gynvael Coldwind’s build-stage analysis to the oss-security mailing list and maintains the Openwall community that hosts the discussion.

Cryptography engineer · early technical analysis

Filippo Valsorda

Shared early reverse engineering findings, with permission, that clarified the payload's gated remote code execution rather than a simple login bypass.

Further primary sources