ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GCE元数据服务安全攻防:SSRF窃取令牌链路与最小权限加固

GCE元数据服务安全攻防:SSRF窃取令牌链路与最小权限加固 1. 先认清元数据服务实例上的“身份证窗口”也是攻击的“秘密仓库”1.1 元数据服务平常是怎么工作的我做安全巡检时发现一个有意思的现象很多运维同事能熟练使用gcloud命令管理GCE实例但对实例内部的169.254.169.254这个地址几乎一无所知。他们不清楚每个GCE实例里都常驻着一个元数据服务Metadata Server也不了解这个服务在默认配置下只对实例内部开放——凡是能从这个虚拟机内发起网络请求的进程都有机会直接访问它。元数据服务的访问方式非常简单在实例的Shell里执行下面这条命令就能看到它的根目录curl -H Metadata-Flavor: Google http://metadata.google.internal/computeMetadata/v1/你也可以把域名换成链路本地地址curl -H Metadata-Flavor: Google http://169.254.169.254/computeMetadata/v1/169.254.169.254是所有主流云平台共用的链路本地元数据地址AWS、GCE、阿里云都遵循这个约定。区别于常规HTTP服务GCE元数据服务要求你在请求时带上Metadata-Flavor: Google这个请求头没有它面对普通浏览器式请求时服务会直接拒绝。这个头的作用相当于“说明自己是合法客户端”但这种校验非常初级——它只能拦住误操作拦不住有意构造的请求。元数据服务解决的问题是让虚拟机在开机阶段“认识自己”。实例ID、机器类型、所在区域、启动脚本、SSH公钥、自定义标签、服务账号信息、临时OAuth Token……这些实例运行期需要的数据都通过元数据服务暴露给虚拟机内部的进程。启动脚本最常见的写法就是在实例初始化时用一行curl把配置参数从元数据服务里拉出来再写入本地配置文件。我用一个生活化的类比描述过这个机制元数据服务就像酒店房间床头柜里的服务手册告诉你WiFi密码、餐厅开放时间、退房流程和紧急联系电话。对有权限住进房间的人来说这本手册很有用问题是这本手册放在房间里任何人都能翻到的位置而且酒店管理员不会检查翻手册的人拿了房卡还是撬了锁。1.2 攻击者在这里会盯上什么攻击者的视角和运维完全不同。拿到一台虚拟机后他们通常不会急着翻磁盘和内存而是先探测元数据服务。原因很简单这里可能藏着“云平台级别的钥匙”而不是这台机器上的普通文件。对GCE来说最诱人的资源位于下面这条路径curl -H Metadata-Flavor: Google http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token返回的JSON里包含access_token和expires_in两个关键字段令牌有效期通常是一小时。这个令牌对应用户身份是这台VM绑定的服务账号。在默认配置下新创建的GCE实例会自动绑定项目默认的Compute Engine服务账号这个默认服务账号往往拥有项目级别的Editor角色——注意是“项目级别”而不是“实例级别”。攻击者一旦成功读取这个令牌拿到的可能是整个项目的写权限。项目级Editor可以创建虚拟机、删除已有资源、修改IAM策略、读取云存储对象、调用各类管理API覆盖面非常广。攻击者拿到令牌后甚至不需要对GCP API有多熟悉直接把令牌交给gcloud命令行工具即可完成身份切换gcloud --oauth2-access-token获取到的token compute instances list这条命令一旦执行成功项目里的资产清单基本就暴露了。如果服务账号权限没做收敛后续还可以做更多危险操作。除了令牌元数据里还可能有自定义键值对。开发团队为了省事把数据库地址、API Key、甚至明文密码写进自定义元数据的例子我见过不少。这部分内容风险同样极高我放在第二部分专门展开。1.3 为什么“网络隔离”不总是能保护它很多读者第一反应是元数据服务只能从实例内部访问外部攻击者连不通169.254.169.254有什么好担心的这个想法不能算错但它忽略了一个关键事实外部攻击者通常不会直接连元数据服务而是先攻破一个能访问元数据服务的“中间载体”。这个载体可以是带SSRF漏洞的Web应用可以是失陷的第三方组件也可以是攻击者在VPC内部横向移动后控制的另一台实例。安全圈常说的“跳板”和“横移”在云环境里最常见的目标之一就是169.254.169.254。GCE默认防火墙规则虽然限制了来自公网的访问但VPC内部默认允许实例访问元数据服务。如果你习惯把所有内网资源放在同一个VPC或子网里又没有设置更严格的东西向边界规则那么VPC内任何一台机器被攻破后攻击者就可以从这台机器出发探测其他实例的元数据服务。元数据服务本身不校验请求者的身份它只关心请求是不是从“这台VM自己的网络栈”里发出的。VM A的元数据服务不会响应VM B的请求但攻击者控制了VM B之后可以利用VM B自身的网络身份请求VM B的元数据再以此为跳板获取云API权限进而操作整个项目。所以元数据服务的威胁从来不是“公网能不能直连”而是“内部任意一个能发起HTTP请求的角落是否可能成为传递令牌的载体”。位置隔离在单台VM的场景下是有效的但在复杂内网拓扑、容器网络和代理链面前它远没有大多数人想象的那么可靠。2. 自查后发现的最常见隐患把密码和Token塞进了自定义元数据2.1 这种现象为何高发有一回我给一个创业团队做GCE环境安全体检项目不算大三个开发环境十几台VM我原本以为半小时能看完。结果在检查其中一台Web服务器实例的启动元数据时发现自定义属性里存着一整段明文配置里面包含生产数据库的Host、端口、账号、密码还有一个第三方支付网关的API Secret。我当时愣住了问团队负责人为什么要这么放。对方很坦率地说“图方便。创建实例时加一句--metadata startup-script脚本里启动时把这些变量读出来比搞一套配置分发系统快多了。”这个回答我听过太多次。项目早期团队规模小把配置文件塞进启动脚本和自定义元数据确实是最快的做法。gcloud一行命令就能带上几十个键值对实例启动时自动注入省去维护配置文件的心智负担。但你付出的代价是这些键值对被放进了元数据服务的公共目录里任何能访问元数据服务的进程、任何能通过启动日志读到命令的平台工具、任何能触发SSRF的Web应用都可能把它们捞出来。更值得警惕的是元数据服务的读取没有IAM鉴权。你设置再精细的IAM策略都不会影响元数据服务内部的读取逻辑。它只认“请求是否从实例内部发起”这一条接下来就是对整个目录开放。只要攻击者控制了这个VM上任何一个普通权限的进程拉取完整键值清单只需要一条命令curl -H Metadata-Flavor: Google http://metadata.google.internal/computeMetadata/v1/instance/attributes/这条命令返回所有自定义键名攻击者再逐一把键值取回即可。整个过程不需要sudo、不需要提权、不需要扫描端口。站在攻击者角度看这是“进来就有”的资源通常比翻找磁盘上的配置文件更快更稳。2.2 这类信息泄露为什么难以快速察觉自定义元数据泄露最麻烦的地方在于它不像数据库拖库那样有异常流量可以观察。攻击者只是对VM发起HTTP请求读取一个名为metadata.google.internal的地址。这种请求在多数IDS和网络监控系统里都被视为正常的内部流量不会触发告警也不会留下特别显眼的异常特征。项目级别的自定义元数据Project Attributes则更隐蔽它会在项目内的每一台VM上都可见。如果一个团队在项目级设置某个通用API Token项目下的所有实例都能读取。我曾见过一个客户把一套内部监控系统的API Token放在Project元数据里想着“反正是内部工具项目就一个”。结果项目里一台临时测试机器被挖矿程序入侵Token被顺走攻击者用这个Token调了企业内部多个管理接口最后是靠账单异常才发现的。很多人觉得自己的环境小不在乎这类问题。但从审计角度看任何明文存储在元数据里的机密都相当于把保险柜钥匙贴在柜门上。风险大小不取决于团队规模取决于这份机密能打开多少扇门。一个几十人的团队可能只有几十个VM但如果数据库密码泄露损失的是整个业务的数据。2.3 正确做法把机密迁到Secret Manager解决这个问题标准路径是用GCP的Secret Manager服务把密码、API Key、数据库连接串这类敏感值从元数据里迁出来。整体流程可以这样做在Secret Manager里创建对应密钥条目把原明文值导入。这里注意给密钥启用版本管理后续轮换只更新新版本。给VM绑定的服务账号授予roles/secretmanager.secretAccessor权限且在IAM条件里限定到具体密钥ID不要给整个项目的授权。修改实例启动脚本通过Secret Manager API在启动阶段拉取密钥值写入本地环境文件或直接作为环境变量使用。删除自定义元数据里的相关键值对同时检查项目属性Project Attributes里有没有残留敏感项。Secret Manager的访问接入了Cloud IAM可以做到按服务账号、按密钥、按条件精确授权。而元数据服务的读取完全不经过IAM两者的安全模型有本质区别。这也是我推行“元数据零敏感信息”原则的根源——不是说你永远不能用元数据传参数而是不能把元数据当成保存凭据的地方。非敏感的配置项比如环境标识、实例角色标签、启动时需要的普通参数留在元数据里没有问题但凡是能用来“登录”“授权”“解密”的信息一律走Secret Manager加IAM路线。3. 最经典的一条攻击链路SSRF直取元数据令牌3.1 SSRF为什么恰好打中元数据服务的痛处SSRF服务端请求伪造Server-Side Request Forgery是Web应用漏洞里和元数据服务关系最紧密的一个。它的成因很直接应用程序接收用户提供的URL或目标地址然后在服务器端代用户发起请求再把响应返回给用户。常见功能包括网页预览卡片、远程图片抓取、URL健康检查、Webhook回调、文件下载代理等。开发者在实现时往往只校验用户提交的URL是不是http/https开头却没有限制请求的目标IP也没有设置出站网络边界。此时攻击者只要把这个URL改成169.254.169.254应用服务器就会替他去访问元数据服务。为什么这类漏洞对元数据服务特别致命核心在于SSRF请求是从应用服务器所在的那台VM的网络栈出去的。元数据服务不看请求的业务逻辑只看“请求是不是从本机内网发出”。应用服务器运行在GCE实例上时SSRF请求到达元数据服务的来源IP就是这台VM的内网地址元数据服务会把它当作合法本地请求处理并返回数据。传统安全设备对公网入站流量严防死守但SSRF把“内部可信网络发起请求”这个前提直接架空了一半。3.2 完整利用流程从发现参数到读取令牌我把这个利用过程拆成几步方便读者对照检查自己的应用。第一步攻击者发现某个URL参数可以被服务端回显。比如链接预览接口输入urlhttps://example.com服务器抓取页面并返回标题和图片这个接口没有限制请求内网地址也没有对响应体做裁剪。第二步攻击者把url改成http://169.254.169.254/computeMetadata/v1/。如果应用没有强制添加Metadata-Flavor: Google请求头GCE元数据服务会拒绝请求。实际攻防中有些应用会透传用户提供的Header这就给了攻击者在请求头里携带Metadata-Flavor: Google的机会。如果应用把用户可控参数拼进HTTP请求头整个读取流程就和在服务器本地敲curl几乎没区别。第三步应用把收到的响应原样返回给攻击者。攻击者这时拿到的是元数据目录页下一步直接访问目标路径http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token响应里包含access_token、expires_in、token_type三个字段。攻击者保存access_token用官方工具一测就能以这台VM的服务账号身份调用GCP API。需要说明的是GCE元数据服务在部分路径上对请求头有要求不同攻击路径的细节会有些差异。但实际项目中我见过不少SSRF漏洞恰好出现在应用可以控制请求头的场景——代码直接用用户输入拼装HTTP请求或通过URL解析透传参数。与其在根因分析时纠结“到底要带几个头”不如把防线前移到“SSRF根本不要发生”的位置代码层面过滤私网地址出口层面掐断到元数据服务的路由。3.3 拿到令牌之后还能做什么攻击者拿到令牌后的行为取决于这个VM服务账号被授予了什么权限。如果是默认服务账号且没有收敛角色大概率是项目级别的Editor权限这意味着能列举项目里所有资源、修改大部分配置甚至创建新的高权限VM。我从防御视角模拟一下最典型的后续动作。攻击者先用token列出所有实例curl -s -H Authorization: Bearer access_token \ https://compute.googleapis.com/compute/v1/projects/project-id/aggregated/instances如果项目ID未知可以从元数据服务直接读取curl -H Metadata-Flavor: Google \ http://metadata.google.internal/computeMetadata/v1/project/project-id拿到实例清单后攻击者会继续探测项目里的存储桶curl -s -H Authorization: Bearer access_token \ https://storage.googleapis.com/storage/v1/b?projectproject-id这种“从令牌到资产枚举”的过程速度极快几十秒就能完成。如果服务账号权限更大攻击者还可能修改IAM策略、导出密钥、在内部网络建立持久化通道。整个过程在Cloud Audit Logs里有记录但很多团队不会实时盯审计日志等发现问题时往往是几天之后了。一次SSRF的成功相当于把VM的云身份直接交到攻击者手上。防御SSRF本质上是在保护云平台的“身份证系统”这也是我为什么反复强调要同时处理“漏洞本身”和“漏洞被利用后的凭证价值”。4. 防守配置四层加固从身份到网络的纵深防御4.1 服务账号最小化降低被盗令牌的权限价值如果攻击者拿到的token只有极小权限SSRF的成功也只能造成有限损失。服务账号最小化是这里的第一道防线也是最基础却最容易被忽略的工作。具体操作可以这样展开不要在GCE实例上使用项目默认服务账号。默认服务账号在项目里通常有较广泛的角色建议专门创建服务账号只授予实例实际需要的权限。一台只跑Nginx的Web服务器服务账号只需要访问指定存储桶或Secret Manager里某个密钥那就只授予这些具体资源上的最小权限角色。按工作负载拆分服务账号。数据库实例、应用实例、CI构建实例分别用不同服务账号不要共用一个“万金油”账号。这既降低单点风险也让审计日志里的身份归属更清晰。定期review服务账号的角色绑定。通过gcloud projects get-iam-policy或Console的IAM页面查看每个服务账号绑定的角色凡是没见过、已停用、权限过大的绑定关系立即清理。一个常见的误区是认为把VM的API访问范围scopes限制住就安全了。API范围只是在VM内部限制了部分调用的“明文能力”不代表服务账号在IAM层面的实际权限。过度依赖scopes而不去精简IAM角色在SSRF场景里仍然可能暴露不必要的能力。比如一个VM限制了storage只读范围但服务账号本身有项目级Editor角色攻击者拿到token后依然可以通过IAM API给自己授权更高权限或者调用其他未受scope限制的接口。所以重点还是收敛服务账号在IAM层的角色绑定。4.2 网络层拦截压缩元数据服务的暴露面GCE元数据服务默认只开放给实例内部公网被防火墙挡住。但这不意味着可以什么都不做。在复杂网络里VPC内部的跳板机、代理网关、容器隔离失效的Pod都可能成为访问元数据服务的入口。我建议在网络层做两件事。第一梳理VPC的防火墙规则避免“全网段开放”。很多项目为了省事在防火墙里放一条允许公网/0访问实例的规则这在云环境里风险极高。应该按源IP分段控制特别是不要把生产VM暴露到任意来源只放行必要的运维跳板机或负载均衡器的健康检查来源。第二在高安全等级环境里收缩实例对元数据服务的访问口径。GCE允许通过防火墙规则和网络标签组合限制哪些实例可以访问169.254.169.254。我操作过的一种做法是给需要访问元数据服务的实例打特定标签创建一条允许该标签实例访问169.254.169.254的规则对不需要访问元数据的实例用更高优先级的规则显式拒绝对该地址范围的访问。这里必须谨慎验证因为元数据服务同时承载实例启动期间的网络配置信息误伤会导致实例初始化异常。容器场景还要额外注意Host网络模式。如果Pod直接使用GCE VM的宿主网络容器内进程和VM进程共享同一个网络栈元数据服务对容器同样可见。Kubernetes节点上的Pod如果配置了hostNetwork: true或者CNI实现允许Pod直接访问链路本地网关地址都需要单独评估隔离策略。最理想的情况是普通工作负载的Pod不分配HostNetwork权限集群内通过NetworkPolicy限制Pod到元数据服务地址的访问。4.3 应用层防护出站代理与请求校验应用层是决定SSRF能否落地的关键防线。一个Web应用理论上很难过滤掉所有恶意请求但可以通过网络出口做统一收口把被封堵的部分放到代理层解决。常见的做法是把VM的默认出站流量引导到显式代理或专用出口网关然后在代理层设置域名/IP白名单。应用进程不直连外网全部经过代理转发。代理只允许访问业务所需的域名比如特定API域名、特定对象存储bucket域名其他全部拒绝。这样一来即使应用存在SSRF漏洞攻击者试图访问169.254.169.254时请求会在代理策略中被拦截。这个方案在大型项目里很常见但实施时要注意几个细节。代理自身不能部署在被审计的同一台VM上否则攻击者拿到VM权限后可以改写代理配置。代理的日志要完整保存便于事后审计和追踪异常出站请求。代理规则变更要有审批流程和变更记录防止运维人员为了排查问题临时放开全部出站规则又忘记回收。如果暂时没条件上代理至少在应用代码里增加一层过滤在发起外呼请求之前解析目标URL的IP如果是私网地址段或链路本地地址段直接拒绝。这个逻辑要覆盖IPv4和IPv6的所有保留地址范围包括127.0.0.0/8、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、169.254.0.0/16、::1、fc00::/7等。同时要注意DNS rebinding问题解析后拿到的IP和实际连接时的IP可能不同更稳妥的做法是在建立连接前对解析结果做二次校验或者直接使用能强制指定IP连接的网络库。4.4 监控与响应如何发现异常引用最后一道防线是“看得见”。元数据服务内部的HTTP访问云平台默认不会为这种内部流量强制打审计日志但GCP管理面上的API调用记录是有的。你需要关注的是哪些服务账号、哪些时间、从哪些来源IP调用了GCP管理API。打开Cloud Audit Logs为compute.googleapis.com、storage.googleapis.com、iam.googleapis.com等关键管理面配置导出规则投递到日志收集系统。为非典型的API调用设置告警基线。比如一个平时只处理Web请求的实例服务账号突然开始批量列举实例列表或下载大对象这极可能是令牌被盗的信号。也可以借助GCP的Security Command Center这类安全托管服务它自带基础威胁监测能力能识别常见的可疑行为模式。但它只是一个辅助工具真正决定安全性的仍然是你账号权限的收敛程度和出站网络的控制力度。另外我建议把针对元数据服务地址的访问行为纳入Web应用日志。很多Web框架不会为这种内部链接的访问单独记录但如果应用因SSRF产生过外呼日志里通常能看到url参数异常或响应体长度异常。在WAF层可以对解析目标IP落在169.254.0.0/16范围内的请求添加告警和阻断规则。我们团队就在网关层加了一条规则凡是业务请求URL解析结果为169.254.0.0/16的直接打安全事件并隔离会话这条规则拦截过好几次来自内部应用的恶意探测。5. 安全自检清单与长期维护建议5.1 快速自查清单如果你接手了一个GCE项目不确定之前有没有踩过这些坑可以从下面几项快速自查登录一台实例在Shell里执行curl -H Metadata-Flavor: Google http://metadata.google.internal/computeMetadata/v1/instance/attributes/检查自定义元数据键值里有没有密码、Token、数据库连接串等高敏感信息有则需要迁移到Secret Manager。查看项目属性里的自定义键值对curl -H Metadata-Flavor: Google http://metadata.google.internal/computeMetadata/v1/project/attributes/同样检查是否有敏感项残留。用同一个VM的服务账号打开IAM页面查看它绑定的项目级角色。如果发现是Editor或Owner级别而VM实际只是普通业务机器说明权限严重过大立即收敛。检查防火墙规则里有没有过于宽松的“允许公网访问实例”和“允许非必要端口对外开放”的规则。GCE默认规则是合理的最小化很多团队后来会越加越宽。排查Web应用里是否存在用户可控URL且响应可回显的功能主要关注远程图片抓取、网页预览、URL检测类接口。如果存在至少要加私网地址段过滤和Host校验。对照这张表可以快速定位当前状态检查项高风险状态建议处理自定义元数据含密码/Token/连接串迁至Secret Manager并清理服务账号角色项目级Editor/大量权限收敛到具体资源的最小权限防火墙规则存在公网全段访问按源IP分段最小化Web应用出站用户可控制URL且无过滤加IP段过滤或上代理收口审计监控未导出管理面日志配置Audit Logs导出与告警5.2 维护周期与个人建议安全配置不是一次性工作元数据服务的问题尤其如此。我建议至少每季度做一次服务账号权限review每次实例模板或启动脚本变更时顺便检查有没有新增敏感信息被塞进元数据。很多问题不是一开始就有的而是某次上线时顺手加了一个--metadata db_passwordxxx之后就再也没人管过。如果条件允许建议在CI/CD流程里加一条静态扫描规则。凡是出现在云计算metadata字段里的键名只要带password、secret、token、key、credential这些关键字直接打回并提示使用Secret Manager。这条规则实现成本很低收益却很高比任何后期审计都更早拦截问题。我在实际排查中最大的体会是GCE元数据服务本身是一个设计高效的功能问题不出在“它存在”这件事上而出在我们对它的依赖方式。把实例的云身份凭证、把业务的关键密钥都放在一个“无IAM鉴权、只靠网络位置隔离”的HTTP接口里本质上是用安全纵深换开发便利。只要接受这个前提防线就很清晰——先收敛服务账号权限再控制网络出口最后做好审计监控。三层都做扎实元数据服务的安全风险基本能压到可控范围。这几个月里凡是找我审过的GCE环境有一半以上或多或少存在元数据里塞敏感值的情况。这也从侧面说明元数据服务配置风险是真的容易被忽略但它一旦被利用影响又往往是整个项目级别的。希望这篇内容能帮你在自己的环境里提前做一次排查别等到SSRF被利用、令牌被偷走之后再来复盘。
返回列表