本文为译文,原文链接为:
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.1
Host: 169.254.169.253:10001
X-Google-RPC-Service-Endpoint: app-engine-apis
X-Google-RPC-Service-Method: /VMRemoteAPI.CallRemoteAPI
Content-Type: application/octet-stream
Content-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数组。例如下面这个例子
运行代码后,会看到很多参数,其中有这样一个参数(为了便于阅读,把这里分成了多行)。
=
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 "的请求,但将主机头改为"
例如:在 "the-expanse.prom-nightly.sandbox.google.com "上测试
利用漏洞
一旦使用gRPC客户端上传了我的应用程序,其实很快会发现,在非生产(staging/tes)的GAE环境中,可以访问"stubby.Send"!,随后又经过一系列的测试,发现了如何简单的调用Stubby
1、用以下JSON PB信息调用 "stubby.GetStubId"
{
"host": "<HOST>"
}
用
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",列出目标支持的所有服务(及其方法)。
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.
然后,这个问题被抄送给了几个员工。
几天之后,再访问非生产的GAE API的时候就会显示错误页面(状态为 "429 Too Many Requests")。
但是依然可以在"staging-appengine.sandbox.googleapis.com "和 "test-appengine.sandbox.googleapis.com "看到这个消息。
后来我收到以下信息,这个漏洞,谷歌给了36337美元。
漏洞报告时间线
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云安全社区群】
进群可以与技术大佬互相交流
进群有机会免费领取节假日礼品
进群可以免费观看技术分享直播
识别二维码回复【社区群】进群
【相关精选文章】
火线Zone是[火线安全平台]运营的云安全社区,内容涵盖云计算、云安全、漏洞分析、攻防等热门主题,研究讨论云安全相关技术,助力所有云上用户实现全面的安全防护。欢迎具备分享和探索精神的云上用户加入火线Zone社区,共建一个云安全优质社区!
如需转载火线Zone公众号内的文章请联系火线小助手:hxanquan(微信)
// 火线Zone //
微信号 : huoxian_zone
点击阅读原文,加入社区,共建一个有技术氛围的优质社区!
原文始发于微信公众号(火线Zone):价值3.6 万美元的Google App Engine RCE
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论