本文为翻译文章,原文链接:https://www.ezequiel.tech/2020/05/rce-in-cloud-dm.html
通过使用内部版本(狗粮(原文dogfood,翻译过来真的就叫狗粮))的谷歌云部署管理器(Google Cloud Deployment Manager),我能够通过Google的全球服务负载平衡器(Global Service Load Balancer)向某些Google内网站点发出请求,这就导致了RCE。
(以下三段引用是译者自己找的相关定义,表哥们可跳过)
什么是dogfood?
Google大量使用自己的产品,他们拥有庞大的开发/办公环境,可支持运行所有的的工作。由于每天都在使用这些产品,所以在Google向公众发布产品之前,会在全公司范围内发布内部测试版本,这些测试版本就叫dogfood,狗粮版本。
一般情况下这些狗粮版本具有一些公众无法使用的功能,但是可能稳定性较差。什么是Google Cloud Deployment Manager
谷歌云部署管理器,Google Cloud Deployment Manager,是一种基础架构部署服务,可以自动创建和管理 Google Cloud 资源。用户可以编写灵活的模板和配置文件,并使用它们创建包含各种 Google Cloud 服务(例如 Cloud Storage、Compute Engine 和 Cloud SQL)的部署,使其通过配置协同工作。什么是Global Service Load Balancer
原文指向的定义链接其实为:Global Software Load Balancer,GSLB,是Google的全球软件负载平衡器。它使我们能够平衡集群之间的实时用户流量,从而使用户需求与可用服务容量相匹配,从而可以以对用户透明的方式处理服务故障。如下图所示,GSLB控制与GFE的连接分配以及对后端服务的请求分配。GSLB允许我们从运行在不同集群中的后端和GFE来给用户提供服务。除了维持前端和后端之间的负载平衡外,GSLB还掌握着后端服务的运行状况,并可以自动将流量从出现故障的群集中转移出去。
这可以通过向部署管理器发送一个特定请求来实现,该请求主要用来创建一个类型提供器(Type Provider)。但添加的是一个未记录的字段叫googleOptions
。
这其中会有一个异步操作,在这异步操作过程中,部署管理器会尝试从指定的描述符URL(descriptor URL)中检索一个描述符文件(descriptor document)。
如果此项操作失败,它可能仍会在返回的错误消息中(例如内部服务器的响应)提示了某些信息。相反,如果成功的话,它将允许攻击者发出复杂的内部请求。
(这里有个示例的链接但是失效了呢)
请注意,这个问题并不仅限于对API的请求,只是说对于此类请求效果最好而已。非API站点的示例(Google帐户和ID管理“ GAIA“后端-测试站点) -该descriptorUrl
有没有都行,因为它不是一个API,一开始就没指望它会成功。
提交该漏洞报告后,谷歌支付了我$31,337(约合人民币204849.97)。
部署管理器是一项谷歌云服务,它提供了一种以编程方式处理基础结构资源的创建、删除和修改的方法(基础架构为代码)。
相关的部署管理器概念为:
类型:描述特定类型的基础结构资源的属性(例如:VM,发行凭证,用户权限), 部署管理器中有几种可用的预定义类型(称为基本类型)
类型提供器:提供服务的可用API站点及其描述符文档,以供部署管理器管理该服务中的类型(例如:用于管理VM实例的API)
资源:表示由类型提供的单个基础结构资源的实例(例如:VM实例)
模板:可重用的Python或Jinja2文件,用来以编程方式配置资源
部署:资源的集合,同时兼顾部署和管理
操作:只要在部署管理器中完成的创建、修改或删除等操作,都会返回一个操作,可以对该操作进行轮询以检查其是否完成或出现错误
与部署管理器进行交互的主要方式是通过其REST API
,其中有以下两个记录版本:v2
(通常可用)和v2beta
(公开beta)(了解有关Google产品启动阶段的更多信息请访问此链接)。
两种版本之间的主要区别在于,类型提供器仅在v2beta
版本中可用。
乍一看,确实很难理解谷歌云部署管理器。如果乡亲们对此感兴趣,我建议大家先上手使用熟悉熟悉,尤其是通过v2beta版本的REST API
方式。阅读docs,然后尝试创建部署和类型提供器来初步掌握。
我会在这篇文章中尽可能提供有用的资源链接,以期使大家更好去理解。
研究部署管理器的第一种方法是查找隐藏的或内部的类型(Types),因为某些Google服务(例如Google App Engine Flexible)在内网使用的就是部署管理器(在部署应用程序时可以在项目日志中查看到)。但是很显然,我什么都没找到。
然后,我查看了部署的Jinja2
和Python
模板。
通过反复试错,终于能够使用特制模板来创建部署(Deployments),这些部署将在操作上将数据作为Python异常返回。
通过这种方法,我能够检查Python库,读取Python代码以及列出/读取文件,但是模板的解释脚本(interpreting script)却以零特权在隔离环境上运行,甚至没有网络连接。
经过这些尝试之后,我尝试创建指向谷歌内网服务API(诸如:issuetracker.corp.googleapis.com
)的类型提供器,但是始终显示操作失败,并提示一条错误消息,指出它未收到对描述符文档的有效响应。并且,当issuetracker.corp.googleapis.com
从外部访问时,会重定向到登录门户网站的HTML 。
任何私有IP地址都会失败,并显示一条错误消息,指出该地址不是有效地址(也尝试过绕过该地址,使用指向私有IP的域和重定向,但是结果都一样)。
这么多失败的尝试让人很受挫,因此有很长一段时间都没有继续研究部署管理器(我并不是一天内一下子干完的,这是一个非常缓慢的佛系过程)。
终于,在阳光灿烂的某一天,我决定好好研究部署管理器API方法,通过谷歌云端控制台,访问指标页面(metrics page),以及在过滤器板块搜寻了很久,终于在一个标题为Methods
的下拉列表里找到了这些方法(包括一个没有文档记录的)。这些方法名称中通常包含API版本。
我注意到除了v2和v2beta(已记录的版本)之外,还有另外两个API版本,分别称为alpha
和dogfood
。
并且我还发现,仅仅通过在每个API调用中把V2
或v2beta
替换为alpha
或dogfood
,就可以引用这些版本的方法。
但是在Alpha
版本中玩了一圈,并没有发现任何可以利用的东西,so sad。
又但是,dogfood
版本倒是有趣一些,特别是当我已经注意到dogfood
这个词被用于Google服务内部测试之后。
Google中的Dogfood
产品版本通常仅适用于Google员工,因此他们会使用这些产品,并在产品移交给客户之前提交bug。
也许此版本具有内部功能,仅适用于Google员工!(嘿嘿嘿)
当我列出该版本的基本类型时,发现其中大多数都在其定义中返回了一个额外的字段:googleOptions
。
For 几个 example:
当我列出自己的类型提供器时,它们也包含了此类额外字段;另外,当我在查询中指定$outputDefaults
系统参数时,可以看到googleOptions
字段包含在哪些字段中。
深入研究了好一会儿,做了各种尝试,包括在这些字段中创建了具有不同值的类型提供器,弄清了它们各自的功能以及它们的期望值(请注意,到目前为止,我依然无法弄清楚它们中的绝大多数是干啥的):
injectProject
布尔值。无论我指定了什么值,Deployment Manager API始终在我的Type Providers上将其设置为false。效果未知。
deleteIntent
枚举。我能够找到一个有效值为CREATE_OR_ACQUIRE。效果未知。
isLocalProvider
布尔值。每当我将其设置为true时,无论其他字段中的值如何,都能成功创建类型提供程序。但是尝试使用它创建部署的话就老是会失败,并显示一条错误信息:无法检索描述符文档。
ownershipKind
枚举。有效值为UNKNOWN、USER和GOOGLE。但是将其设置为任一这些值均未观察到任何影响,在研究过程中我始终将其设置为`GOOGLE`。
transport
枚举。最初发现的有效值为:UNKNOWN_TRANSPORT_TYPE和HARPOON。将其设置为任何这些值均未观察到效果。
credentialType
枚举。我最初发现的有效值为: UNKNOWN_CREDENTIAL_TYPE和OAUTH。将其设置为任何这些值均未观察到效果。
gslbTarget
字符串。其值为空或类似于Blade:<TARGET>或gslb:<TARGET>之类的东西。将其设置为任何值均未观察到效果。
descriptorUrlServerSpec
字符串,与gslbTarget相同或为空。将其设置为任何值均未观察到效果。
上述这些值非常有用,GSLB
是Google的Global Service Load Balancer
的简称,它的作用就像内部DNS服务器和负载平衡器之间的混合体。
根据《SRE手册》,当为GSLB
提供一个符号名称(类似于域名)时,它会把流量定向到一个链接BNS地址(Borg Naming Service,博格命名服务),该地址与Google的内部IP地址等效。
这可以有很大可能用来实现内部服务器的SSRF!
但是无论我把gslbTarget
和descriptorUrlServerSpec
设置成什么值,似乎都不起任何作用。
然后,我尝试暴力破解有效的credentialType
值,并找到一个新的值: GAIAMINT
。
我之前看到过这个引用名称,比如,Google Git commit
。
在使用具有该值的类型提供器测试部署的时候,我还测试了如果将类型提供器设置为使用OAuth 2.0
访问令牌作为其身份验证机制时会发生什么情况。
得亏试了这一步,我注意到我之前设置的一个伪造的API(而不是在Authorization标头中接收访问令牌),现在将标头设置为如下所示:EndUserCreds 1 <URL-safe Base64 data>
。
我不知道如何对此进行解码,但它看起来像是具有某些其他二进制格式的协议缓冲区数据(protobuf data),并且可以获取一些字符串:anonymous
, [email protected]
(服务帐户的电子邮件,部署管理器使用该邮件作为我的项目上的token),cloud-dm
和cloudgaia::vjgv73:9898
。
看起来这货都是供内网使用的,一些谷歌员工证实它确实是供Google内网系统之间身份验证用户的,所以可能无法从外部访问。
但是除了这点怪异之处以外,我无法暴力破解credentialType
的任何其他有效值,也无法传输任何值。
在这一点上,我还尝试将staging_
添加到API版本的开头,因为我注意到谷歌计算引擎API在Staging
环境中都是这么做的(在某些地方确实提到了这种情况,例如GitHub PR中),并且它起作用了!
但是Staging
环境似乎与Production
环境完全相同。
在多次尝试失败告终之后,我又好几周都没研究过部署管理器。
有一天,我灵机一动,想使用协议缓冲区(一个谷歌开发的二进制序列化格式)找出credentialType
和transport
枚举的缺失值,因为在protobuf上的枚举被表示为数字,而不是字符串,所以我可以只从1开始计数,直到找到一个新值为止。
Protobufs主要用于gRPC,这是Google开发的并由许多Google API支持的远程过程调用(RPC)系统。
不幸的是,部署管理器API并不支持gRPC,但支持一个相对未知的功能:Proto over HTTP
。
Proto over HTTP
是一项实验性的gRPC后备功能,只在某些Google API上适用,也没有很好的文档说明,每个API的可用性各不相同,不同的API实施起来可能有所不同。不是每个支持GRPC的API都会支持Proto over HTTP
,反之亦然。所以我必须在部署管理器API上好好检查一番,并且是这样做的:
1.URL路径保持不变 (/deploymentmanager/<VERSION>/projects/<PROJECT>/global/...)
2.header的Content-Type设定为application/x-protobuf
3.在Production中,失败并显示错误消息为:不允许Proto over HTTP进行服务
4.它适用于Staging!
在熟知这些的前提下,我调用了API的get Type Provider
方法,并使用称为protoc
(协议缓冲区编译器)的工具及其--decode_raw
选项对响应协议进行了解码。
从而得到未命名的原型字段编号,以及分配给它们的值。
比较检索到的原型中的值和JSON API中的值,我迅速将每个字段号与其字段名进行匹配,并对类型提供器的协议消息定义进行逆向。
上述描述的简单示例:
1.通过JSON API创建类型提供程序:
2.通过JSON API获得了相同的类型提供程序:
3.通过Proto over HTTP API获得了相同的类型提供程序:
4.用协议解码响应:
5.找出与每个字段对应的数字(例如1 = name,2 = id,3 = insertTime等等)
6.用这些信息构造原始消息定义的近似值
经过一些修改之后,通过Proto over HTTP
在proto字段中创建具有不同值的类型提供器并解码协议缓冲区响应, 我得到了我所缺少的足够近似的值:
transport
GSLB-它将来自部署管理器的请求定向到内部Google站点,这些站点通过gslbTarget和descriptorUrlServerSpec指定
credentialType
ENDUSERCREDS,TYPE_CREDENTIAL-它们的作用似乎与OAUTH和UNKNOWN_CREDENTIAL_TYPE相同
将transport
设置为GSLB
是发出内部请求的关键!
通过新发现的transport值(设为GSLB),我可以通过精心设计的类型提供器,使得部署管理器能够将请求定向到谷歌内网站点......只要我知道gslbTarget
的地址。
这是为Google App Engine Admin API-测试环境创建类型提供程序的示例(自我的2018 GAE RCE起,由于429错误而无法从外部访问)。
在dogfood
版本上,通过列出类型获得了blade:apphosting-admin
,而appengine.v1.version
类型将gslbTarget
参数设置的就是这个值。
在命令的末尾加了-nightly
参数,因为在2018年GAE Test API
从外部禁用之前,我就关注到这个字符串参数了。
这个类型提供器工作效果很棒,我成功创建了一个Deployment
,使用它来将新应用启动到GAE Test中,以检查我在2018年发现的bug是否已正确修复。
如果我指定了一些无效的gslbTarget
参数值(并且我总是把descriptorUrlServerSpec
的值设置成与gslbTarget
的值一样),创建类型提供程序的操作将失败,要么显示一条错误消息:无法连接到GSLB端点,显示一个从内部站点返回的错误响应(通常为404 Not Found);要么显示该响应不是有效的描述符文档(例如,某些站点返回了正常的HTML)以及响应数据。
一个站点甚至返回了一个错误页面,其中包含Java堆栈跟踪和一条消息,消息内容如下:调试信息,仅对内部IP可见!(Debugging information, only visible to internal IPs!)
因此,我可以通过这种方式检索一些内部信息。
如果我指定了一些有效的gslbTarget
参数,像issuetracker.corp.googleapis.com
网站的blade:corp-issuetracker-api
(我从我过去的一些研究中获知了GSLB名字),就能够执行调用至API!
即使我不知道Issue Tracker
资源的格式如何,也可以通过在新的类型提供器上调用listTypes
来轻松克服。
这些都是有趣的问题,但我有点怀疑他们所造成的影响有多大,特别是因为这些请求是和部署管理器服务帐户的凭证(针对我的项目)一起生成的,这将可能限制允许进行通信的站点。
在研究此问题时,我告知了一些Google员工,我找到了一种发送请求给GSLB站点的方法,他们让我将其记录在VRP授权票证上,以便SRE团队可以对我的行动有所了解,万一他们能检测到我的请求呢。
他们还解释了向GSLB站点发送请求时存在的一个潜在问题:
如果服务A代表用户C向服务B发出请求,会C的授权。如果没有C的凭据,则将检查A的授权。
这个问题真的很有趣,因为我注意到部署管理器使用的服务帐户凭据是由[email protected]
委派的(我可以在Cloud Console日志中看到委派者的ID ),所以我认为,Google产品帐户至少具有委派某些服务帐户令牌的权限。
我只需要找到一种方法达成此目的,并删除服务帐户的凭据,即可使用部署管理器的身份。
到了这时,已是乌拉圭的夜晚,华灯初上,所以我只是在授权票据上写下了我的研究成果来结束这一天的研究。
第二天早上,我的爱犬在早上6点就将我叫醒,接着就收到授权票据的通知更新,其中一个如下:
然后,Eduardo迅速为我提交了一份VRP报告,对其进行了分类,然后将其升级为P0并给出了一个Nice catch
!从报告提交到Nice catch
仅用了不到5分钟的时间!,这也许是最快的RCE VRP报告:)。
那天晚些时候,我问了Eduardo几个问题,他告诉我这个bug现在被视为事故,只有RCE漏洞才被这样对待。
因此,他们要求我停止进一步深入渗透,并将我的发现过程和结果的详细信息发送给他们。
我询问了此问题的潜在进一步利用方式,我的理解是:
提权可以尝试通过部署管理器服务的身份([email protected])来实现,因为它有可能可以访问内部服务,而普通服务帐户则无法做到这一点。
不确定是否存在这样一个攻击载体(attack vectors,在这里不知道是不是跳板的意思(愚蠢的译者注)),允许攻击者获得一个连接到谷歌内部系统的shell,但其权限可能得足够高才行。
正是由于这种可能存在的极大影响,Google将其视为RCE,并发放了31,337美元的奖励(他们当前的RCE标准金额)。
非常感谢Google VRP!
这是一个非常有趣的bug,接下来我很想知道谷歌云部署管理器中还能找到哪些问题。
该漏洞已被修复。解决方法似乎只是:现在在类型提供器上执行create
、patch
或update
操作时,指定gslbTarget
和descriptorUrlServerSpec
参数无效。
dogfood
版本可能仍能通过API访问,但是,这并不意味着这是它本身的一个安全问题。(虽然可能有一些隐藏的安全漏洞)。
此外,在将我的发现报告给Google之后,甚至在完成了本文撰写的前几稿之后,我还是想检查一下是否可以通过外部公开访问Staging Deployment Manager API的dogfood版本的发现文档。
看呐,它可以: https://staging-deploymentmanager.sandbox.googleapis.com/$discovery/rest?version=dogfood
(GitHub的地址在这里,以防将来停止工作)。
该发现文档涵盖了googleOptions
域,还涉及到了其他域的范围,但还远远不够,所以即使我之前就注意到了该文档,还是得执行和上文一样的步骤。
2020年5月7日:在VRP授权票据中发现并提及了该漏洞
2020年5月8日:谷歌员工核查漏洞,提交RCE报告并迅速将其升级
2020年5月19日:颁发$31,337.00奖励
2020年5月20日:漏洞已修补