In the previous post we described how to perform privilege escalation using an unprivileged shell outside a container and a root shell within an unprivileged docker container. The method used in that post relied on the ability to create device files within the container and pass the device file to the user outside the container through /proc/PID/root.
In this post we show another method that, instead of smuggling a device file out of the container - we smuggle a file descriptor into the container. Once root has access to the file descriptor they are able to perform actions on the referenced file even if it is outside the mount namespace of the containerized process.
Both of these attacks are relevant in a setting where someone is trying to provide an unprivileged user with a limited root by using containers. One example of this would be in a managed laptop used by a developer. Developers usually want root on their machine while the local IT-organization doesn't want to give root to employees. In such a scenario it may be tempting to give the developer access to root within a container, which we again show is not a good idea.
The base of this method uses the AF_LOCAL sockets, also known as unix domain sockets. These are special sockets that are placed on the filesystem instead of being bound to specific ports on a network device or IP address. Clients that want to access the service listening on a socket points to said file when creating their socket.
Now a nice part of such sockets is that one can send file descriptors over them. If you read the last post, we discussed how root within a container is limited because they can't address files outside the container because of the use of a mount namespace. In this post we describe how to pass a file descriptor into the container, allowing root in the container to actually address files within.
Let's get to it! To abuse the AF_LOCAL socket to help root out of a container we do the following:
1. In container, as root: Create AF_LOCAL socket listening on file /socket
2. In container, as UID of user outside container: Launch process, save pid Step2_PID
3. Outside container: Connect to the /socket file through /proc/Step2_PID/root/socket
4. Outside container: Send file descriptor for root directory /
5. In container, as root: Receive file descriptor and modify files outside container
And to make this process simple an example program is available at: https://github.com/FSecureLABS/fdpasser. The example program does a chmod 6777 on a file outside of the container, in the example below this file is /etc/shadow.
In container, as root: ./fdpasser recv /moo /etc/shadow
Outside container, as UID 1000: ./fdpasser send /proc/$(pgrep -f "sleep 1337")/root/moo
Outside container: ls -la /etc/shadow
Output: -rwsrwsrwx 1 root shadow 1209 Oct 10 2019 /etc/shadow
As containers provide a way of isolating root it is sometimes assumed that they can be used to give a low privilege user access to root without providing the user full control on the machine. In this post we have shown that such a root user within a container with the CAP_CHOWN and CAP_SETUID capabilities, provides an opportunity for an unprivileged user to abuse their root level access within the container to escalate their privileges outside of the container. Both CAP_CHOWN and CAP_SETUID are provided to root in unprivileged docker containers.