价值3.6 万美元的Google App Engine RCE

admin 2022年8月3日23:03:02评论29 views字数 7087阅读23分37秒阅读模式

https://www.ezequiel.tech/p/36k-google-app-engine-rce.html


我是如何发现漏洞的


前段时间,我偶然发现每一个谷歌应用引擎(GAE)的应用程序都会用"X-Cloud-Trace-Context "头来响应每个HTTP请求,所以我认为返回该响应头的网站都可能运行在GAE上。


也正是因为这个原因

我了解了appengine.google.com本身就是在GAE上运行的,但是它可以执行一些其他地方无法完成的操作,而普通用户的应用程序也无法完成这些操作,所以我就想,它是如何完整这些操作的。我认为有可能它必须利用一些API/接口或者只有谷歌自己运行的应用程序才可以用的服务等,所以我想试试能不能找到这些API


首先,开始我学习了GAE应用程序如何执行内部操作(例如写日志或者获取OAuth的token),我发现在Java8环境下,它是通过发送协议缓冲区(PB)消息到http://169.254.169.253:10001/rpc_http 的内部HTTP端口。


请求与下面类似

POST /rpc_http HTTP/1.1Host: 169.254.169.253:10001X-Google-RPC-Service-Endpoint: app-engine-apisX-Google-RPC-Service-Method: /VMRemoteAPI.CallRemoteAPIContent-Type: application/octet-streamContent-Length: <LENGTH>
<PROTO_MESSAGE>

而PB是一个 "apphosting.ext.remote_api.Request "消息,其中包括。

  • service_name = Name of the API to call

  • method = Name of the API's method to invoke

  • request = Bytes of the inner PB request (Encoded in binary wire format)

  • request_id = Security ticket (Given to the app with every GAE request), this is required even though it is

HTTP请求的响应包就是对应的PB消息,表示API回复的消息,或者回复错误的消息。

安全票据可以通过这几行代码获得(在Java 8运行时)。

import com.google.apphosting.api.ApiProxy;import java.lang.reflect.Method;
Method getSecurityTicket = ApiProxy.getCurrentEnvironment().getClass().getDeclaredMethod("getSecurityTicket");getSecurityTicket.setAccessible(true);String security_ticket = (String) getSecurityTicket.invoke(ApiProxy.getCurrentEnvironment());

为了举例这一个过程。我想使用"https://www.googleapis.com/auth/xapi.zoo"来获得一个谷歌OAuth Token,步骤如下

1、生成一个 "apphosting.GetAccessTokenRequest "消息,其中包括。

  • scope = ["https://www.googleapis.com/auth/xapi.zoo"]

2、生成一个 "apphosting.ext.remote_api.Request "消息,其中包括。

  • servicename = "appidentity_service" (The API that provide access to the GAE Service Account)

  • method = "GetAccessTokenRequest"

  • request = The bytes of the PB message generated in the previous step, encoded in binary wire format

  • request_id = Security ticket

3、发送HTTP请求

4、解码响应,它应该是一个 "apphosting.GetAccessTokenResponse "消息

由于这个端口可以访问一些内部的东西,我确信这一定与 "appengine.google.com "用来执行内部操作的东西有关,但我在HTTP端口中找不到任何东西。

最开始我猜测可能使用了在同一个服务器(169.254.169.253)上的其他端口,所以上传了一个Nmap到GAE,并且针对服务器进行扫描(为了在GAE中运行二进制文件,我把它们和应用程序一起上传,然后在运行时把它们复制到/tmp并给它们执行权限,因为文件系统的其余部分是只读的。)

我发现端口4是开放的,构造请求发送之后,收到了乱七八糟的数据,但是有一个可读的字符串,在网上查了一下,我发现这是一个gRPC服务

我想在GAE上运行一个Java的 gRPC客户端,但是有些问题,比如内置的gRPC库似乎并不完整,每次上传一个完整的库时,还是会调用原来的内置库,所以我写了一个C++的客户端在GAE上运行

经过一系列的尝试与报错,我发现gRPC服务就像HTTP端口一样,运行着一个 "apphosting.APIHost "API。但是有一个地方不同,它可以选择对PB信息进行JSON编码,而不仅仅只是二进制,所以这样让测试变的更加容易。

下面举一个客户端的实际例子

由于我在服务器中没有发现其他东西,我觉得 "appengine.google.com "在内部做的操作要么是连接访问不同的服务器,要么是用RPC服务(HTTP/gRPC)来调用一些隐藏的API或者方法。我试过用Nmap来扫描看看能否找到其他的服务器,但是我只找到了Metadata元数据的服务器,这没啥用,所以我确信它肯定使用了隐藏的API,所以如何找到这些隐藏的API?

首先,我收集了所有能找到的协议缓冲区定义(从.JAR文件中发现的.CLASS文件中提取,以及从运行时中发现的二进制文件中提取),并在其中搜索任何可能指向某些隐藏API的东西(我提取的所有PB定义文件都可以在这里找到https://github.com/ezequielpereira/GAE-RCE/tree/master/protos)。我发现 "apphosting/base/appmaster.proto "文件很有希望,它有几个PB信息,似乎是修改App Engine内部设置的内部方法,还有一个名为 "AppMaster "的API,其中定义了一些方法,但经过几次试验,没有找到这些方法的调用。

因为没有在PB定义中找到隐藏的API/方法,所以只能去其他地方试试看,还是在二进制文件中寻找,主要遇到的问题就是文件多,并且比较费时,而且还有很多我不理解的东西(不过,我使用字符串+grep的组合来搜索的,对逆向不太了解),但在看到主要的二进制文件"javaruntimelauncher_ex"有很多命令行参数后,那么有个新的想法,它在GAE环境中运行的时候会收到什么样的参数。

最开始,其实想直接获取参数是比较难的,我试过把找到的每一个Java变量与它相应的参数联系起来,但是这不太现实。

后来又试了一些比较好的方法,例如在C++中创建一个Java库,用一个方法来读取传递给启动器的参数并返回,这样会比较方便切简单,多亏了Stack Overflow的帖子,用这几行代码就可以检索信息。

int argc = -1;char **argv = NULL;
static void getArgs(int _argc, char **_argv, char **_env) { argc = _argc; argv = _argv;}
__attribute__((section(".init_array"))) static void *ctr = (void*) getArgs;

然后用一个简单的方法将参数转换为一个Java数组。例如下面这个例子

运行代码后,会看到很多参数,其中有这样一个参数(为了便于阅读,把这里分成了多行)。

--api_call_deadline_map=  app_config_service:60.0,  blobstore:15.0,  datastore_v3:60.0,  datastore_v4:60.0,  file:30.0,  images:30.0,  logservice:60.0,  modules:60.0,  rdbms:60.0,  remote_socket:60.0,  search:10.0,  stubby:10.0

我马上就看到了我已经用过的API,例如LogService(用于写日志),所以我判断这些是通过内部HTTP端口提供的一些API

我还看到了"stubby",在之前的一些谷歌产品中的错误信息中看到过它,在SRE中也读到过这个,所以我觉得这里是一个RPC基础设施,有可能是 "appengine.google.com "执行内部操作的一种方式。

目前来看,已经知道了一个内部API的名字,但是如何知道它有哪些方法?

用C++gRPC客户端试了几个方法名称,但是都返回错误,这些方法不存在,所以没办法只能在谷歌上搜,发现一篇2021年的帖子,上面的错误信息是:

 The API call stubby.Send() took too long to respond and was cancelled.

所以试了一下Send方法,可是还是返回错误。

但是我觉得这个方法肯定是存在的,错误信息可能只是我的请求方法有问题,所以为了看看存在与不存在的错误的差异性,想找到一个存在的方法和一个不存在的方法来看看区别,随后发现如果在gRPC客户端中,没有设置 "apphosting.APIRequest.pb"字段,那么请求一个不存在的方法就会返回不存在的错误,如果是一个存在的方法,就会返回请求不完整,所以"stubby.Send"这个方法实际上是存在的

那么现在如何调用这些方法?

实在是想不出怎么在生产的GAE部署环境中去访问这些方法,但是后来想起来,因为这个报错,已经获得了对staging(staging-appengine.sandbox.googleapis.com)和test(test-appengine.sandbox.googleapis.com)GAE部署环境的访问权(一般来说,普通的Google用户无法访问非生产部署环境

还好对这些部署环境有一些研究,知道怎么去调用其中运行的应用程序

1、首先上传一个manual scaling的版本(否者无法正常返回,会返回403,可能因为其他的原因吧)。

2、执行对 "www.appspot.com "的请求,但将主机头改为".prom-.sandbox.google.com"如果应用程序通常在"save-the-expanse.appspot.com"上运行,应该把""改为 "save-the-expanse", 如果把应用程序上传到暂存的GAE环境,应该把""改为 "qa",如果上传到测试环境的GAE,应该改为"nightly"。

例如:在 "the-expanse.prom-nightly.sandbox.google.com "上测试

利用漏洞

一旦使用gRPC客户端上传了我的应用程序,其实很快会发现,在非生产(staging/tes)的GAE环境中,可以访问"stubby.Send"!,随后又经过一系列的测试,发现了如何简单的调用Stubby

1、用以下JSON PB信息调用 "stubby.GetStubId"

{  "host": "<HOST>"}

设置为你想调用的方法的托管地址(例如,"google.com:80","pantheon.corp.google.com:80","blade:monarch-cloudprod-streamz")。"blade:"似乎像谷歌使用的内部DNS系统,例如,"blade:cloudresourcemanager-project "在内部是"cloudresourcemanager.googleapis.com "在外部(有些,如 "blade:monarch-cloudprod-streamz",没有一个与外部对应的)。

2、前面的请求将返回一个JSON PB消息,"stub_id "是其唯一的字段,然后保存这个值

3、用以下JSON PB信息调用 "stubby.Send"。

{  "stubby_method": "/<SERVICE>.<METHOD>",  "stubby_request": "<PB>",  "stub_id": "<STUB_ID>"}

为了找到 "stubbymethod "的值,可以把它设置为"/ServerStatus.GetServices",并设置一个空的 "stubbyrequest",它将返回一个格式化的 "rpc.ServiceList",列出目标支持的所有服务(及其方法)。是PB消息的字节数(二进制线格式)。

4、如果成功,调用将返回一个JSON PB消息,"stubby_response "是其唯一的字段,它将有响应的PB消息字节(以二进制线格式)。

发现这一点后,我做了一些测试,但无法找到任何我认为危险的Stubby调用。尽管如此,我还是向谷歌报告了这个问题,优先级别为P1。

在第一次报告之后,当我再次回顾我之前发现的一切,还是想找到一些可以成功用于攻击的方法,我注意到,除了"stubby",在从Java启动器二进制得到的参数中还有一个"appconfigservice",这是另外一个隐藏的API。

在之前获得的PB定义中,没有办法直接找到它的方法,在谷歌中搜索也没有结果,但是后来发现在"apphosting/base/quotas.proto "中提到了,例如"APPCONFIGSERVICEGETAPPCONFIG",稍微测试一下就会发现,"appconfig_service.GetAppConfig "是一个真正的隐藏方法。

"appconfigservice"有几个比较有意思的方法,其中最有意思的是"appconfigservice.ConfigApp "和 "appconfigservice. SetAdminConfig "方法,因为这两个方法允许我配置一些内部的设置,例如允许电子邮件的发送者,应用程序的服务账户ID,忽略配额限制,并将我的应用程序设置为"SuperAPP"(虽然不知道这里的意思,但是听起来就感觉很强),并赋予"FILEGOOGLE3ACCESS"

appconfigservice.SetAdminConfig "方法的请求信息为 "apphosting.SetAdminConfigRequest",而 "appconfigservice.ConfigApp "的请求信息为 "apphosting.GlobalConfig"。

由于 "apphosting/base/quotas.proto",我还发现了其他一些API/方法,如 "basement.GaiaLookupByUserEmail"。

发现这一点后,马上向谷歌报告了新的发现,他们提升了报告的优先级并回复。

Please stop exploring this further, as it seems that you could easily break something using these internal APIs.

然后,这个问题被抄送给了几个员工。

价值3.6 万美元的Google App Engine RCE

几天之后,再访问非生产的GAE API的时候就会显示错误页面(状态为 "429 Too Many Requests")。

但是依然可以在"staging-appengine.sandbox.googleapis.com "和 "test-appengine.sandbox.googleapis.com "看到这个消息。

价值3.6 万美元的Google App Engine RCE

后来我收到以下信息,这个漏洞,谷歌给了36337美元。

价值3.6 万美元的Google App Engine RCE

漏洞报告时间线

1、2018年2月。发现问题 2、2018年2月25日。初次报告(只有 "存根 "API)。3、2018年3月4日和5日。发现并报告 "appconfigservice "的API 4、2018年3月6日和13日之间。对非生产 GAE环境的访问被阻止,出现了429错误页面 5、2018年3月13日。发放了36337美元的奖金 6、2018年5月16日。问题被确认为已修复


线Zone

价值3.6 万美元的Google App Engine RCE



价值3.6 万美元的Google App Engine RCE


价值3.6 万美元的Google App Engine RCE


价值3.6 万美元的Google App Engine RCE

线Zone[线]线Zone

线Zone线hxanquan


价值3.6 万美元的Google App Engine RCE

//  线Zone //

 : huoxian_zone


价值3.6 万美元的Google App Engine RCE


原文始发于微信公众号(火线Zone):价值3.6 万美元的Google App Engine RCE

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年8月3日23:03:02
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   价值3.6 万美元的Google App Engine RCEhttps://cn-sec.com/archives/1220269.html

发表评论

匿名网友 填写信息