黑客系列:你的Ansible包配置安全吗?

admin 2021年11月17日20:31:16黑客系列:你的Ansible包配置安全吗?已关闭评论483 views字数 6464阅读21分32秒阅读模式

译文来源:https://blog.includesecurity.com/2021/06/hack-series-is-your-ansible-package-configuration-secure/。受个人知识所限或偏见影响,部分内容或存在过度曲解误解现象,望师傅们包含提出建议,感谢。

我们对客户的评估工作中,需要对各种类型的软件和云系统进行入侵,我们经常被要求去检查一些诸如Ansible这样的配置管理工具。在这篇文章中,我们将深入探讨在Ansible的世界中存在的软件包管理漏洞是什么样的。首先,我们先要回顾一下Ansible是什么,然后向安全人士提供一些提示技巧,以便于在较低层次上对其进行调试,并探讨dnf模块中的CVE和apt模块中的一个有趣的问题。

为了保障我们对DevSecOps的持续关注与向防御方人员提供的帮助建议,本系列的下一篇文章中将会讲到Semgrep等工具在捕获Andisble配置漏洞方面上的优势和劣势。

Ansible

Ansible 是一款开源的,基于Python的,并由红帽公司开发的配置管理工具。它使DevOps等其他系统的维护人员能够轻松地用YAML格式编写由一系列任务组成的自动化脚本,然后针对目标主机来运行这些脚本。

Ansible的一个关键特点就是无代理的:目标主机无需安装Ansible,只需要安装Python和SSH。运行脚本的机器(也就是Ansible中的“控制节点”)通过SSH将运行任务所需的Python代码复制到目标主机(“受管节点”),然后远程执行这些代码。受管节点在“inventory(仓库)”中被组成一组,以便于被脚本锁定。

黑客系列:你的Ansible包配置安全吗?

在2019年,Ansible曾是最受欢迎的云配置管理工具。然而,由于存在“不可改变的基础设施”这一范式的存在,导致人们后来更加热衷于选择Terraform和Docker来执行一些之前可能由Ansible来完成的任务,但它仍然是一个非常受欢迎的配置资源、服务和应用程序的工具。

Ansible提供了大量的内置模块,这些模块本质上就是调用aptyumsysctl等常用系统命令的高级接口。这些模块都是一些用于将指定的YAML任务翻译成在受管节点上实际执行命令的Python文件。例如,下面的脚本中包含了一个Ansible任务,使用apt模块在基于Debian的系统上安装NGINX。通常情况下,Ansible 脚本是针对远程主机运行的,但在我们的例子中,为了说明一些问题,我们是针对本地主机运行的。

yaml
- name: Sample Apt Module Playbook
hosts: localhost
become: yes
become_user: root
tasks:
- name: ensure nginx is installed
apt:
name: nginx
state: present

为了更好地了解这一脚本在底层到底做了什么,让我们使用一个调试技术,这一技术在我们后面检查漏洞的时候会很有用。由于Ansible本身并没有提供查看确切运行命令的方法,我们可以使用一个简单的strace调用。strace可以让我们对该脚本在ansible-playbook下正常运行时触发的系统调用流程进行跟踪,即便是Ansible产生出了多个子进程(“-f”标志),这样我们就可以查看最终被执行的命令。

sh
$ sudo strace -f -e trace=execve ansible-playbook playbook.yml 2>&1 | grep apt
[pid 11377] execve("/usr/bin/apt-get", ["/usr/bin/apt-get", "-y", "-o", "Dpkg::Options::=--force-confdef", "-o", "Dpkg::Options::=--force-confold", "install", "nginx"], 0x195b3e0 /* 33 vars */) = 0

使用strace命令行选项(“-e trace=execve”)并用grep作为过滤器,我们能够确保无关的系统调用信息不会输出到终端上;这也就避免了Ansible和apt模块在最终完成任务前需要运行所有配置代码的干扰噪音。最终,我们可以看到,脚本运行了apt-get install nginx的命令,并增加了一些额外的命令行标志,以自动接受确认提示和交互对话。

如果你在跟踪流程的过程中,没有在strace的输出中看到apt-get install命令,请确保NGINX是卸载或未安装状态的。为了提高性能并杜绝一些不必要的副作用,Ansible首先会检查该任务是否已经实现,所以如果它认为NGINX已经处于安装状态的话,就会提前返回“ok“状态结束任务。

Ansible安全审计的十大技巧

Ansible将以简单的YAML格式声明的任务转换为系统命令,通常以root身份在受管节点上运行。这一抽象概念很容易导致那些看上去任务要做的事情和实际发生的事情之间的不匹配。我们将探讨Ansible内置模块中的这种不匹配在哪些地方有可能会对所有的受管节点中产生配置漏洞。

但首先,让我们回过头来看,通过这些对Ansible管理的基础设施进行审计的通用技巧来了解一些情况。从基础设施的安全角度来看,Ansible并不像其他一些配置管理工具那样会暴露出很多攻击面。SSH是用于从控制节点连接到受管节点的默认传输方式,因此Ansible流量利用了OpenSSH服务器提供的理智的默认值、加密技术以及与Linux服务器的集成性。然而,Ansible还可以以多种方式进行部署,在编写角色和脚本时可能会错过最佳实践。下面是IncludeSec的十大Ansible安全检查技巧,在审查配置时最好是要记住这些:

  1. 是否使用了旧版本的Ansible,且该版本易受到已知CVE漏洞的攻击
  2. 在YAML文件中是否存在硬编码的密码
  3. 不同环境(生产环境、开发环境、暂存环境)中的受管节点是否没有适当的分离成库存?
  4. Ansible运行的控制节点是否被基于主机/操作系统的安全控制完全锁定?
  5. 是否启用了便于模板注入的不安全检索
  6. SSHD的配置文件是否使用了不推荐的设置,如允许root登录或启用远程端口转发?
  7. 是否正在使用替代性的连接方法(如ansible-pull),并对其进行了适当的保护?
  8. 默认情况下,是否对脚本运行的输出进行了记录或审计?
  9. 特权任务的机密输出是否进行了记录
  10. 相关性高的角色/任务(例如那些管理认证、或安装软件包的角色/任务)是否真的在运行它所看上去那样的任务?

这些提示技巧是否适用,显然取决与该组织是在Ansible Tower这样的工具后面管理Ansible,还是在一个所有开发人员对生产环境都有SSH访问权的初创公司。然而,有一点是不变的,那就是Ansible通常是用安装包的方式来部署受管节点的,所以包管理任务中的配置漏洞是特别值得关注的。我们将重点讨论以Ansible YAML格式声明的常见软件包管理操作,可能会产生意想不到的安全后果的情况。

CVE-2020-14365:dnf模块中忽略了软件包签名

在Ansible模块中,YAML抽象概念和现实之间最明显的不匹配类型是一个彻头彻尾的bug。最近的一个例子就是CVE-2020-14365。dnf模块使用dnf软件包管理器安装软件包,dnf是yum的后继者,在Fedora Linux上是默认安装的。这个bug是该模块没有对其下载的软件包进行签名验证导致的。下面是在Ansible版本2.8.15到2.9.13之间上运行时存在漏洞的任务示例:

yaml
- name: The task in this playbook was vulnerable to CVE-2020-14365
hosts: localhost
become: yes
become_user: root
tasks:
- name: ensure nginx is installed
dnf:
name: nginx
state: present

这一漏洞如果被那些高级攻击者盯上的话,就会变得非常验证;为供应链攻击打开了一个缺口。由于缺乏签名验证,软件包镜像和网络中的中间人(MITM)攻击者都有可能提供自己的软件包,在安装过程中以主机上的root身份执行任意命令。

黑客系列:你的Ansible包配置安全吗?

关于如何执行这种攻击的更多细节,这一指南中从MITM的角度出发,介绍了有关注入后门的apt软件包。这个场景是几年前在HackTheBox的靶机上提供的。

在大多数情况下,Linux发行版上的GPG软件包签名是赋予下载软件包真实性和完整性的唯一方法,而这一事实使得问题变得更加严重。这些软件包镜像并没有广泛的应用HTTPS(详见此处为何APT不使用HTTPS的理由),还包括dnf。即便是在镜像与主机之间采用了HTTPS传输,CVE仍然可以被恶意的镜像利用,但至少是要比MITM攻击难得多了。我们做了一个快速测试,尽管Fedora比Debian使用了更多的HTTPS镜像,但由于地理上的相近原则,一些默认的镜像源仍是只有HTTP的:

黑客系列:你的Ansible包配置安全吗?

该CVE产生的根本原因是Ansible的dnf模块导入了一个Python模块作为处理dnf操作的接口,但没有调用一个关键的_sig_check_pkg()函数。据推测,这个检查要么是被遗忘了,要么被认为是在导入的模块中自动执行的。

在对软件包版本进行降级操作时,可以绕过软件包的签名检查

dnf的例子显然就是一个bug,不过现在已经修复好了,所以让我们继续讨论一个更加微妙的不匹配类型吧,YAML接口没有干净地映射到所需要的底层行为。这一次是在apt包管理器模块中,我们在一些生产环境中的Ansible脚本中看到了这个错误。

在一个大型的基础设施中,从多个源中安装软件包是非常常见的,这些源包括了官方发行库、第三方软件库和内部软件库。有时,一个软件包的最新版本会导致依赖性问题或删除所依赖的功能。一些比较杂乱的团队经常采取的解决方案是将软件包降级到最后一个有效的版本。虽然降级操作不应该是一个长期的解决方案,但当最新版本的软件正在破环生产环境或该软件包的更新存在错误时,降级行为就会变得有必要了。

当通过命令行进行交互运行时,apt install(和apt-get install,这些命令对于我们安装软件的目的来说是一样的)允许你指定一个你想降级的旧版本软件包,它将完成这项工作。但是当自动接受确认提示时(在Ansible使用的“-y”模式下),apt会产生报错,除非明确指定了--allow-downgrades参数。因为降级可能会对其他软件包造成破坏,所以需要进一步的确认。但是Ansible的apt模块并没有提供一个与--allow-downgrades的等价选项;没有明确的方法可以在Ansible中进行降级操作。

如果搜索“ansible downgrade package(Ansible降级软件包)”时在Stackoverflow中出现的第一个答案建议使用force: true(或force: yes,在YAML中的作用相同):

yaml
- name: Downgrade NGINX in a way that is vulnerable
hosts: localhost
become: yes
become_user: root
tasks:
- name: ensure nginx is installed
apt:
name: nginx=1.14.0-0ubuntu1.2
force: true
state: present

这种做法没有问题,如果没有后续操作的话,这种模式可以成为一个组织定期在主机上运行的固定配置。但不幸的是,它产生了一个类似于dnf CVE的漏洞,导致签名验证失效。

为了探究下到底发生了什么,让我们使用strace命令来查看一下完整的调用流程:

sh
$ sudo strace -f -e trace=execve ansible-playbook apt_force_true.yml 2>&1 | grep apt
[pid 479683] execve("/usr/bin/apt-get", ["/usr/bin/apt-get", "-y", "-o", "Dpkg::Options::=--force-confdef", "-o", "Dpkg::Options::=--force-confold", "--force-yes", "install", "nginx=1.14.0-0ubuntu1.2"], 0x1209b40 /* 33 vars */) = 0

force: true 选项增加了--force-yes参数(如apt模块文档中所述)。但--force-yes就是一把钝锤,它将忽略安装中的任何问题,包括下载软件包中的坏签名。如果在命令行中手动运行同样的apt-get install命令,它会警告:--force-yes is deprecated,use one of the options starting with --allow instead(--force-yes已经过时了,请使用以--allow开头的选项代替)。值得称赞的是,Ansible还在文档中警告说明force“是一种破坏性的行为,有可能会对你的系统产生破坏,几乎不应该被使用。”

那么,为什么在我们看到的Ansible部署中,force: true的使用如此普遍?这是因为除了使用命令shell模块运行完整的apt install命令之外,没有其他的方法来解决这种常见的降级情况,而这一风格与Ansible的宗旨也恰恰相反。

对于Ansible问题的追踪器这一问题,人们多年来一直要求apt模块提供一个allow_downgrade的选项,但有两个单独的pull请求因为不符合项目的需求而一直被搁置。Ansible的每项功能都需要集成测试,而由于Debian的衍生发行版通常不会在其默认的仓库中存放旧版本的软件包来进行降级,就导致这些测试很难提供这一功能。要知道,自2018年以来,yumdnf模块就已经有了allow_downgrade选项了。

解决问题方案

在IncludeSec,我们喜欢尽其所能为开源做出贡献,所以我们已经公开了一个pull请求,以解决apt模块的这个缺点。这次的修改中包含了集成测试,希望能满足项目需求并得到合并!

与此同时,如何在Ansible管理的基础设施中安全的降回一个旧版本的软件包呢?首先,运行一次性的apt install命令,加上--allow-downgrades选项。接下来,可以使用Apt Pinningdpkg holding来阻止软件包的后续升级,这是Debian衍生发行版中的原生方法,可以实现这一点。Ansible可以通过dpkg_selections模块来执行保持这一操作:

yaml
- name: Downgrade and Hold a Package
hosts: localhost
become: yes
become_user: root
tasks:
- name: ensure nginx is downgraded
command:
cmd: "apt install -y -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef --allow-downgrades nginx=1.16.0-1~buster"
- name: ensure nginx is held back
dpkg_selections:
name: nginx
selection: hold

总的来说,这种方法并不浅显易懂,也不巧妙,因此也就成了一个在YAML接口与所需底层操作之间的泄露抽象概念的示例。

本系列的下一部分将探讨如何使用Semgrep来识别该漏洞以及Ansible脚本中的其他漏洞。我们将回顾一下Ansible的十大安全审计检查技巧,看看有多少艰难的工作可以通过静态分析来实现自动化。请各位静候佳音!

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年11月17日20:31:16
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   黑客系列:你的Ansible包配置安全吗?https://cn-sec.com/archives/632639.html