Quick navigation

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 #1 – The Python Language Server


Google Cloud Shell provides users with a feature called “Open In Cloud Shell”. By using this feature, users can create a link that automatically opens Cloud Shell and clones a Git repository hosted on either Github or Bitbucket. This is done by passing the ‘cloudshell_git_repo’ parameter to the Cloud Shell URL, as can be seen in the code below:

<a href="https://ssh.cloud.google.com/cloudshell/editor?cloudshell_git_repo=http://path-to-repo/sample.git"><img alt="Open in Cloud Shell" src ="https://gstatic.com/cloudssh/images/open-btn.svg"></a>

Upon opening the link, Cloud Shell is launched and the ‘http://path-to-repo/sample.git&#8217; repo is cloned inside the users home directory.

Multiple parameters can be passed, other than the ‘cloud_git_repo’ GET-parameter. When combining the ‘cloud_git_repo’ with the ‘open_in_editor’ parameter we can clone a repository and launch the Theia IDE on a file specified all at once. A full overview of all supported GET-parameters can be found in the Cloud Shell documentation.


When a user clones a Git repository containing ‘some_python_file.py’ and passes this file to the open_in_editor GET-parameter (‘open_in_editor=some_python_file.py’) the Theia editor starts editing the specified file. In the editor we can clearly see that all of a sudden the IDE received syntax highlighting and autocompletion capabilities:

When inspecting the processes running with ‘ps’ we notice a new process. The editor_exec.sh script fired up the pyls python language server.

wtm          736  0.0  0.1  11212  2920 ?        S<s  13:54   0:00 /bin/bash /google/devshell/editor/editor_exec.sh python -m pyls

The parent process appears to be sshd. If we attach strace to the sshd process and watch the Python language server being fired up, we can inspect all the system calls being executed. We save the output to ‘/tmp/out’ for later inspection.

While going through all the syscalls in ‘/tmp/out’ i noticed the Python language server is trying to query non-existent packages in my home directory with the stat() syscall.

538   stat("/home/wtm/supervisor", 0x7ffdf08e11e0) = -1 ENOENT (No such file or directory)
542   stat("/home/wtm/pyls", 0x7ffcbbf61a10) = -1 ENOENT (No such file or directory)
542   stat("/home/wtm/google", 0x7ffcbbf5fe00) = -1 ENOENT (No such file or directory)

When Python < 3.3 tries to import a package, it looks for a ‘__init__.py’ file which is executed. (See PEP 382 for more information). We now have our attack vector!

Constructing the exploit

If we create an evil Python git repository named ‘supervisor’, ‘pyls’ or ‘google’ containing a malicious ‘__init__.py’ we can trick the Python language server into executing arbitrary code. All we have to do is store the evil repository on Github and point our victim to https://ssh.cloud.google.com/console/editor?cloudshell_git_repo=https://github.com/offensi/supervisor&open_in_editor=__init__.py. By passing ‘__init__.py’ to the ‘open_in_editor’ GET-parameter, we force the IDE into automatically launching the Python Language Server.

That same language server now starts looking for a package named ‘supervisor’ which ofcourse now can be found since we have just cloned the repository with the same name. The malicious code hidden inside ‘__init__.py’ is then executed, meaning our victims GCP resources are compromised.

Continue reading: Bug #2 – A custom Cloud Shell image