本文为译文,原文地址:https://www.trendmicro.com/en_us/research/22/i/stronger-cloud-security-in-azure-function-using-custom-cloud-container.html
我们已经对Azure Functions和Azure App Services中的安全漏洞及其后果进行了大量撰写。开发人员可以增强云安全性并最小化这些差距的一种方法是创建自定义容器映像并使用Distroless 方法。在这篇文章中,我们将讨论转向熟练的开发人员可以做些什么来最大程度地减少这些安全漏洞的影响,特别是在 Azure Function 中。
Azure 函数
Azure Functions是一种无服务器解决方案,旨在为开发人员简化应用程序的部署和维护。
从表面上看,我们有应用服务计划,它保证了物理硬件的分配,我们可以把它想象成一个虚拟机。在里面,我们可以找到一个已安装的 Docker 容器引擎。此引擎执行使用 Azure-function-host 运行时构建的容器映像。Azure-function-host,顾名思义,有效地管理 Azure Function Runtime,使其负责与 Azure 后端的通信。
此架构在触发无服务器功能执行时执行 azure-functions-worker,然后使用提供的功能代码执行实际的无服务器应用程序。
图 1. Azure Functions 的简化架构
在 Azure Function 中创建自定义容器
所选堆栈的默认容器映像可以替换为自定义容器映像。在这种情况下,映像必须包含azure-function-host,以便它可以与 Azure Functions 一起正常工作。值得一提的是,创建自定义容器的选项仅适用于 Azure Functions Premium 计划的 Linux 平台。
图 2. 使用自定义 Docker 容器创建 Azure Function
对于此博客条目,我们按照Azure 文档使用 Python 作为我们的代码解释器来创建自定义容器。但是,我们稍作修改,选择 Azure 中的私有容器注册表进行部署。
图 3. 为 Azure 自定义容器无服务器部署设置私有注册表
我们在本地构建容器镜像,然后将其推送到我们配置为与无服务器功能链接的私有注册表中。
图 4. 部署图
构建镜像
对于我们的基础映像,我们从Microsoft Container Registry中的Azure Functions Base 列表中选择了mcr.microsoft.com/azure-functions/python:4-python3.9。
现在我们回到本博客条目的目标,即在不影响其功能的情况下更好地保护 Azure Functions 的使用。这个目标可以分解为三个目标:
- 删除无服务器应用程序执行上下文中的敏感环境变量
- 减少容器镜像并限制我们的应用程序所需的权限
- 尽量减少我们的更改对 Azure Functions 功能的影响
重要的是要注意,函数主机可能需要一些环境变量才能运行,因此整个无服务器应用程序才能工作。但是,我们希望确保我们的无服务器应用程序无法访问此类敏感变量。
在开始之前,我们需要确定基于 azure-functions/mesh:3.7.1-python3.9 创建 Azure 函数和使用 Azure Function Base-Python 映像创建相同函数时选择的 Python 堆栈的差异。
图 5. 容器镜像比较
如图 5 所示,网格容器映像在 root 用户下执行初始化包装脚本,然后在 app 用户下使用 sudo 命令执行 WebHost.dll 二进制文件,从而将所有环境变量传递给 WebHost.dll。相比之下,基础镜像默认在 root 用户下执行 WebHost.dll 二进制文件。WebHost.dll 然后执行 python-worker,该进程随后将执行无服务器代码本身。
删除敏感的环境变量
WebHost.dll 中需要敏感的环境变量才能运行。由于这种性质,敏感信息会被继承到 python-worker 进程中,并从中执行无服务器代码。由于变量是进程内存的一部分,我们删除它们的选项是有限的。另外,我们可以利用读权限和/proc/文件系统的性质,打印在同一用户下运行的其他进程环境变量。
图 6. 访问其他进程环境变量
由于此功能,最好的选择是更改 WebHost.dll 二进制文件(或其配置)的功能,以在不同的用户下执行语言工作者,而无需敏感的环境变量。
由于我们已经掌握了容器镜像构建过程,我们可以研究什么是最好的更改点。由于我们的解释器是 Python,因此注入代码的最简单方法是重命名容器映像中的 Python 二进制文件,并将其替换为原始名称下的自定义 shell 脚本。
图 7. 容器环境分析
我们的 shell 脚本的内容很简单。我们使用sudo -u www-data命令以不同的用户身份执行 Python worker,而无需传递环境变量。
如果开发人员想要传递环境变量,他们可以使用 unset 命令和 sudo 的 E 参数来限制对敏感变量的访问。
图 8. 在用户 www-data 下执行 python 并传递所有其他参数
如图 9 所示,我们能够摆脱环境变量并在需要时限制对敏感变量的访问。
图 9. 在没有额外环境变量的情况下运行 python worker
图 10. 拒绝访问敏感的环境变量
我们还测试了我们所做的更改是否仍然允许我们在 Azure 环境中成功运行我们的无服务器功能。图 11 显示了该测试的结果。
图 11. 在 Azure 上运行的自定义容器,没有环境变量
distroless 方法:减少容器镜像和限制权限
我们的第二个目标是将容器二进制文件和图像大小减少到最低限度(应用程序及其依赖项),这种方法更好地称为无发行方法。使用这种方法,我们将通过删除对于运行应用程序不是必需的二进制文件来减少我们的自定义容器,并且可以在成功利用的情况下为攻击者提供有用的工具。
我们从容器镜像中删除的二进制文件都是来自/bin目录的二进制文件,其中也包括 shell。因此,我们需要稍后更新我们的环境调整。在我们的演示示例中,我们还删除了位于/usr/bin目录中的curl、wget和perl二进制文件。
图 12. distroless 容器 Dockerfile 示例
最大限度地减少我们的变化的影响
我们现在需要尽量减少更改的影响并确保功能。因为我们删除了 shell 解释器,所以我们的脚本将无法工作,因此我们将脚本替换为执行相同工作的自定义编译二进制文件。然而,我们没有使用 shell 解释器,而是使用了execve系统函数。这个函数让我们为新进程设置环境变量,允许我们指定我们应用程序中需要的非敏感环境变量,我们可以使用getenv函数动态获取。
图 13. 自定义二进制 PoC
图 14. 在 Azure Serverless 环境中成功部署自定义映像,指定可用于执行的无服务器代码的环境变量
结论
在之前的博客文章中,我们讨论了我们在云中看到的架构设计缺陷,这些缺陷可能允许恶意行为者在成功利用环境变量后滥用环境变量。
在我们关于Azure App Services 威胁模型的条目中,我们展示了架构设计中的漏洞,例如对包含敏感信息的容器和环境变量使用主 root 密码。我们解释了为什么将敏感信息存储在环境变量中是一个坏主意,即使 DevOps 社区可能不这么认为。我们还描述了将敏感信息保存在环境变量中的后果。
如前所述,我们正在将讨论转向开发人员可以做些什么来最大程度地减少云中安全漏洞的影响。我们的目标是通过对允许开发人员生成的容器映像进行一些微调来做到这一点。开发人员不仅应该知道在表面之下运行的是什么,而且还应该知道信任默认图像有其局限性。他们应该仔细评估服务,即使在使用值得信赖的服务时也要学会保持警惕。
加强安全性和维护应用程序功能可能很困难。我们证明,通过适当的容器镜像设计,可以摆脱环境变量并将不敏感的环境变量转移给低权限的语言工作者,因此看到平台开发人员也执行此类安全措施并不牵强。